Muduo網絡庫實現 [十五] - HttpContext模塊

目錄

設計思路

類的設計

解碼過程

模塊的實現

私有接口

請求函數

解析函數

公有接口

疑惑點


設計思路

記錄每一次請求處理的進度,便于下一次處理。

上下文模塊是Http協議模塊中最重要的一個模塊,他需要記錄每一次請求處理的進度,需要保存一個HttpRequest對象,后續關于這個連接的http的處理的信息全部都是在這個上下文中保存。

那既然是記錄,肯定就要讓它知道當前是在哪個進度了,所以我們就用狀態碼去表示

//處理狀態
enum HttpRecvStatu{RECV_ERR,   //接收錯誤RECV_LINE,  //接收請求行RECV_HEAD,  //接收頭部RECV_BODY,  //接收正文RECV_OVER   //接收完畢
};

注意:這些枚舉值通常表示當前正在處理的狀態,而不是已經完成的狀態。

同時,由于收到的http請求報文可能是會出錯的,而出錯的話,我們是不會將這個報文進行業務的處理的,而是直接返回一個請求錯誤的狀態碼的報文,那么我們的上下文當中不可避免的還需要保存一個變量用來保存狀態碼。

還需要有一個 HttpRequest 對象來存儲從客戶端請求中解析出的各種信息。

具體來說,HttpRequest 對象通常會存儲以下請求要素:

  1. 請求方法 (Method):如 GET、POST、PUT、DELETE 等
  2. 請求的 URL 路徑
  3. HTTP 版本 (如 HTTP/1.1)
  4. 請求頭 (Headers):如 Content-Type、User-Agent、Cookie 等
  5. 請求參數 (如 URL 中的查詢參數)
  6. 請求體 (Body):POST 請求中包含的數據
  7. 其他請求相關的元數據

類的設計

既然它需要那么多狀態,那說明它肯定是要處理這些狀態的,也就是需要處理請求行,處理頭部,處理正文,處理錯誤。那這些報文從哪里來呢?肯定是需要從緩沖區接收過來的,當然接收過來之后,我們還需要把緩沖區的數據的請求行段,請求頭部段,請求正文段,分離出來,以便后續我們使用。接下來我們先看一個解碼的過程,來加深理解一下流程

解碼過程

假設我們收到了以下 HTTP 請求:

GET /my%20documents/report.pdf?search=machine+learning&year=2023 HTTP/1.1

第一步:解析請求行

正則表達式匹配結果:

  • matches[1] = "GET"(請求方法)
  • matches[2] = "/my%20documents/report.pdf"(路徑部分)
  • matches[3] = "search=machine+learning&year=2023"(查詢字符串部分)
  • matches[4] = "HTTP/1.1"(HTTP版本)

第二步:解碼路徑部分

我們對 matches[2] 使用 UrlDecode(matches[2], false) 進行解碼:

  • 參數 false 表示不將加號(+)轉換為空格
  • 將 %20 解碼為空格字符
  • 輸入:"/my%20documents/report.pdf"
  • 輸出:"/my documents/report.pdf"

第三步:處理查詢字符串

  1. 首先拆分查詢字符串(matches[3]):
    • 輸入:"search=machine+learning&year=2023"
    • 使用 & 作為分隔符拆分
    • 結果:["search=machine+learning", "year=2023"]
  2. 對每個參數進行處理: 對于第一個參數 "search=machine+learning":
    • 找到等號(=)位置:pos = 6
    • 提取鍵:str.substr(0, pos) = "search"
    • 提取值:str.substr(pos + 1) = "machine+learning"
    • 對鍵進行解碼:UrlDecode("search", true) = "search"(無需解碼)
    • 對值進行解碼:UrlDecode("machine+learning", true) = "machine learning" (注意這里使用 true 參數,將加號轉換為空格)
    • 將鍵值對保存到請求對象:_request.setParam("search", "machine learning")
    對于第二個參數 "year=2023":
    • 找到等號(=)位置:pos = 4
    • 提取鍵:str.substr(0, pos) = "year"
    • 提取值:str.substr(pos + 1) = "2023"
    • 對鍵進行解碼:UrlDecode("year", true) = "year"(無需解碼)
    • 對值進行解碼:UrlDecode("2023", true) = "2023"(無需解碼)
    • 將鍵值對保存到請求對象:_request.setParam("year", "2023")

結果

請求對象中現在包含以下數據:

  • 路徑: "/my documents/report.pdf"
  • 查詢參數:
    • "search" -> "machine learning"
    • "year" -> "2023"

所以我們的類聲明如下?

#define MAX_LINE 8192  // HTTP請求行最大長度// HTTP請求解析上下文類
class HttpContext
{
private:HttpParseStatu _parse_status; // 當前解析狀態int _status_code;             // 狀態碼HttpRequest _request;         // 存儲解析出的HTTP請求信息private:bool RecvLine(Buffer *buf);   // 接收請求行bool RecvHead(Buffer* buf);   // 接收請求頭部bool RecvBody();              // 接收請求正文bool ParseLine(const string &line);  // 解析請求行bool ParsesHead(string &line); // 解析請求頭部public:HttpContext();                // 構造函數void ReSet();                 // 重置解析狀態int StatusCode();             // 獲取HTTP狀態碼HttpParseStatu ParseStatus(); // 獲取當前解析狀態HttpRequest& Request();       // 獲取解析完成的HTTP請求void RecvHttpRequest(Buffer *buf); // 接收處理HTTP請求數據
};

模塊的實現

對于私有模塊,也就是接收緩沖區的數據,然后把請求行,請求頭部,請求正文獲取到并且解析出來,然后放入到HttpRequest中存儲,以供應用層進行調用

私有接口

請求函數

對于請求函數,基本上就是大差不差,先判斷狀態是否匹配,然后獲取緩沖區數據的一行數據,進行判斷是否是完整的數據,然后判斷異常,異常有兩種,第一種是沒有拿到請求數據(請求行,請求頭部,請求正文),但是緩沖區的數據已經超過最大值了,但還不是完整的數據,這說明數據是錯的,那么我們就不處理,直接修改狀態成錯誤,設置錯誤狀態碼就行了。第二種就是拿到了請求數據,但是數據也是巨大,這個時候我們也是不處理,修改狀態為錯誤。設置錯誤狀態碼。如果合法了就開始解析請求數據,然后更新數據。這里解析請求數據是不一樣的,等會重點講解析的函數

    bool RecvLine(Buffer *buf) // 接收請求行{if (_parse_status != RECV_HTTP_LINE){return false;}string line = buf->GetLineAndPop(); // 根據 HTTP 協議規范,HTTP 請求的結構是固定的,第一行必須是請求行if (line.size() == 0)               // 說明出問題了,去緩沖區找問題{if (buf->ReadAbleSize() > MAX_LINE) // 說明此時緩沖區有數據,但是數據太大了還沒有結束{_parse_status = RECV_HTTP_ERR;_status_code = 414; // URI TOO LONGreturn false;}// 否則就說明緩沖區的請求行太少,還沒發完,再等等return true;}if (line.size() > MAX_LINE) // 說明雖然拿到了請求行,但是肯定是錯誤的,請求行的數據哪能那么多{_parse_status = RECV_HTTP_ERR;_status_code = 414; // URI TOO LONGreturn false;}//走到這就說明請求行合法了,開始處理bool ret = ParseLine(line);if(ret == false){return false;}//請求行狀態結束,更新下一個狀態_parse_status = RECV_HTTP_HEAD;return true;}bool RecvHead(Buffer* buf)   // 接收請求報頭{if (_parse_status != RECV_HTTP_HEAD) //進行判斷是否是請求報頭的狀態了{return false;}//走到這就說明第一行的請求行已經被取走了,現在緩沖區的第一行就是請求報頭了while(1)//因為報頭的格式每行都是xxxx\n\r,一直到空格行才算結束{string line = buf->GetLineAndPop();if (line.size() == 0)               // 說明出問題了,去緩沖區找問題{if (buf->ReadAbleSize() > MAX_LINE) // 說明此時緩沖區有數據,但是數據太大了還沒有結束{_parse_status = RECV_HTTP_ERR;_status_code = 414; // URI TOO LONGreturn false;}// 否則就說明緩沖區的請求行太少,還沒發完,再等等return true;}if (line.size() > MAX_LINE) // 說明雖然拿到了請求行,但是肯定是錯誤的,請求行的數據哪能那么多{_parse_status = RECV_HTTP_ERR;_status_code = 414; // URI TOO LONGreturn false;}//走到這就說明拿到了正常的數據if(line == "\n" || line == "\r\n") //如果這一行是報頭的結束標志就要退出循環{break;}int ret = ParsesHead(line); //因為每一行都不一樣,所以每取一行就要進行保存if(ret == false){return false;}}_parse_status = RECV_HTTP_BODY;return true;}bool RecvBody()   // 接收請求正文{if(_parse_status != RECV_HTTP_BODY){return false;}size_t content_size = _request.GetLength(); //這也是固定格式,就是正文中會有一行是表示正文長度的if(content_size == 0)  //因為正文可能有也可能沒有{_parse_status = RECV_HTTP_OVER;return true;}//因為正文可能會很長int real_size = content_size - _request._body.size();//如果緩沖區數據充足if(buf->ReadAbleSize() >= real_size){_request._body.append(buf->ReadPos(), real_size);buf->MoveReadIndex(real_size);_parse_status = RECV_HTTP_OVER;return true;}//如果緩沖區數據不夠,先全拿出,但是不能設置狀態_request._body.append(buf->ReadPos(), buf->ReadAbleSize());buf->MoveReadIndex(buf->ReadAbleSize());return true;}

對于請求報頭來說,因為每行都是xxx: yyyyy\r\n;的格式,但是內容是不同的,所以我們要處理一行就解析一行,不然等你讀取完你再處理,你xxx對應的值是yyyy,不還是要把每行再分離出來嗎?所以就需要一個循環,取一行就解析一行。一直到讀取到空格行,也就是結束的標志。然后把狀態更新一下

對于請求正文來說,也有一些不同,因為一般情況下,正文的數據會非常的多,一次性會處理不完,處理不完怎么辦呢,那么我們就讀取一點拿過來一點,然后記錄下這個讀取長度。在請求頭部中,會有個記錄請求正文長度的,我們獲取到這個長度之后,然后減去這個已經存儲的長度,就是剩余我們還需要的長度,然后下次再進行判斷,如果緩沖區的數據大于了還需要的長度,就說明已經有充足的數據了,然后就直接把剩余的數據追加到之前的數據后面就可以了,接著更新狀態

解析函數

首先,我們最先從緩沖區獲取的數據也就是請求行數據,那肯定最先用的就是解析請求行函數了

解析請求行的時候我們使用的正則表達式是這個:

(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?

?在這個正則表達式的匹配結果中,如果我們的url中沒有攜帶參數,那么參數部分的匹配結果就是一個空串,他也是在matches里面的,這一點我們不需要關心,因為后續我們解析參數的時候會將這種情況給他處理了。

然后我們用?bool ret = regex_match(line, matches, e); 去把獲取到的數據放在maches中,然后通過matches[1],[2],[...]放到我們定義的HttpRequest對象中存儲起來。

query 變量存儲的是從HTTP請求URL中提取的查詢字符串(query string)部分。

具體來說,當HTTP請求有如下形式時:

GET /path/to/resource?name=value&another=data HTTP/1.1

query 變量會存儲 name=value&another=data 這部分內容。

在正則表達式匹配中,matches[3] 對應的是第三個捕獲組 (?:\\?(.*))? 中的 (.*) 部分,也就是問號 ? 后面的所有內容,直到空格之前。

之后的代碼會進一步處理這個查詢字符串:

  1. 使用 & 分隔符將查詢字符串分割成多個鍵值對
  2. 對每個鍵值對,查找 = 的位置來分離鍵和值
  3. 對鍵和值進行URL解碼(處理百分號編碼和特殊字符)
  4. 將解碼后的鍵值對存儲到請求對象

最后就是

  • 從已經找到的每個查詢字符串部分(如"name=value")中,使用等號("=")的位置將字符串分割成兩部分
  • string key = Util::UrlDecode(str.substr(0, pos),true);
    • 提取等號前面的部分作為鍵(key)
    • 使用str.substr(0, pos)獲取從字符串開始到等號位置的子字符串
    • 然后用Util::UrlDecode進行URL解碼,true參數表示將加號(+)轉換為空格
  • string val = Util::UrlDecode(str.substr(pos+1),true);
    • 提取等號后面的部分作為值(value)
    • 使用str.substr(pos+1)獲取從等號后一個位置到結尾的子字符串
    • 同樣進行URL解碼,true參數表示將加號轉換為空格
  • _requset.SetParam(key,val);
    • 將解碼后的鍵值對添加到HTTP請求對象中
    • 這樣應用程序就可以通過HTTP請求對象訪問這些查詢參數
    bool ParseLine(const string &line)  // 解析請求行{smatch matches;std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?");bool ret = regex_match(line, matches, e);if(ret == false){_parse_status = RECV_HTTP_ERR;_status_code = 400; // BAD REQUESTreturn false;}//存儲請求的方法,資源路徑,協議版本_request._method = matches[1];_request._path = Util::UrlDecode(matches[2],false); //資源路徑需要解碼,但是只有查詢字符串才要空格轉加號_request._version = matches[4];string query = matches[3];//分三步:1.分割&前后的內容放入vector中 2.循環查找=的位置,分割=前后的內容,前面是key,后面是val 3.存儲kvvector<string> query_array;Util::Spilt<query, "&", &query_array>;for(auto &str : query_array){size_t pos = str.find("=");if(pos == string::npos) //這個是固定格式,如果沒有找到=就說明是錯誤的{_parse_status = RECV_HTTP_ERR;_status_code = 400; // BAD REQUESTreturn false;}string key = Util::UrlDecode(str.substr(0, pos),true);string val = Util::UrlDecode(str.substr(pos+1),true);_requset.SetParam(key,val);}return true;}

接下來就是解析報頭了,先把每行中的最后\n\r給刪掉,然后去找每行的固定格式: 找到之后更新狀態就行了

    bool ParsesHead(string &line) // 解析請求報頭{//先刪除末尾無用字符if(line.back() == "\n"){line.pop_back();}if(line.back() == "\n"){line.pop_back();}size_t pos = line.find(": "); //這個也是固定格式if(pos == string::npos){_parse_status = RECV_HTTP_ERR;_status_code = 400; // BAD REQUESTreturn false;}}

?為什么會沒有解析正文這個函數呢?

  • 靈活性考慮 - HTTP請求正文有多種格式(JSON、XML、表單數據、二進制數據等),不同應用需要不同的解析方式。如果在庫中內置特定的解析方法,可能會限制庫的通用性。
  • 分離關注點 - 這符合"關注點分離"的軟件設計原則,網絡庫負責網絡通信和基本協議解析,應用層代碼負責特定業務邏輯和數據格式解析。
  • 避免依賴膨脹 - 如果實現各種正文格式的解析,可能需要引入額外的依賴庫(如JSON解析庫),這會使muduo庫變得臃腫。
  • 性能考慮 - 通用的解析方法可能無法滿足特定應用的性能需求,讓用戶自行實現可以針對特定場景進行優化。

在實際使用中,muduo的這種設計讓開發者可以根據自己的需求選擇適合的請求正文解析方式,比如對于JSON格式可以使用rapidjson,對于表單數據可以自行實現解析邏輯等。這提供了更大的靈活性,也符合C++庫的設計理念。

公有接口

HttpContext() - 構造函數,初始化解析狀態為接收請求行(RECV_HTTP_LINE),狀態碼為200(成功)。?
void ReSet() - 重置函數,將狀態恢復到初始狀態: 重置狀態碼為200?
重置解析狀態為接收請求行?
清空請求對象?
int StatusCode() - 返回當前HTTP狀態碼。?
HttpParseStatu ParseStatus() - 返回當前的解析狀態(如接收請求行、接收頭部等)。?
HttpRequest& Request() - 返回已解析的HTTP請求對象的引用,供上層訪問。?
void RecvHttpRequest(Buffer *buf) - 核心解析函數,根據當前解析狀態調用相應的處理函數: 如果是接收請求行狀態,調用RecvLine?
如果是接收頭部狀態,調用RecvHead?
如果是接收正文狀態,調用RecvBody

    HttpContext():_parse_status(RECV_HTTP_LINE),_status_code(200){}void ReSet(){_status_code(200);_parse_status(RECV_HTTP_LINE);_request.Reset();}//返回狀態碼int StatusCode(){return _status_code;}//返回解析狀態HttpParseStatu ParseStatus(){return _parse_status;}//返回已經解析并處理的請求信息HttpRequest& Request(){return _request;}void RecvHttpRequest(Buffer *buf){switch (_parse_status){case RECV_HTTP_LINE:RecvLine(buf);case RECV_HTTP_HEAD:RecvHead(buf);case RECV_HTTP_BODY:RecvBody(buf);default:break;}}

疑惑點

讀取請求的接口,為什么不要break?

  • 提高解析效率 - 允許在一次函數調用中盡可能多地解析數據。例如,如果緩沖區中同時包含了請求行和請求頭部的數據,這種設計可以一次性處理完所有可用數據,而不必等待下一次調用。
  • 狀態機連續處理 - HTTP解析是一個狀態機過程,當一個狀態處理完成后,如果有更多數據可以處理,應該立即進入下一個狀態進行處理。
  • 最大化緩沖區利用 - 充分利用每次RecvHttpRequest調用處理盡可能多的數據,減少處理延遲。

解析和接收兩個意思一樣嗎?

接收(Receiving)

  • 指的是從網絡中獲取原始數據(字節流)的過程
  • 屬于網絡 I/O 操作,涉及到套接字讀取
  • 關注的是"如何獲取數據"
  • 例如:從 TCP 連接讀取字節流到緩沖區

解析(Parsing)

  • 指的是將已接收的原始數據轉換為結構化信息的過程
  • 屬于數據處理操作,涉及到語法分析
  • 關注的是"如何理解數據"
  • 例如:將 HTTP 原始報文分解為請求行、頭部字段、請求體等

在 HTTP 服務器的工作流程中:

  1. 首先接收原始的 HTTP 請求報文(字節流)
  2. 然后解析這些字節流,提取出各種 HTTP 請求組件
  3. 最后基于解析結果進行業務處理

?為什么需要設置單個接收函數,比如接收請求行,接收請求報頭,難道不能設置一個函數用于接收整個報文嗎?

為什么要解析呢 它既然接收成功了不就說明是一個完整的嗎??

解析就好比翻譯,你收到了一封外國友人的信,他是用他們國家的語言寫的,你雖然有一個完整的信,但是你讀不懂內容,所以就需要翻譯信的內容了?

std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?");?這個是什么?

_request._path = Util::UrlDecode(matches[2],false); //資源路徑需要解碼,但是只有查詢字符串才要空格轉加號

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/900209.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/900209.shtml
英文地址,請注明出處:http://en.pswp.cn/news/900209.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

解決GraalVM Native Maven Plugin錯誤:JAVA_HOME未指向GraalVM Distribution

目錄 問題描述解決方案為什么需要這樣配置&#xff1f; 問題描述 在你的項目中&#xff0c;如果你遇到了以下錯誤信息&#xff1a; [ERROR] Failed to execute goal org.graalvm.buildtools:native-maven-plugin:0.10.5:test (native-test) on project DIctSystemInJavaUsing…

java 代碼錯誤分析

錯誤代碼 class Test {private static String name; // 聲明一個私有靜態變量 namename "World"; // 靜態初始化塊&#xff0c;給 name 賦值為 "World"System.out.print(name); // 打印 name 的值public static void main(String[] args) {System.out.p…

企業供應鏈管理

企業供應鏈管理 企業供應鏈管理 企業供應鏈管理企業信息化信息化的作用信息化的發展階段信息化建設的挑戰 SRM&#xff08;供應商關系管理&#xff09;SRM架構參考圖企業內部系統協作&#xff1a; ERP (企業資源計劃)OA (辦公自動化)業務功能模塊&#xff1a;企業日常辦公 EMS …

Pascal語言的系統監控

Pascal語言的系統監控 引言 在現代計算機系統中&#xff0c;系統監控是確保計算機平穩運行的重要組成部分。無論是個人計算機還是大型服務器&#xff0c;監控系統的性能、資源使用及狀態&#xff0c;都是提高系統效率、及時發現問題的關鍵。Pascal語言作為一種結構化編程語言…

出現次數超過一半的數(信息學奧賽一本通-1186)

【題目描述】 給出一個含有n&#xff08;0 < n < 1000&#xff09;個整數的數組&#xff0c;請找出其中出現次數超過一半的數。數組中的數大于-50且小于50。 【輸入】 第一行包含一個整數n&#xff0c;表示數組大小&#xff1b; 第二行包含n個整數&#xff0c;分別是數組…

解決 CANoe 多測試用例下固定 IP 地址沖突問題的分析與方案

問題描述&#xff1a; CANoe的測試環境如下&#xff1a; 在Ethernet1總線上&#xff0c;通過VN5620連接了PCU&#xff08;實物&#xff09;&#xff1b; 使用VtestStudio&#xff08;VTS&#xff09;開發&#xff0c;并且生成了三個測試腳本(vtt文件)&#xff0c;分別為&#…

React 項目使用 pdf.js 及 Elasticpdf 教程

摘要&#xff1a;本文章介紹如何在 React 中使用 pdf.js 及基于 pdf.js 的批注開發包 Elasticpdf。簡單 5 步可完成集成部署&#xff0c;包括數據的云端同步&#xff0c;示例代碼完善且簡單&#xff0c;文末有集成代碼分享。 1. 工具庫介紹與 Demo 1.1 代碼包結構 ElasticP…

python爬蟲:小程序逆向(需要的工具前期準備)

前置知識點 1. wxapkg文件 如何查看小程序包文件 打開wechat的設置&#xff1a; .wxapkg概述 .wxapkg是小程序的包文件格式&#xff0c;且其具有獨特的結構和加密方式。它不僅包含了小程序的源代碼&#xff0c;還包括了圖像和其他資源文件&#xff0c;這些內容在普通的文件…

Prolog語言的強化學習

Prolog語言的強化學習 引言 強化學習&#xff08;Reinforcement Learning, RL&#xff09;是機器學習的一個重要分支&#xff0c;它通過與環境交互來學習最優策略&#xff0c;以最大化累積獎勵。在強化學習中&#xff0c;智能體&#xff08;Agent&#xff09;通過試錯方式與環…

開源且完全沒有審核限制的大型語言模型的概述

開源且完全沒有審核限制的大型語言模型的概述 關鍵要點 研究表明&#xff0c;存在多個開源的大型語言模型&#xff08;LLM&#xff09;完全沒有審核限制&#xff0c;適合開放對話。包括基于 Llama、Mixtral、Phi-2 和 StableLM 的模型&#xff0c;參數范圍從 2.78 億到 4050 億…

思二勛:未來所有的業務都將生于AI、長于AI、成于AI

每個時代都有其標志性的技術&#xff0c;每個技術的產生或極大地解放了個體的勞動力&#xff0c;提高了個體與組織之間的協作效率&#xff0c;或極大地促進了生產效率或使用體驗&#xff0c;或將極大地優化了資源配置和供需匹配效率&#xff0c;從而提高人們的生活水平。從青銅…

瑪卡巴卡的k8s知識點問答題(六)

21. 什么是 ReplicaSet&#xff0c;說明它的主要用途。 ReplicaSet是k8s中的一個控制器&#xff0c;他用于保證任何時候&#xff0c;都有指定數量的Pod副本在運行&#xff0c;他是RC的升級版&#xff0c;支持更靈活的Pod選擇器&#xff08;基于集合的標簽選擇&#xff09; 主…

P7453 [THUSC 2017] 大魔法師 Solution

Description 給定序列 a ( a 1 , a 2 , ? , a n ) a(a_1,a_2,\cdots,a_n) a(a1?,a2?,?,an?)&#xff0c; b ( b 1 , b 2 , ? , b n ) b(b_1,b_2,\cdots,b_n) b(b1?,b2?,?,bn?) 和 c ( c 1 , c 2 , ? , c n ) c(c_1,c_2,\cdots,c_n) c(c1?,c2?,?,cn?)&…

免費送源碼:Java+ssm+MySQL SpringBoot社區配送服務系統小程序 計算機畢業設計原創定制

摘要 隨著科學技術的飛速發展&#xff0c;社會的方方面面、各行各業都在努力與現代的先進技術接軌&#xff0c;通過科技手段來提高自身的優勢&#xff0c;社區當然也不例外。社區配送服務系統小程序是以實際運用為開發背景&#xff0c;運用軟件工程原理和開發方法&#xff0c;…

SQL語句(一)—— DDL

目錄 一、SQL 基礎知識 &#xff08;一&#xff09;SQL 通用語法 &#xff08;二&#xff09;SQL 分類 二、DDL —— 數據庫操作 1、查詢所有數據庫 2、查詢當前數據庫 3、創建數據庫 4、刪除數據庫 5、切換數據庫 三、DDL —— 表操作 &#xff08;一&#xff09;查…

【Android】界面布局-線性布局LinearLayout-例子

線性布局&#xff08;LinearLayout&#xff09;是一種重要的界面布局中&#xff0c;也是經常使用到的一種界面布局 ? 在線性布局中&#xff0c;所有的子元素都按照垂直或水平的順序在界面上排列 ?如果垂直排列&#xff0c;則每行僅包含一個界面元素 ?如果水平排列&…

leetcode數組-長度最小的子數組

題目 題目鏈接&#xff1a;https://leetcode.cn/problems/minimum-size-subarray-sum/ 給定一個含有 n個正整數的數組和一個正整數 target** 。** 找出該數組中滿足其總和大于等于target的長度最小的 子數組 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其長度**…

一周學會Pandas2 Python數據處理與分析-Jupyter Notebook安裝

鋒哥原創的Pandas2 Python數據處理與分析 視頻教程&#xff1a; 2025版 Pandas2 Python數據處理與分析 視頻教程(無廢話版) 玩命更新中~_嗶哩嗶哩_bilibili Jupyter (Project Jupyter | Home&#xff09;項目是一個非營利性開源項目&#xff0c;于2014年由IPython項目中誕生…

前端頁面鼠標移動監控(鼠標運動、鼠標監控)鼠標節流處理、throttle、限制觸發頻率(setTimeout、clearInterval)

文章目錄 使用lodashjs庫手動實現節流&#xff08;通過判斷之前設定的定時器setTimeout是否存在&#xff09; 使用lodashjs庫 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Com…

java流程控制04:if選擇結構

選擇結構 if單選擇結構 if雙選擇結構 if多選擇結構 嵌套的if結構 switch多選擇結構 if單選擇結構 我們很多時候需要去判斷一個東西是否可行&#xff0c;然后我們才去執行&#xff0c;這樣一個過程在程序中用if語句來表示 語法&#xff1a; if(布爾表達式){//如果布爾表達…