Muduo網絡庫實現 [十六] - HttpServer模塊

目錄

設計思路

類的設計

模塊的實現

公有接口

私有接口

疑問點


設計思路

本模塊就是設計一個HttpServer模塊,提供便攜的搭建http協議的服務器的方法。那么這個模塊需要如何設計呢? 這還需要從Http請求說起。

首先從http請求的請求行開始分析,請求行里面有個方法。分為靜態資源請求功能性請求的。

靜態資源請求顧名思義就是用來獲取服務器中的某些路徑下的實體資源,比如文件的內容等,這一類請求中,url 中的資源路徑必須是服務器中的一個有效的存在的文件路徑。比如:

  • HTML/CSS/JavaScript文件
  • 圖片(JPG、PNG、GIF等)
  • 視頻和音頻文件
  • PDF、文檔等靜態文件
  • 字體文件

而如果提取出來的資源路徑并不是一個實體文件的路徑,那么他大概率是一個功能性請求,這時候就有用戶來決定如何處理這個請求了,也就是我們前面說過的 請求路徑 和 處理方法的路由表。

功能性請求如下

  • 用戶登錄/注冊
  • 商品搜索結果
  • 個人資料頁面
  • 訂單處理
  • API接口調用
  • 數據統計和報表生成

但是還有一種特殊的情況就是資源路徑是一個目錄,比如 / ,這時候有可能是一個訪問網站首頁的請求,所以我們需要判斷在這個路徑后面加上 index.html (也可以是其他的文件名,取決于你的網站的首頁的文件名) ,如果加上之后,路徑有效且存在實體文件,那么就是一個靜態資源請求,如果還是無效,那么就是一個功能性請求。

功能性請求如何處理呢?這是由使用或者說搭建服務器的人來決定的。 用戶未來想要提供某些功能,可以讓他和某個虛擬的目錄或者說特定的路徑綁定起來。 比如提供一個登錄功能,那么用戶可以規定 ?/login 這個路徑就代表登錄的功能,未來如果收到了一個請求資源路徑是 /login ,那么就不是請求實體資源,而是調用網站搭建者提供的登錄的方法進行驗證等操作。 一般來說這些虛擬路徑不會和實體資源路徑沖突。

同時,對于這種功能性請求對應的路徑,他并不是說一個路徑只能有一個功能,不同的請求方法,同一個路徑,最終執行的方法也可以是不同的,這具體還是要看使用者的設定。

所以為了維護這樣的功能性路徑和需要執行的方法之間的映射關系,我們需要為每一種請求方法都維護一張路由表,路由表中其實就是保存了路徑和所需要執行的方法之間的映射關系。

在我們這里,就只考慮常用的五種方法,get,post,delete,head,put,其他的暫時就不提供支持了

    //五張路由表using Handler = std::function<void(const HttpRequest&,HttpResponse*)>; using HandlerTable = std::unordered_map<std::string,Handler>;HandlerTable _get_route; HandlerTable _post_route; HandlerTable _head_route; HandlerTable _put_route; HandlerTable _delete_route;

這是交給用戶進行設置的,我們也會提供五個接口給用戶用來添加處理方法。

但是,這樣的表真的好嗎??

在實際的應用中,比如有以下的功能性請求的請求路徑 , /login1213 , /login12124 , /login1213626 , /login12152 , /login1295 , /login1275 ,對于這樣的一類路徑,他們其實需要執行的是同一個方法,而并不需要為每一個類似的路徑設置一個方法,而路徑后半部分的數字其實后續可以當成參數來用。

那么綜上所述,我們的路由表中作為 key 值的并不是 std::string ,而是只需要滿足某一種匹配要求的路徑,都可以執行某一方法,那么作為 key 值的其實是正則表達式。

    using HandlerTable = std::unordered_map<std::regex,Handler>;

但是如果我們編譯一下就會發現,正則表達式是不能作為哈希的 key 值的,或者說不匹配默認的哈希函數。?

我們可以思考一下,我們用正則表達式作為 key 了,那么后面不管使用何種數據結構來存儲正則表達式和操作方法的映射關系,我們都是要遍歷整個路由表的,需要遍歷表中的所有的正則表達式,然后拿著我們的路徑來進行正則匹配,匹配上了就說明這是我們要找的方法,如果匹配不上就說明不是,不管怎么樣,都是要進行遍歷,那么其實我們直接用數組來存儲也是一樣的。

所以最終我們使用 vector 來存儲用戶方法。

    using HandlerTable = std::vector<std::pair<std::regex,Handler>>;

而HttpServer模塊中除了五張路由表,還需要一個TcpServer對象,這是毋庸置疑的。 同時還需要保存一個網頁根目錄,這個根目錄是要交給用戶設置的,由使用者決定。

 
//支持Http協議的服務器
class HttpServer
{
private:TcpServer _server;std::string _base_path; //網頁根目錄//五張路由表using Handler = std::function<void(const HttpRequest&,HttpResponse*)>; using HandlerTable = std::vector<std::pair<std::regex,Handler>>;HandlerTable _get_route; HandlerTable _post_route; HandlerTable _head_route; HandlerTable _put_route; HandlerTable _delete_route; public:
};

類的設計

那么需要哪些接口呢?

首先需要提供給用戶的五個設置功能方法的接口,以及設置網頁根目錄和服務器線程數的接口。

還需要提供給用戶是否開啟超時釋放,以及啟動服務器的接口。

提供給用戶的接口就這么多,其實都很簡單,難的是私有的一些接口:

首先,未來拿到一個完整請求之后,我們需要能夠判斷這個請求是靜態資源請求還是功能性請求。

  • 如果是資源性請求我們需要怎么做?
  • 如果是功能性請求我們有需要怎么做?

最后還需要將相應組織成一個tcp報文進行回復。

同時還需要提供未來設置給TcpServer的連接建立和新數據到來的回調方法,這兩個方法是必需的,因為在連接建立時我們必須要設置上下文,在新數據到來時必須要有邏輯來決定怎么處理。

class HttpServer {
public://構造函數HttpServer();// 配置設置類方法void SetBaseDir();         // 設置靜態資源路徑的根目錄void SetThreadCount();      // 設置線程數量void Listen();  //開始監聽// 路由設置方法void Get();                 // 設置GET方法void Post();                // 設置Post方法void Put();                 // 設置Put方法void Delete();              // 設置Delete方法void Head();                // 設置Head方法private:// 連接和消息處理回調void OnConnected();         // 設置一個上下文void OnMessage();           // 把請求報文從應用層緩沖區提取出來// 請求處理和路由bool IsFileHandler();       // 判斷是否是靜態資源請求void Route();               // 通過請求類型分別進行處理void Dispatcher();          // 功能性請求處理void FileHandler();         // 靜態資源請求處理void ErrorHandler();        // 錯誤信息填充void WriteResponse(); //把響應對象轉換成響應報文并發送
};

模塊的實現

下面我將詳細的講解,這些模塊都是干嘛的,讓各位能有個更清晰的思路

公有接口

首先是設置靜態資源路徑根目錄,這個是為了開發者設定的,因為用戶在訪問的時候,很大概率是不會加上根目錄的,就好比你要在圖片網站上找一個蘋果的圖片,對于網站來說他這個蘋果肯定是分類在水果目錄下面的,你在輸入的時候大概率是直接apple.png,如果不進行設置默認根目錄的話,那么肯定是找不到的,當你設置了根目錄之后,你再輸入apple.png的時候,它就會默認的變成fruit/apple.png了。

而開發者在進行設置的情況下,可能會出現粗心,比如說這個目錄名字我少寫了一個字母,所以這個函數需要先去尋找是否存在這個目錄,要是不存在那就是開發者寫錯了,我要反饋給開發者,要是沒寫錯我就設置傳入的dir為根目錄

void SetBasedir(const string &dir)
{assert(Util::IsDirectory(dir) == true);_basedir = dir;
}

接著就是開發者設置下線程的數量了,直接就是傳入要設置的count就行

void SetThreadPoolCount(int count)
{_server.SetThreadCount(count);
}

然后就是開始監聽的接口了

    void Listen() // 開始監聽{_server.Start();}

接下來就是路由表的使用了,對不同的方法的路由表都設置相應的正則表達式模式字符串和對應的回調函數,當用戶輸入了對應的請求方法的時候就去對應的路由表里查找。至于如何定義,那就是開發者來自定義了

void Get(const string &pattern, const Handler &handler)
{_get_route.push_back({regex(pattern), handler});
}void Post(const string &pattern, const Handler &handler)
{_post_route.push_back({regex(pattern), handler});
}void Put(const string &pattern, const Handler &handler)
{_put_route.push_back({regex(pattern), handler});
}void Delete(const string &pattern, const Handler &handler)
{_delete_route.push_back({regex(pattern), handler});
}

最后就是構造函數的實現了,需要傳入一個端口號來對我們內部的TcpServer對象進行初始化,然后包括三個內容,啟動非活躍連接銷毀,設置當連接來到的時候的回調函數用來設置上下文,以及把消息從緩沖區獲取的回調函數

HttpServer(int port, int timeout = DEFAULT_TIMEOUT): _server(port)
{// 啟用非活躍連接釋放_server.EnableInactiveRelease(timeout);// 設置連接回調函數_server.SetConnectedCallback(bind(&HttpServer::OnConnected, this, placeholders::_1));// 設置消息回調函數_server.SetMessageCallback(bind(&HttpServer::OnMessage, this, placeholders::_1, placeholders::_2));
}

私有接口

這些私有接口是由開發者去調用的,來設置一些信息,讓使用者去更好的使用

首先是設置給新連接設置一個上下文,用于當連接被切換的時候保存其中的數據,當下次再切換回來的時候,能接著當前的數據繼續進行操作,所以需要的參數就是一個新連接的引用,因為連接是會被放在TCP的全連接隊列中的

    void OnConnected(const PtrConnection &conn) // 設置一個上下文{conn->SetContext(HtppContext());DBG_LOG("new connection %p", conn.get());}

接著是對于該新連接數據的提取,因為這些數據從TCP的接收緩沖區是先提取到用戶態中的緩沖區的,但是因為TCP是面向字節流的,也就是在用戶態的緩沖區存儲的數據都是以字節的形式,但是我Http要的是報頭,正文,請求行,這種類型的呀,所以肯定也是需要轉換的也就是用到了context的模塊,接著就是判斷請求的狀態碼,如果是狀態碼大于400了,就說明錯誤了,此時就需要進行處理了,可能你開發者想這個時候給用戶彈出一個錯誤網頁,所以接下來就調用ErrorHandler函數,然后把這個錯誤響應回去,再重置下HTTP上下文,準備處理新的請求,然后把緩沖區更新下也就是清空緩沖區,接著就是關閉連接了。如果是正常的狀態碼就返回了

    void OnMessage(const PtrConnection &conn, Buffer *buf)   // 處理從客戶端接收到的HTTP請求消息{while(buf->ReadAbleSize() > 0)         // 只要緩沖區中還有可讀數據就繼續處理{HttpContext *context = conn->GetContext()->get<HttpContext>();  // 從連接上下文中獲取HTTP上下文對象context->RecvHttpRequest(buf);      // 從緩沖區中解析HTTP請求數據HttpRequest &req = context->GetRequest();  // 獲取解析后的HTTP請求對象HttpResponse rsp;                   // 創建HTTP響應對象if(context->StatusCode() >=400)     // 檢查HTTP狀態碼,如果大于等于400表示出錯{ErrorHandler(req,&rsp);         // 調用錯誤處理函數生成錯誤響應WriteResponse(conn,req,rsp);    // 將錯誤響應寫回客戶端context->Reset();               // 重置HTTP上下文,準備處理新的請求buf->MoveReadIndex(buf->ReadAbleSize());  // 清空緩沖區中剩余的所有數據conn->ShutDown();               // 關閉連接}return;}}

可能會有同學問了,這里的context->Reset(); ? buf->MoveReadIndex(buf->ReadAbleSize());?的是不是可以丟棄了,因為后面直接就是shutdown了

可以是可以但是這樣是不規范的,釋放鏈接是釋放鏈接,在釋放鏈接之前,我們正常去處理錯誤邏輯,將各個環節該置空的置空,該清理的清理,這是我們必須在代碼中進行體現的,這才是一個好的代碼習慣。并且來說,conn->shutdow還不是實際發送的邏輯,在后續還會進行一些判斷的,所以上層該做的工作還是要做的


這個操作之后,我們也就解析了二進制的數據了,然后判斷請求行的方法是什么類型的,是不是靜態資源的請求。需要四步:首先需要判斷有沒有設置資源根目錄。靜態資源請求的方法必須是 GET 或者 HEAD ,因為其他的方法不是用來獲取資源的。然后靜態資源請求的路徑必須是一個合法的路徑。最后就需要判斷請求的路徑的資源是否存在。但是我們需要考慮路徑是目錄的時候,給它加上一個 index.html。最后就是判斷文件是否存在

bool IsFileHandler(const HttpRequest &req)
{// 1.查看設置了靜態資源根目錄if(_basedir.empty())return false;// 2.請求方法必須是GET或HEADif(req._method != "GET" && req._method != "HEAD")return false;// 3.請求資源路徑必須合法if(Util::ValidPath(req._path) == false)return false;// 4.請求資源必須存在,且是一個普通文件// 為防止修改路徑,先復制一份string req_path = _basedir + req._path;// 先處理特殊情況:如果請求的是目錄,就換成請求首頁if(req._path.back() == '/')req_path += "index.html";// 如果不是普通文件就錯誤if(Util::IsRegular(req_path) == false)return false;return true;
}

接下來我們舉二個例子來更好的幫我們捋一捋思路?

例子1: 正常的圖片請求

  • 用戶訪問 http://example.com/images/logo.png
  • 瀏覽器發送 GET 請求,路徑為 /images/logo.png
  • 函數檢查:
    • 靜態根目錄已設置為 /var/www/html/
    • 請求方法是 GET ?
    • 路徑 /images/logo.png 是合法的 ?
    • 文件 /var/www/html/images/logo.png 存在且是普通文件 ?
  • 函數返回 true,服務器會提供這個圖片文件

例子2: 請求目錄

  • 用戶訪問 http://example.com/blog/
  • 瀏覽器發送 GET 請求,路徑為 /blog/
  • 函數檢查:
    • 靜態根目錄已設置 ?
    • 請求方法是 GET ?
    • 路徑 /blog/ 是合法的 ?
    • 檢測到路徑以 / 結尾,自動添加 index.html
    • 檢查 /var/www/html/blog/index.html 是否存在
    • 如果存在,返回 true;如果不存在,返回 false

接下來就是開發者對于路由表的規則的設置了,首先就是判斷是什么類型的請求,如果是靜態資源請求就調用靜態資源的處理方法,如果是功能性的請求就匹配路由表中的方法,并且對不同方法設置不同的請求,如果兩種請求都不是就說明是錯的了

// 通過請求類型分別處理
void Route(HttpRequest &req, HttpResponse *rsp)
{// 是靜態資源請求就靜態資源處理if(IsFileHandler(req) == true)return FileHandler(req, rsp);// 動態性請求就動能性請求處理if(req._method == "GET" || req._method == "HEAD")return Dispatcher(req, rsp, _get_route);else if(req._method == "POST")return Dispatcher(req, rsp, _post_route);else if(req._method == "PUT")return Dispatcher(req, rsp, _put_route);else if(req._method == "DELETE")return Dispatcher(req, rsp, _delete_route);// 兩個都不是就返回405 METHOD NOT ALLOWEDrsp->_status = 405;return;
}

接下來就是對靜態資源請求的處理,首先構造一個完整的文件路徑,然后如果結尾是/就說明是訪問的首頁,然后把路徑讀響應到正文中,如果文件讀取失敗就說明錯誤了。然后用ExtMime把路徑中的最后一部分給分離出來,然后填入到響應報頭的Content-type中

// 靜態資源請求處理
void FileHandler(const HttpRequest &req, HttpResponse *rsp)  // 處理靜態文件請求的函數
{// 讀取靜態文件資源,放到rsp的body,并設置mime// 判斷里面沒有修改資源路徑,所以在這里要修改string req_path = _basedir + req._path;  // 構造完整的文件路徑if(req._path.back() == '/')  // 如果請求路徑以'/'結尾(請求的是目錄)req_path += "index.html";  // 自動添加index.html作為默認頁面bool ret = Util::ReadFile(req_path, &rsp->_body);  // 讀取文件內容到響應體if(ret == false)  // 如果文件讀取失敗return;  // 直接返回,不設置任何響應內容string mime = Util::ExtMime(req_path);  // 根據文件擴展名獲取MIME類型rsp->SetHeader("Content-type", mime);  // 設置Content-type響應頭return;  // 處理完成,返回
}

接下來幾個例子,幫助大家理解

假設用戶通過瀏覽器訪問你的網站,請求了以下幾個不同的資源:

  • HTML頁面請求:
    • 用戶訪問 http://yourwebsite.com/about.html
    • 服務器找到 about.html 文件
    • ExtMime 函數確定這是HTML文件,返回 text/html
    • 服務器在響應頭中設置 Content-type: text/html
    • 瀏覽器收到響應后,看到這個MIME類型,知道應該將內容解析為HTML并渲染網頁
  • 圖片請求:
    • 當HTML頁面中引用了圖片 <img src="logo.png">
    • 瀏覽器請求 http://yourwebsite.com/logo.png
    • 服務器找到 logo.png 文件
    • ExtMime 函數確定這是PNG圖片,返回 image/png
    • 服務器在響應頭中設置 Content-type: image/png
    • 瀏覽器看到這個MIME類型,知道應該將內容解析為PNG圖片并顯示

接著就是對動態請求的處理。先循環遍歷路由表表里面正則表達式是否匹配請求對象的資源路徑,以找到對應的處理函數填充響應對象,如果找不到就是404

// 功能性請求處理
void Dispatcher(HttpRequest &req, HttpResponse *rsp, Handlers &handlers)  // 處理動態請求的分發器函數
{// 循環遍歷路由表表里面正則表達式是否匹配請求對象的資源路徑,以找到對應的處理函數填充響應對象for(auto &handler :handlers)  // 遍歷所有注冊的處理器{const regex &e = handler.first;  // 獲取當前處理器的正則表達式const Handler &func = handler.second;  // 獲取當前處理器的處理函數bool ret = regex_match(req._path, req._matches, e);  // 嘗試匹配請求路徑與正則表達式if(ret == false)  // 如果不匹配continue;  // 繼續檢查下一個處理器return func(req, rsp);  // 找到匹配的處理器,調用對應的處理函數并返回}// 找不到就是404rsp->_status = 404;  // 設置HTTP狀態碼為404(未找到)
}

接著就是把響應對象轉換成響應報文并發送。思路就是先把報頭完善一下,也就是進行判斷是短鏈接就把字段的Connection設置為close,否則就設置成keep-alive,然后判斷Content-Length有沒有填,ContentType應該填什么。要是重定向的話,就更新Location字段為重定向的url,這些都是必要的條件。當完成了這些之后,就可以構建響應報文了完善響應行,響應報頭,添加空行,添加響應正文,最后就是發送響應報文

// 把響應對象轉化成響應報文并發送
void WriteResponse(const PtrConnection &conn, const HttpRequest &req, HttpResponse &rsp)  // 將HTTP響應對象序列化為HTTP報文并發送
{// 1.完善報頭if(req.Close() == true)  // 如果請求要求關閉連接rsp.SetHeader("Connection", "close");  // 設置Connection頭為closeelsersp.SetHeader("Connection", "keep-alive");  // 否則設置為keep-alive保持連接if(rsp._body.empty() == false && rsp.HasHeader("Content-Length") == false)  // 如果響應體不為空且沒有設置Content-Length頭rsp.SetHeader("Content-Length", to_string(rsp._body.size()));  // 設置Content-Length頭為響應體大小if(rsp._body.empty() == false && rsp.HasHeader("Content-Type") == false)  // 如果響應體不為空且沒有設置Content-Type頭rsp.SetHeader("Content-Type", "application/octet-stream");  // 設置默認的Content-Type為二進制流if(rsp._redirect_flag = true)  // 如果需要重定向rsp.SetHeader("Location", rsp._redirect_url);  // 設置Location頭指向重定向URL// 2.組織響應報文stringstream rsp_str;  // 創建字符串流用于構建HTTP響應// 響應行rsp_str << req._version << " " << to_string(rsp._status) << " " << Util::StatusDesc(rsp._status) << "\r\n";  // 構建狀態行// 響應報頭for(auto &head : rsp._headers)  // 遍歷所有響應頭{rsp_str << head.first << ": " << head.second << "\r\n";  // 添加每個響應頭到響應中}// 一個空行rsp_str << "\r\n";  // 添加空行分隔響應頭和響應體// 響應正文rsp_str << rsp._body;  // 添加響應體// 3.發送響應報文conn->Send(rsp_str.str().c_str(), rsp_str.str().size());  // 發送完整的HTTP響應
}

最后就是設置錯誤信息,如果這個路徑是錯誤的,應該給用戶返回什么

void ErrorHandler(const HttpRequest &req, HttpResponse *rsp)  // 處理HTTP錯誤的函數
{string body;  // 創建一個字符串用于構建HTML錯誤頁面body += "<html>";  // HTML文檔開始標簽body += "<head>";  // 頭部開始標簽body += "<meta http-equiv='Content-Type' content='text/html;charset=utf-8'>";  // 設置頁面元數據,指定內容類型和字符集body += "</head>";  // 頭部結束標簽body += "<body>";  // 正文開始標簽body += "<h1>";  // 一級標題開始標簽body += std::to_string(rsp->_status);  // 添加HTTP狀態碼body += " ";  // 添加空格body += Util::StatusDesc(rsp->_status);  // 添加HTTP狀態碼的描述文本body += "</h1>";  // 一級標題結束標簽body += "</body>";  // 正文結束標簽body += "</html>";  // HTML文檔結束標簽// 響應正文類型是htmlrsp->SetContent(body, "text/html");  // 設置響應內容為HTML并指定MIME類型
}

疑問點

std::regex確實不能直接用作std::unordered_map的鍵

http不是管理的是協議的請求和響應嗎 這個?void OnConnect(const PtrConnection& conn) 連接不是應該由tcp去處理嗎?

“請求可能分多個TCP包到達,需要有地方存儲部分解析的數據,這個地方就是上下文” 那這個數據不是存儲在用戶的緩沖區當中的嗎?

HttpContext *context = conn->GetContext()->get<HttpContext>(); 這個代碼不懂

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

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

相關文章

多模態記憶融合:基于LSTM的連續場景生成——突破AI視頻生成長度限制

一、技術背景與核心挑戰 2025年視頻生成領域面臨的關鍵難題是長時程連貫性——傳統方法在生成超過5分鐘視頻時會出現場景跳變、物理規則不一致等問題。本研究提出時空記憶融合架構&#xff08;ST-MFA&#xff09;&#xff0c;通過LSTM記憶門控與多模態對齊技術&#xff0c;在R…

LabVIEW油氣井井下集成監測系統

LabVIEW平臺開發的油氣井井下集成監測系統通過實時監控油氣井的井下環境參數&#xff0c;如溫度、壓力和有害氣體含量&#xff0c;有效提高了油氣采收率并確保了作業安全。系統利用高精度傳感器和強大的數據處理能力&#xff0c;通過綜合監測和分析&#xff0c;實現了對油氣井環…

【python畫圖】:從入門到精通繪制完美柱狀圖

目錄 Python數據可視化&#xff1a;從入門到精通繪制完美柱狀圖一、基礎篇&#xff1a;快速繪制柱狀圖1.1 使用Matplotlib基礎繪制1.2 使用Pandas快速繪圖 二、進階篇&#xff1a;專業級柱狀圖定制2.1 多系列柱狀圖2.2 堆疊柱狀圖2.3 水平柱狀圖 三、專業參數速查表Matplotlib …

【 解決Cline插件無法激活及DeepSeek模型請求卡頓或者無法加載問題】

解決Cline插件無法激活及DeepSeek模型請求卡頓問題 問題描述 在VSCode中使用Cline插件時遇到以下問題&#xff1a; 插件長時間卡在"activating"激活狀態成功激活后發起DeepSeek對話時&#xff0c;API請求階段持續卡頓夜間時段問題出現頻率較低 環境信息 Cline版…

聊透多線程編程-線程互斥與同步-9.C# 線程互斥實現方式

目錄 1. 鎖機制 (Locking Mechanisms) (1) lock 關鍵字 (2) Monitor 類 2. 跨進程互斥機制 3. 信號量機制 (1) Semaphore 和 SemaphoreSlim 4. 讀寫鎖機制 (1) ReaderWriterLockSlim 5. 原子操作機制 (1) Interlocked 類 6. 自旋鎖機制 (1) SpinLock 線程互斥是一種…

eNSP無法啟動AR報錯碼40,而且按照eNSP幫助手冊排查都沒用,我的處理方法【自己存檔版】

問題&#xff1a; 已經嘗試過eNSP的幫助手冊&#xff0c;發現都沒用&#xff01; eNSP啟動AR設備報錯碼40且常規排查無效時&#xff0c;可嘗試以下解決方案&#xff08;按優先級排序&#xff09;&#xff1a; 1. 關閉Hyper-V和Windows沙盒&#xff08;我是這個問題&#xff0…

秒殺系統解決兩個核心問題的思路方法總結:1.庫存超賣問題;2.用戶重復搶購問題。

秒殺系統解決兩個核心問題 秒殺系統解決兩個核心問題&#xff1a;一、解決庫存超賣的核心邏輯&#xff1a;解釋&#xff1a;原子性保證&#xff1a; 二、如何避免重復搶購&#xff1a;使用 Redis 做唯一標識判斷優點&#xff1a; 三、流程完整梳理&#xff1a;四、通過數據庫建…

【集成電路版圖設計學習筆記】3.基本電路元件(MOS,電容,電阻)

一、MOSFET 在版圖設計中&#xff0c;要定義一個mosfet&#xff0c;最關鍵的層次是polysilicon&#xff08;多晶硅&#xff09;和active&#xff08;有源區&#xff09;。用有源區定義了一個矩形的區域&#xff0c;在這個區域內才可以形成一個有源器件&#xff0c;然后再用多晶…

藍橋杯之差分題型

一維差分 問題描述 給定一個長度為 nn 的序列 aa。 再給定 mm 組操作&#xff0c;每次操作給定 33 個正整數 l,r,dl,r,d&#xff0c;表示對 al~ral~r? 中的所有數增加 dd。 最終輸出操作結束后的序列 aa。 Update&#xff1a;由于評測機過快&#xff0c;n,mn,m 于 2024…

深入剖析 C/S 與 B/S 架構及網絡通信基礎

目錄 C/S 架構詳解? 概念與示例? 優點? B/S 架構詳解? 概念與示例? 優勢? 缺點? C/S 與 B/S 的區別? 架構組成? 使用場景? 開發和維護? 安全性? 網絡通信基礎? IP 地址? MAC&#xff08;物理地址&#xff09;? 端口? 路由器? 網關? 子網掩…

常見免殺框架的使用(3款)---【AniYaGUI1.2.0、AV_Evasion_Tool掩日、FoxBypass_V1.0】

一、AniYaGUI1.2.0免殺框架 環境&#xff1a;虛擬機Win10 、云服務器 工具&#xff1a;Xshell、CobaltStrike 項目下載地址&#xff1a; https://github.com/piiperxyz/AniYa 1. 安裝Go語言環境 確保Win10虛擬機安裝 Golang 且環境變量中包含 go 否則?法編譯&#xff08;注…

Apache HTTPD 換行解析漏洞

漏洞介紹 CVE-2017-15715 Apache HTTPD 是一個廣泛使用的 HTTP 服務器&#xff0c;可以通過 mod_php 模塊來運行 PHP 網頁。在其 2.4.0 到 2.4.29 版本中存在一個解析漏洞&#xff0c;當文件名以 1.php\x0A 結尾時&#xff0c;該文件會被按照 PHP 文件進行解析&#xff0c;這…

常用開發環境/工具版本選擇(持續更新中)

操作系統&#xff1a;Ubuntu Server Version&#xff08;LTS&#xff09;Latest Sub VerRelease Time24.04(Noble Numbat)24.04.22025-02-1622.04(Jammy Jellyfish)22.04.52024-09-1120.04(Focal Fossa)20.04.62023-03-1418.04(Bionic Beaver)18.04.62021-09-1516.04.7(Xenial…

STM32 認識STM32

目錄 什么是嵌入式&#xff1f; 認識STM32單片機 開發環境安裝 安裝開發環境 開發板資源介紹 單片機開發模式 創建工程的方式 燒錄STM32程序 什么是嵌入式&#xff1f; 1.智能手環項目 主要功能有&#xff1a; 彩色觸摸屏 顯示時間 健康信息&#xff1a;心率&#…

C#核心筆記——(六)框架基礎

我們在編程時所需的許多核心功能并不是由C#語言提供的,而是由.NET Framework中的類型提供的。本節我們將介紹Framework在基礎編程任務(例如虛的等值比較、順序比較以及類型轉換)中的作用。我們還會介紹Framework中的基本類型,例如String、DateTime和Enum. 本章中的絕大部分…

AI——K近鄰算法

文章目錄 一、什么是K近鄰算法二、KNN算法流程總結三、Scikit-learn工具1、安裝2、導入3、簡單使用 三、距離度量1、歐式距離2、曼哈頓距離3、切比雪夫距離4、閔可夫斯基距離5、K值的選擇6、KD樹 一、什么是K近鄰算法 如果一個樣本在特征空間中的k個最相似&#xff08;即特征空…

transient關鍵字深度解析

Java transient 關鍵字深度解析 transient(意思:瞬時的,瞬間的) 1. 核心概念 (1) 基本定義 作用:標記字段不參與序列化 適用場景: 敏感數據(如密碼、密鑰) 臨時計算字段 依賴運行時環境的字段(如Thread對象) (2) 語法示例 java public class User implements Se…

信刻電子檔案藍光光盤刻錄安全檢測長期歸檔

信刻一直致力于為檔案館、各行業檔案部門&#xff0c;提供跨網數據交換、電子檔案數據磁光異質備份歸檔解決方案。所研制的電子檔案光盤智能長期歸檔系統&#xff0c;滿足國產環境下”刻、管、存、檢、用”全生命周期管理應用需求&#xff0c;能夠提供一份離線歸檔、一份近線存…

Word 中“母版頁”的等效機制

Word 和 PowerPoint 不太一樣——**Word 實際上沒有像 PowerPoint 那樣的“母版頁&#xff08;Master Page&#xff09;”**功能。但它有1個和“母版頁”功能類似的東西&#xff0c;可能造成你看到的“校徽自動出現在每一頁”的現象&#xff1a; ? Word 中“母版頁”的等效機制…

Go:反射

為什么使用反射 在編程中&#xff0c;有時需編寫函數統一處理多種值類型 &#xff0c;這些類型可能無法共享同一接口、布局未知&#xff0c;甚至在設計函數時還不存在 。 func Sprint(x interface{}) string {type stringer interface {String() string}switch x : x.(type) …