從零開始實現 C++ TinyWebServer 處理請求 HttpRequest類詳解

文章目錄

  • HTTP 請求報文
  • HttpRequest 類
  • 實現 Init() 函數
  • 實現 ParseRequestLine() 函數
  • 實現 ParseHeader() 函數
  • 實現 ParsePath() 函數
  • 實現 ParseBody() 函數
  • 實現 ParsePost() 函數
  • 實現 ParseFromUrlEncoded() 函數
  • 實現 UserVerify() 函數
  • 實現 Parse() 函數
  • HttpRequest 代碼
  • HttpRequest 測試

從零開始實現 C++ TinyWebServer 項目總覽
項目源碼

HTTP 請求報文

 2025-02-27 200519.png

 2025-02-27 200654.png

一個典型的 HTTP 請求包含請求行、請求頭和請求體(可選)。

  • 請求行:包含請求方法(如 GETPOST 等)、請求路徑(如 /index.html)和 HTTP 版本(如 HTTP/1.1)。
  • 請求頭:包含多個鍵值對,用于傳遞額外的信息,如 HostUser-Agent 等。
  • 請求體:通常在 POST 請求中使用,用于傳遞數據。

HttpRequest 類

在 Web 服務器中,HttpRequest類起著至關重要的作用,主要負責處理客戶端發送的 HTTP 請求。具體來說,它承擔了以下幾個關鍵任務:

  1. 請求解析:接收客戶端發送的 HTTP 請求報文,并對其進行解析,將請求報文中的各個部分(如請求行、請求頭、請求體等)提取出來,以便后續處理。
  2. 狀態管理:在解析過程中,通過狀態機來管理解析的狀態,確保解析過程的正確性和順序性。
  3. 請求處理:根據解析結果,進行相應的處理,例如處理 GET 請求、POST 請求等,并根據請求的內容和服務器的配置,決定如何響應客戶端。
class HttpRequest {
public:enum PARSE_STATE {REQUEST_LINE,HEADERS,BODY,FINISH};HttpRequest() { Init(); }~HttpRequest() = default;void Init();bool Parse(Buffer& buff);std::string Method() const;std::string Path() const;std::string& Path();std::string Version() const;std::string GetPost(const std::string& key) const;std::string GetPost(const char* key) const;bool IsKeepAlive() const;private:static int ConverHex(char ch);	// 16進制轉10進制static bool UserVerify(const std::string& name, const std::string& pwd, bool is_login);	// 驗證用戶bool ParseRequestLine(const std::string& line);	// 解析請求行void ParseHeader(const std::string& line);		// 解析請求頭void ParseBody(const std::string& line);		// 解析請求體void ParsePath();	// 解析請求路徑void ParsePost();	// 解析Post事件void ParseFromUrlEncoded();	// 解析urlstatic const std::unordered_set<std::string> DEFAULT_HTML;static const std::unordered_map<std::string, int> DEFAULT_HTML_TAG;PARSE_STATE state_;std::string method_;std::string path_;std::string version_;std::string body_;std::unordered_map<std::string, std::string> header_; // HTTP 請求的頭部信息std::unordered_map<std::string, std::string> post_;   // POST 請求的數據
};

實現 Init() 函數

void HttpRequest::Init() {state_ = REQUEST_LINE;method_ = path_ = version_ = body_ = "";header_.clear();post_.clear();
}

實現 ParseRequestLine() 函數

  • 使用正則表達式^([^ ]*) ([^ ]*) HTTP/([^ ]*)$以及std::regex_match來匹配請求行。
  • 若匹配成功,將匹配結果分別存儲到method_path_version_中,并將解析狀態設置為HEADERS,表示接下來要解析請求頭,返回true
bool HttpRequest::ParseRequestLine(const std::string& line) {// GET /index.html HTTP/1.1std::regex patten("^([^ ]*) ([^ ]*) HTTP/([^ ]*)$");std::smatch match;// 匹配指定字符串整體是否符合if (std::regex_match(line, match, patten)) { method_ = match[1];path_ = match[2];version_ = match[3];state_ = HEADERS;return true;} LOG_ERROR("RequestLine Error"); return false;
}

實現 ParseHeader() 函數

  • 如果路徑為/,則將其修改為/index.html
  • 否則,檢查路徑是否在DEFAULT_HTML中,如果存在則在路徑后面添加.html后綴。
void HttpRequest::ParseHeader(const std::string& line) {std::regex patten("^([^:]*): ?(.*)$");std::smatch match;if (std::regex_match(line, match, patten)) {header_[match[1]] = match[2];} else {state_ = BODY;}
}

實現 ParsePath() 函數

  • 如果路徑為/,則將其修改為/index.html
  • 否則,檢查路徑是否在DEFAULT_HTML中,如果存在則在路徑后面添加.html后綴
void HttpRequest::ParsePath() {if (path_ == "/") {path_ = "/index.html";} else {if (DEFAULT_HTML.find(path_) != DEFAULT_HTML.end())path_ += ".html";}
}

實現 ParseBody() 函數

  • 將請求體內容存儲到body_中。
  • 調用ParsePost函數處理 POST 請求數據。
  • 將解析狀態設置為FINISH,表示解析完成。
void HttpRequest::ParseBody(const std::string& line) {body_ = line;ParsePost();    state_ = FINISH;LOG_DEBUG("Body: %s, len: %d", line.c_str(), line.size());
}

實現 ParsePost() 函數

  • 檢查請求方法是否為POST,并且請求頭中的Content-Type是否為application/x-www-form-urlencoded
  • 如果滿足條件,調用ParseFromUrlencoded函數解析 URL 編碼的請求體。
  • 檢查路徑是否為登錄或注冊相關路徑,如果是則調用UserVerify函數,進行用戶驗證,根據驗證結果設置相應的跳轉路徑。
void HttpRequest::ParsePost() {if (method_ == "POST" && header_["Content-Type"] == "application/x-www-form-urlencoded") {ParseFromUrlEncoded();if (DEFAULT_HTML_TAG.count(path_)) { // 登錄/注冊int tag = DEFAULT_HTML_TAG.find(path_)->second;LOG_DEBUG("Tag: %d", tag);if (tag == 0 || tag == 1) {bool is_login = (tag == 1);if (UserVerify(post_["username"], post_["password"], is_login))path_ = "/welcome.html";elsepath_ = "/error.html";}}}
}

實現 ParseFromUrlEncoded() 函數

  • 首先檢查請求體是否為空,如果為空則直接返回。
  • 遍歷請求體字符串,根據不同的字符進行不同的處理:
    • 遇到=,將之前的字符作為鍵存儲到key中,并更新起始位置j
    • 遇到&,將之前的字符作為值存儲到value中,并將鍵值對存儲到post_容器中,同時更新起始位置j
    • 遇到+,將其替換為空格。
    • 遇到%,將其后的兩個十六進制字符轉換為十進制整數,并更新請求體內容。
  • 最后處理剩余的鍵值對。
void HttpRequest::ParseFromUrlEncoded() {if (body_.size() == 0)return;std::string key, value;int num = 0;int n = body_.size();int i = 0, j = 0;while (i < n) {char ch = body_[i];// username=john%20doe&password=123456switch (ch) {case '=': // 獲取鍵值對key = body_.substr(j, i - j); j = i + 1;break;case '&': // 獲取鍵值對value = body_.substr(j, i - j);j = i + 1;post_[key] = value;LOG_DEBUG("%s = %s", key.c_str(), value.c_str());break;case '+': // 替換為空格body_[i] = ' ';break;case '%':num = ConverHex(body_[i + 1]) * 16 + ConverHex(body_[i + 2]);body_[i + 2] = num % 10 + '0';body_[i + 1] = num / 10 + '0';i += 2;break;default:break;}i++;}assert(j <= i);if (post_.count(key) == 0 && j < i) {value = body_.substr(j, i - j);post_[key] = value;}
}

實現 UserVerify() 函數

  • 首先檢查用戶名和密碼是否為空,如果為空則返回false
  • 使用SqlConnRAII管理數據庫連接,構造查詢語句,查詢數據庫中是否存在該用戶名。
  • 如果是登錄行為,比較輸入的密碼和數據庫中的密碼是否一致,一致則驗證成功。
  • 如果是注冊行為,檢查用戶名是否已被使用,若未被使用則將用戶信息插入數據庫。
// 驗證用戶的用戶名和密碼
bool HttpRequest::UserVerify(const std::string& name, const std::string& pwd, bool is_login) {if (name == "" || pwd == "")return false;LOG_INFO("Verify name: %s pwd: %s", name.c_str(), pwd.c_str());MYSQL* sql;SqlConnectRAII(&sql, SqlConnectPool::GetInstance());assert(sql);bool flag = false;char order[256] = {0};MYSQL_RES* res = nullptr;      // 查詢結果集if (!is_login) // 注冊flag = true;snprintf(order, 256, "SELECT username, password FROM User WHERE username='%s' LIMIT 1", name.c_str());LOG_DEBUG("%s", order);// 查詢用戶信息if (mysql_query(sql, order)) {if (res)mysql_free_result(res); // 查詢失敗,釋放結果集return false;}res = mysql_store_result(sql);   // 存儲查詢結果到res中// 處理查詢結果while (MYSQL_ROW row = mysql_fetch_row(res)) {LOG_DEBUG("MYSQL ROW: %s %s", row[0], row[1]);std::string password = row[1];if (is_login) {if (pwd == password) {flag = true;} else {flag = false;LOG_INFO("pwd error!");}} else {flag = false;LOG_INFO("user used!");}}mysql_free_result(res);// 注冊(用戶名未被使用)if (!is_login && flag) {LOG_DEBUG("register");bzero(order, 256);snprintf(order, 256, "INSERT INTO User(username, password) VALUES('%s', '%s')", name.c_str(), pwd.c_str());LOG_DEBUG("%s", order);if (mysql_query(sql, order)) {LOG_ERROR("MySQL insert fail: Error: %s", mysql_error(sql));flag = false;} else {LOG_DEBUG("UserVerify success!");flag = true;}}return flag;
}

實現 Parse() 函數

  • 首先檢查Buffer中是否有可讀數據,如果沒有則返回false

  • 循環讀取Buffer中的數據,直到沒有可讀數據或者解析狀態為FINISH

  • 利用search函數查找\r\n來確定每行數據的結束位置,將每行數據提取出來存為line

  • 根據當前解析狀態state_的不同,調用不同的解析函數進行處理:

    • 若為REQUEST_LINE狀態,調用ParseRequestLine_解析請求行,若解析失敗則返回false,成功則繼續解析路徑。
    • 若為HEADERS狀態,調用ParseHeader_解析請求頭,若Buffer中剩余可讀字節小于等于 2,則認為是 GET 請求,將狀態設置為FINISH
    • 若為BODY狀態,調用ParseBody_解析請求體。
  • 如果lineend到達Buffer的寫指針位置,說明數據讀完,調用buff.RetrieveAll()清空Buffer,并跳出循環;否則跳過\r\n繼續解析。

  • 最后記錄解析出的請求方法、路徑和版本信息,并返回true表示解析成功。

bool HttpRequest::Parse(Buffer& buff) {const char* END = "\r\n";if (buff.ReadableBytes() == 0)return false;while (buff.ReadableBytes() && state_ != FINISH) {// 找到buff中,首次出現"\r\n"的位置const char* line_end = std::search(buff.ReadBegin(), buff.WriteBeginConst(), END, END + 2);string line(buff.ReadBegin(), line_end);switch (state_) {case REQUEST_LINE:if (ParseRequestLine(line) == false)return false;ParsePath();break;case HEADERS:ParseHeader(line);if (buff.ReadableBytes() <= 2) // get請求,提前結束state_ = FINISH;break;case BODY:ParseBody(line);break;default:break;}if (line_end == buff.WriteBegin()) { // 讀完了buff.RetrieveAll();break;}buff.RetrieveUntil(line_end + 2); // 跳過回車換行}LOG_DEBUG("[%s] [%s] [%s]", method_ .c_str(), path_.c_str(), version_.c_str());return true;
}

HttpRequest 代碼

http_request.h

#ifndef HTTP_REQUEST_H
#define HTTP_REQUEST_H#include <cstring>
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <algorithm>
#include <regex> // 正則表達式
#include <mysql/mysql.h>#include "../buffer/buffer.h"
#include "../log/log.h"
#include "../pool/sql_connect_pool.h"class HttpRequest {
public:enum PARSE_STATE {REQUEST_LINE,HEADERS,BODY,FINISH};HttpRequest() { Init(); }~HttpRequest() = default;void Init();bool Parse(Buffer& buff);std::string Method() const;std::string Path() const;std::string& Path();std::string Version() const;std::string GetPost(const std::string& key) const;std::string GetPost(const char* key) const;bool IsKeepAlive() const;private:static int ConverHex(char ch);static bool UserVerify(const std::string& name, const std::string& pwd, bool is_login);bool ParseRequestLine(const std::string& line);void ParseHeader(const std::string& line);void ParseBody(const std::string& line);void ParsePath();void ParsePost();void ParseFromUrlEncoded();static const std::unordered_set<std::string> DEFAULT_HTML;static const std::unordered_map<std::string, int> DEFAULT_HTML_TAG;PARSE_STATE state_;std::string method_;std::string path_;std::string version_;std::string body_;std::unordered_map<std::string, std::string> header_; // HTTP 請求的頭部信息std::unordered_map<std::string, std::string> post_;   // POST 請求的數據
};#endif // HTTP_REQUEST_H

http_request.cc

#include "http_request.h"const std::unordered_set<std::string> HttpRequest::DEFAULT_HTML {"/index", "/register", "/login", "/welcome", "/video", "/picture"
};// 登錄/注冊
const std::unordered_map<std::string, int> HttpRequest::DEFAULT_HTML_TAG {{"/login.html", 1}, {"/register.html", 0}
};void HttpRequest::Init() {state_ = REQUEST_LINE;method_ = path_ = version_ = body_ = "";header_.clear();post_.clear();
}bool HttpRequest::ParseRequestLine(const std::string& line) {// GET /index.html HTTP/1.1std::regex patten("^([^ ]*) ([^ ]*) HTTP/([^ ]*)$");std::smatch match;if (std::regex_match(line, match, patten)) {method_ = match[1];path_ = match[2];version_ = match[3];state_ = HEADERS;return true;} LOG_ERROR("RequestLine Error"); return false;
}void HttpRequest::ParseHeader(const std::string& line) {std::regex patten("^([^:]*): ?(.*)$");std::smatch match;if (std::regex_match(line, match, patten)) {header_[match[1]] = match[2];} else {state_ = BODY;}
}void HttpRequest::ParseBody(const std::string& line) {body_ = line;ParsePost();    state_ = FINISH;LOG_DEBUG("Body: %s, len: %d", line.c_str(), line.size());
}void HttpRequest::ParsePost() {if (method_ == "POST" && header_["Content-Type"] == "application/x-www-form-urlencoded") {ParseFromUrlEncoded();if (DEFAULT_HTML_TAG.count(path_)) { // 登錄/注冊int tag = DEFAULT_HTML_TAG.find(path_)->second;LOG_DEBUG("Tag: %d", tag);if (tag == 0 || tag == 1) {bool is_login = (tag == 1);if (UserVerify(post_["username"], post_["password"], is_login))path_ = "/welcome.html";elsepath_ = "/error.html";}}}
}// 16進制轉10進制
int HttpRequest::ConverHex(char ch) {if (ch >= 'A' && ch <= 'F')return ch - 'A' + 10;if (ch >= 'a' && ch <= 'f')return ch - 'a' + 10;return ch;
}void HttpRequest::ParseFromUrlEncoded() {if (body_.size() == 0)return;std::string key, value;int num = 0;int n = body_.size();int i = 0, j = 0;while (i < n) {char ch = body_[i];// username=john%20doe&password=123456switch (ch) {case '=': // 獲取鍵值對key = body_.substr(j, i - j); j = i + 1;break;case '&': // 獲取鍵值對value = body_.substr(j, i - j);j = i + 1;post_[key] = value;LOG_DEBUG("%s = %s", key.c_str(), value.c_str());break;case '+': // 替換為空格body_[i] = ' ';break;case '%':num = ConverHex(body_[i + 1]) * 16 + ConverHex(body_[i + 2]);body_[i + 2] = num % 10 + '0';body_[i + 1] = num / 10 + '0';i += 2;break;default:break;}i++;}assert(j <= i);if (post_.count(key) == 0 && j < i) {value = body_.substr(j, i - j);post_[key] = value;}
}void HttpRequest::ParsePath() {if (path_ == "/") {path_ = "/index.html";} else {if (DEFAULT_HTML.find(path_) != DEFAULT_HTML.end())path_ += ".html";}
}// 驗證用戶的用戶名和密碼
bool HttpRequest::UserVerify(const std::string& name, const std::string& pwd, bool is_login) {if (name == "" || pwd == "")return false;LOG_INFO("Verify name: %s pwd: %s", name.c_str(), pwd.c_str());MYSQL* sql;SqlConnectRAII(&sql, SqlConnectPool::GetInstance());assert(sql);bool flag = false;char order[256] = {0};MYSQL_RES* res = nullptr;      // 查詢結果集if (!is_login) // 注冊flag = true;snprintf(order, 256, "SELECT username, password FROM User WHERE username='%s' LIMIT 1", name.c_str());LOG_DEBUG("%s", order);// 查詢用戶信息if (mysql_query(sql, order)) {if (res)mysql_free_result(res); // 查詢失敗,釋放結果集return false;}res = mysql_store_result(sql);   // 存儲查詢結果到res中// 處理查詢結果while (MYSQL_ROW row = mysql_fetch_row(res)) {LOG_DEBUG("MYSQL ROW: %s %s", row[0], row[1]);std::string password = row[1];if (is_login) {if (pwd == password) {flag = true;} else {flag = false;LOG_INFO("pwd error!");}} else {flag = false;LOG_INFO("user used!");}}mysql_free_result(res);// 注冊(用戶名未被使用)if (!is_login && flag) {LOG_DEBUG("register");bzero(order, 256);snprintf(order, 256, "INSERT INTO User(username, password) VALUES('%s', '%s')", name.c_str(), pwd.c_str());LOG_DEBUG("%s", order);if (mysql_query(sql, order)) {LOG_ERROR("MySQL insert fail: Error: %s", mysql_error(sql));flag = false;} else {LOG_DEBUG("UserVerify success!");flag = true;}}return flag;
}bool HttpRequest::Parse(Buffer& buff) {const char* END = "\r\n";if (buff.ReadableBytes() == 0)return false;while (buff.ReadableBytes() && state_ != FINISH) {// 找到buff中,首次出現"\r\n"的位置const char* line_end = std::search(buff.ReadBegin(), buff.WriteBeginConst(), END, END + 2);string line(buff.ReadBegin(), line_end);switch (state_) {case REQUEST_LINE:if (ParseRequestLine(line) == false)return false;ParsePath();break;case HEADERS:ParseHeader(line);if (buff.ReadableBytes() <= 2) // get請求,提前結束state_ = FINISH;break;case BODY:ParseBody(line);break;default:break;}if (line_end == buff.WriteBegin()) { // 讀完了buff.RetrieveAll();break;}buff.RetrieveUntil(line_end + 2); // 跳過回車換行}LOG_DEBUG("[%s] [%s] [%s]", method_ .c_str(), path_.c_str(), version_.c_str());return true;
}std::string HttpRequest::Method() const {return method_;
}std::string HttpRequest::Path() const {return path_;
}std::string& HttpRequest::Path() {return path_;
}std::string HttpRequest::Version() const {return version_;
}std::string HttpRequest::GetPost(const std::string& key) const {if (post_.count(key) == 1)// return post_[key]; post_有const屬性,所以不能用[]return post_.find(key)->second;return "";
}std::string HttpRequest::GetPost(const char* key) const {assert(key != nullptr);if (post_.count(key) == 1)return post_.find(key)->second;return "";
}bool HttpRequest::IsKeepAlive() const {if (header_.count("Connect") == 1) return header_.find("Connect")->second == "keep-alive" && version_ == "1.1";return false;
}

HttpRequest 測試

測試HttpRequest的,解析,注冊,登錄功能

#include "../code/http/http_request.h"
#include <iostream>void TestHttpRequest() {// 初始化日志系統Log* logger = Log::GetInstance();logger->Init(0, "./logs/", ".log", 1024);// 初始化測試HttpRequest request;// 初始化數據庫連接池SqlConnectPool* conn_pool = SqlConnectPool::GetInstance();conn_pool->Init("localhost", 3306, "Tian", "123456", "web_server", 10);// 解析請求測試std::string http_request = "GET /index.html HTTP/1.1\r\n""Host: example.com\r\n""Connection: keep-alive\r\n""\r\n";Buffer buff;buff.Append(http_request);bool parseResult = request.Parse(buff);assert(parseResult);// 訪問器方法測試assert(request.Method() == "GET");assert(request.Path() == "/index.html");assert(request.Version() == "1.1");// 模擬注冊 POST 請求std::string register_request = "POST /register.html HTTP/1.1\r\n""Host: example.com\r\n""Content-Type: application/x-www-form-urlencoded\r\n""Content-Length: 23\r\n""\r\n""username=test&password=123456";request.Init();buff.Append(register_request);parseResult = request.Parse(buff);assert(parseResult);assert(request.Method() == "POST");assert(request.Path() == "/welcome.html");assert(request.Version() == "1.1");assert(request.GetPost("username") == "test");assert(request.GetPost("password") == "123456");// Post 數據獲取測試// 模擬登錄 POST 請求std::string login_request = "POST /login HTTP/1.1\r\n""Content-Type: application/x-www-form-urlencoded\r\n""Content-Length: 19\r\n""\r\n""username=test&password=123456""\r\n";buff.Append(login_request);request.Init();parseResult = request.Parse(buff);assert(parseResult);assert(request.Method() == "POST");assert(request.Path() == "/welcome.html");assert(request.Version() == "1.1");assert(request.GetPost("username") == "test");assert(request.GetPost("password") == "123456");std::cout << "All tests passed!" << std::endl;
}int main() {TestHttpRequest();return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(tests)# 設置 C++ 標準和編譯器選項
set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")# 定義公共源文件和特定文件
set(COMMON_SOURCES ../code/buffer/buffer.cc ../code/log/log.cc)
set(HTTP_SOURCE ../code/http/http_request.cc)
set(POOL_SOURCE ../code/pool/sql_connect_pool.cc)# 查找 MySQL 庫
find_package(PkgConfig REQUIRED)
pkg_check_modules(MYSQL REQUIRED mysqlclient)
# 包含 MySQL 頭文件目錄
include_directories(${MYSQL_INCLUDE_DIR})# 添加可執行文件
add_executable(http_request_test http_request_test.cc ${COMMON_SOURCES} ${HTTP_SOURCE} ${POOL_SOURCE})
# 鏈接庫
target_link_libraries(http_request_test ${MYSQL_LIBRARIES})

return 0;
}


**CMakeLists.txt**```cmake
cmake_minimum_required(VERSION 3.10)
project(tests)# 設置 C++ 標準和編譯器選項
set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")# 定義公共源文件和特定文件
set(COMMON_SOURCES ../code/buffer/buffer.cc ../code/log/log.cc)
set(HTTP_SOURCE ../code/http/http_request.cc)
set(POOL_SOURCE ../code/pool/sql_connect_pool.cc)# 查找 MySQL 庫
find_package(PkgConfig REQUIRED)
pkg_check_modules(MYSQL REQUIRED mysqlclient)
# 包含 MySQL 頭文件目錄
include_directories(${MYSQL_INCLUDE_DIR})# 添加可執行文件
add_executable(http_request_test http_request_test.cc ${COMMON_SOURCES} ${HTTP_SOURCE} ${POOL_SOURCE})
# 鏈接庫
target_link_libraries(http_request_test ${MYSQL_LIBRARIES})

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

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

相關文章

systemd-networkd 的 *.network 配置文件詳解 筆記250323

systemd-networkd 的 *.network 配置文件詳解 筆記250323 查看官方文檔可以用 man systemd.network命令, 或訪問: https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html 名稱 systemd.network — 網絡配置 概要 network.network 描述 一個純…

自定義mavlink 生成wireshark wlua插件錯誤(已解決)

進入正題 python3 -m pymavlink.tools.mavgen --langWLua --wire-protocol2.0 --outputoutput/develop message_definitions/v1.0/development.xml 編譯WLUA的時候遇到一些問題 1.ERROR:SCHEMASV:SCHEMAV_CVC_ENUMERATION_VALID 3765:0:ERROR:SCHEMASV:SCHEMAV_CVC_ENUMERAT…

計算機操作系統(四) 操作系統的結構與系統調用

計算機操作系統&#xff08;四&#xff09; 操作系統的結構與系統調用 前言一、操作系統的結構1.1 簡單結構1.2 模塊化結構1.3 分層化結構1.4 微內核結構1.5 外核結構 二、系統調用1.1 系統調用的基本概念1.2 系統調用的類型 總結&#xff08;核心概念速記&#xff09;&#xf…

深入解析 Spring IOC AOP:原理、源碼與實戰

深入解析 Spring IOC & AOP&#xff1a;原理、源碼與實戰 Spring 框架的核心在于 IOC&#xff08;控制反轉&#xff09; 和 AOP&#xff08;面向切面編程&#xff09;。今天&#xff0c;我們將深入剖析它們的原理&#xff0c;結合源碼解析&#xff0c;并通過 Java 代碼實戰…

LLM之RAG理論(十四)| RAG 最佳實踐

RAG 的過程很復雜&#xff0c;包含許多組成部分。我們如何確定現有的 RAG 方法及其最佳組合&#xff0c;以確定最佳 RAG 實踐&#xff1f; 論文 《Searching for Best Practices in Retrieval-Augmented Generation》給出了回答。 本文將從以下三方面進行介紹&#xff1a; 首先…

利用knn算法實現手寫數字分類

利用knn算法實現手寫數字分類 1.作者介紹2.KNN算法2.1KNN&#xff08;K-Nearest Neighbors&#xff09;算法核心思想2.2KNN算法的工作流程2.3優缺點2.4 KNN算法圖示介紹 3.實驗過程3.1安裝所需庫3.2 MNIST數據集3.3 導入手寫數字圖像進行分類3.4 完整代碼3.5 實驗結果 1.作者介…

C語言-適配器模式詳解與實踐

文章目錄 C語言適配器模式詳解與實踐1. 什么是適配器模式&#xff1f;2. 為什么需要適配器模式&#xff1f;3. 實際應用場景4. 代碼實現4.1 UML 關系圖4.2 頭文件 (sensor_adapter.h)4.3 實現文件 (sensor_adapter.c)4.4 使用示例 (main.c) 5. 代碼分析5.1 關鍵設計點5.2 實現特…

Rust函數、條件語句、循環

文章目錄 函數**語句與表達式**條件語句循環 函數 Rust的函數基本形式是這樣的 fn a_func(a: i32) -> i32 {}函數名是蛇形風格&#xff0c;rust不在意函數的聲明順序&#xff0c;只需要有聲明即可 函數參數必須聲明參數名稱和類型 語句與表達式 這是rust非常重要的基礎…

maptalks圖層交互 - 模擬 Tooltip

maptalks圖層交互 - 模擬 Tooltip 圖層交互-模擬tooltip官方文檔 <!DOCTYPE html> <html><meta charsetUTF-8 /><meta nameviewport contentwidthdevice-width, initial-scale1 /><title>圖層交互 - 模擬 Tooltip</title><style typet…

好吧好吧,看一下達夢的模式與用戶的關系

單憑個人感覺&#xff0c;模式在達夢中屬于邏輯對象合集&#xff0c;回頭再看資料 應該是一個用戶可以對應多個模式 問題來了&#xff0c;模式的ID和用戶的ID一樣嗎&#xff1f; 不一樣 SELECT USER_ID,USERNAME FROM DBA_USERS WHERE USERNAMETEST1; SELECT ID AS SCHID, NA…

python socket模塊學習記錄

python黑馬程序員 通過python內置socket模塊&#xff0c;在電腦本地開發一個服務器&#xff0c;一個客戶端&#xff0c;連接后進行連續的聊天。服務器和客戶端均可輸入exit&#xff0c;主動退出連接。 服務器開發.py import socket# 創建Socket對象 socket_server socket.s…

7-2 sdut-C語言實驗-逆序建立鏈表

7-2 sdut-C語言實驗-逆序建立鏈表 分數 20 全屏瀏覽 切換布局 作者 馬新娟 單位 山東理工大學 輸入整數個數N&#xff0c;再輸入N個整數&#xff0c;按照這些整數輸入的相反順序建立單鏈表&#xff0c;并依次遍歷輸出單鏈表的數據。 輸入格式: 第一行輸入整數N;&#xff…

針對永磁電機(PMM)的d軸和q軸電流,考慮交叉耦合補償,設計P1控制器并推導出相應的傳遞函數

電流控制回路:針對永磁電機(PMM)的d軸和q軸電流&#xff0c;考慮交叉耦合補償&#xff0c;設計P1控制器并推導出相應的傳遞函數。 1. 永磁電機&#xff08;PMM&#xff09;的數學模型 在同步旋轉坐標系&#xff08; d ? q d - q d?q 坐標系&#xff09;下&#xff0c;永磁同…

ROS多機通信(四)——Ubuntu 網卡 Mesh 模式配置指南

引言 使用Ad-hoc加路由協議和直接Mesh模式配置網卡實現的網絡結構是一樣的&#xff0c;主要是看應用選擇&#xff0c; Ad-Hoc模式 B.A.T.M.A.N. / OLSR 優點&#xff1a;靈活性高&#xff0c;適合移動性強或需要優化的復雜網絡。 缺點&#xff1a;配置復雜&#xff0c;需手動…

chap1:統計學習方法概論

第1章 統計學習方法概論 文章目錄 第1章 統計學習方法概論前言章節目錄導讀 實現統計學習方法的步驟統計學習分類基本分類監督學習無監督學習強化學習 按模型分類概率模型與非概率模型 按算法分類按技巧分類貝葉斯學習核方法 統計學習方法三要素模型模型是什么? 策略損失函數與…

爬蟲案例-爬取某站視頻

文章目錄 1、下載FFmpeg2、爬取代碼3、效果圖 1、下載FFmpeg FFmpeg是一套可以用來記錄、轉換數字音頻、視頻&#xff0c;并能將其轉化為流的開源計算機程序。 點擊下載: ffmpeg 安裝并配置 FFmpeg 步驟&#xff1a; 1.下載 FFmpeg&#xff1a; 2.訪問 FFmpeg 官網。 3.選擇 Wi…

車載以太網網絡測試-22【傳輸層-DOIP協議-5】

目錄 1 摘要2 DoIP時間參數2.1 ISO 13400定義的時間參數2.2 參數示例 3 DoIP節點內部狀態機4 UDSonIP概述5 總結 1 摘要 本文繼續對DOIP協議進行介紹&#xff0c;主要是DOIP相關的時間參數、時間參數定義以及流程示例。推薦大家對上文專題進行回顧&#xff0c;有利于系統性學習…

(論文總結)思維鏈激發LLM推理能力

研究背景&動機 背景:擴大模型規模已被證實具有提升模型性能和模型效率的功效&#xff0c;但是LLM對于完成推理、算術任務仍有較大不足。 動機:從之前的應用和研究中得知&#xff0c;可以用生成自然語言解釋、使用神經符號等形式語言的方法來提高大模型的算術推理能力&…

前后端開發概述:架構、技術棧與未來趨勢

一、前后端開發的基本概念 1.1 什么是前后端開發&#xff1f; 前后端開發是 Web 開發的兩個核心部分&#xff0c;各自承擔不同的職責&#xff1a; 前端&#xff08;Frontend&#xff09; 負責網頁的用戶界面&#xff08;UI&#xff09;和用戶體驗&#xff08;UX&#xff09;…

anythingLLM結合searXNG實現聯網搜索

1、docker-compose 部署searXNG GitHub - searxng/searxng-docker: The docker-compose files for setting up a SearXNG instance with docker. cd /usr/local git clone https://github.com/searxng/searxng-docker.git cd searxng-docker 2、修改 .env文件 # By default…