介紹HTTP協議基本結構與Linux中基本實現HTTPServer

介紹HTTP協議基本結構與基本實現HTTPServer

HTTP協議

前面已經了解了協議的重要性并且已經定義了屬于我們自己的協議,但是在網絡中,已經有一些成熟的協議,最常用的就是HTTP協議

在互聯網世界中,HTTP(HyperText Transfer Protocol,超文本傳輸協議)是一個至關重要的協議。它定義了客戶端(如瀏覽器)與服務器之間如何通信,以交換或傳輸超文本(如HTML文檔)

HTTP協議是客戶端與服務器之間通信的基礎。客戶端通過HTTP協議向服務器發送請求,服務器收到請求后處理并返回響應。HTTP協議是一個基于TCP協議的無連接、無狀態的協議,即每次請求都需要建立新的連接,且服務器不會保存客戶端的狀態信息

因為HTTP是基于TCP的,而TCP是面向字節流的,所以在傳遞信息時肯定會存在收到的信息不完整的情況,那么根據前面的經驗,不論是客戶端和服務端都需要做數據完整性檢查

URL與URI

URI(統一資源標識符)是用于標識網絡資源的字符串,而URL(統一資源定位符)是URI的一個擴展,它不僅標識資源,還提供了定位該資源的方法

一個標準的URL通常包含以下幾個部分:

http://www.example.com:80/path/to/resource?param1=value1&param2=value2#fragment
  1. 協議(http://):指定客戶端和服務器之間通信使用的協議
  2. 主機名(www.example.com):資源所在服務器的域名或IP地址
  3. 端口號(:80):服務器上運行的服務的端口號(HTTP默認為80)
  4. 路徑(/path/to/resource):資源在服務器上的具體位置
  5. 查詢參數(?param1=value1&param2=value2): 發送給服務器的額外參數
  6. 片段標識符(#fragment): 資源內的特定部分的引用

深入URL結構

仔細觀察URL的結構,除去協議、主機名和端口號以外,剩余的部分正好就是文件的路徑,而在操作系統之下標記一個文件的路徑也是使用同樣的結構,所以這里就可以得出一個結論:URL的結構實際上就是找到對應的主機,再在該主機上找到指定路徑下的文件

兩臺計算機進行網絡通信實際上就是在做IO,而要做IO就需要有對應的數據交換,所以首先就必須要定位到文件的位置,定位文件就是通過路徑進行的,所以URL的設計思想對應的就是操作系統定位文件的方式,但是需要注意在URL中,路徑開始的第一個/并不代表著操作系統的根目錄,而是Web應用的根目錄

URL編碼

URL編碼是一種將非ASCII字符和特殊字符轉換為可在URL中安全傳輸的編碼方式。因為URL只能使用ASCII字符集中的一部分字符,所以需要對其他字符進行編碼

編碼原理

URL編碼遵循以下規則:

  1. ASCII字母、數字和某些特殊字符保持不變,即:

    • 字母:A-Z, a-z
    • 數字:0-9
    • 特殊字符:-, _, ., ~
  2. 其他字符(如空格、中文、特殊符號)按照以下步驟編碼:

    • 將字符轉換為字節序列(通常使用UTF-8編碼)
    • 每個字節用百分號%后跟兩位十六進制數表示

常見編碼示例

字符URL編碼說明
空格%20 (或 +)空格可以編碼為%20+(表單提交時)
!%21感嘆號
#%23井號(必須編碼,因為在URL中有特殊含義)
$%24美元符號
&%26和號(必須編碼,因為用于分隔參數)
+%2B加號
/%2F正斜杠(在路徑中通常不編碼)
:%3A冒號(在協議分隔符后通常不編碼)
=%3D等號(在查詢參數中通常不編碼)
?%3F問號(必須編碼,因為用于引導查詢參數)
@%40at符號
%E4%B8%AD漢字"中"的UTF-8編碼

編碼示例

原始URL中包含中文和特殊字符:

https://example.com/搜索?關鍵詞=中國&category=歷史

編碼后的URL:

https://example.com/%E6%90%9C%E7%B4%A2?%E5%85%B3%E9%94%AE%E8%AF%8D=%E4%B8%AD%E5%9B%BD&category=%E5%8E%86%E5%8F%B2

URL編碼的應用場景

  1. 查詢參數傳遞:確保包含特殊字符的參數能正確傳遞
  2. 表單提交:當使用GET方法提交表單時,表單數據會編碼到URL中
  3. 國際化URL:包含非英語字符的URL需要編碼
  4. 防止字符注入攻擊:編碼可以防止某些特殊字符被解釋為代碼

注意事項

  • 不同的字符集可能產生不同的編碼結果,現代Web應用通常使用UTF-8
  • 某些舊系統可能使用非標準編碼(如GB2312)
  • 瀏覽器會自動對輸入的URL進行編碼
  • 在服務器端編程中,各種語言都提供了URL編碼和解碼的函數
  • 編碼應該在正確的位置進行,不要對整個URL進行編碼,而是對各部分中的特殊字符進行編碼

URL編碼確保了含有特殊字符的數據可以通過URL安全傳輸,是Web應用開發中的基礎知識

HTTP請求結構和HTTP響應結構

基本認識

既然是傳遞數據,那么肯定要做的就是確定雙方都認識的一個結構,這樣獲取到的數據才能被正確解釋,在HTTP中,有兩種結構,分別是HTTP請求和HTTP響應

一個基本的HTTP請求結構如下圖所示:

在這里插入圖片描述

一個基本的HTTP響應結構如下圖所示:

在這里插入圖片描述

在上面兩個結構圖中,可以看到都存在換行符,這里的換行符更準確來說應該是\r\n

但是這僅僅只是定義了結構,根據前面的知識可以知道除了有結構以外,還需要對結構化的數據進行序列化和反序列化,HTTP協議的設計者也考慮到了這一點,所以HTTP實際上有自己的序列化和反序列化方式,而不需要程序員自己去實現前面類似于使用JSONCPP庫進行JSON格式字符串的來回轉換

HTTP請求結構

在HTTP請求結構中:

首先是請求行部分,其中第一個就是HTTP的請求方法,具體的方法一共有6種,但是最常見的就是GETPOST方法,具體二者的區別會在后面的章節進行講解;接著是URI實際上對應的就資源路徑;最后就是HTTP版本,具體在HTTP版本部分會有具體介紹,此處不具體說明

第二部分就是請求報頭,請求報頭所有的屬性都是以key: value的形式存在,具體存在哪些屬性在后面的章節會提及,此處不具體說明

最后就是請求正文,這里一般存放著請求時傳遞給服務器的參數,但是具體是否存在請求參數需要看請求方式,具體見后面的章節進行講解

HTTP響應結構

在HTTP響應結構中:

首先是響應行部分,第一個就是HTTP版本,之所以HTTP請求和響應中都需要指定版本號是為了確保通信雙方能夠正確理解和處理彼此的消息,處理版本差異,并在不同版本的HTTP協議間提供平滑的過渡。這種設計使得HTTP協議能夠在保持向后兼容的同時不斷發展和改進;接著是狀態碼,HTTP協議規定,不論是請求成功還是請求失敗都需要給客戶端響應,但是如果一味地響應同一種數據,那么客戶端就無法分辨哪一個為正常數據,所以為了標識不同的響應結果,就需要這個狀態碼,在深入HTTP序列化和反序列化有具體講解,此處不具體說明;最后就是狀態碼描述,這個描述文字對應的狀態碼的含義,具體見深入HTTP序列化和反序列化

第二部分和第三部分與HTTP請求結構的第二部分相同,此處不再贅述

封裝網絡接口

接下來需要編寫處理HTTP請求的服務器,而因為HTTP是基于TCP的,所以為了后面編寫的方便,此處先對前面使用的網絡接口進行封裝

基本設計思路

本次封裝考慮采用模版方法設計模式(Template Method Pattern),其是一種行為型設計模式,它定義了一個算法的框架,但將一些步驟的具體實現延遲到子類中。通過這種方式,模板方法允許子類在不改變算法結構的情況下重新定義某些步驟

該模式的核心思想是:在父類中定義算法的整體結構,而將某些具體步驟留給子類去實現 。這樣可以確保算法的骨架保持一致,同時允許子類根據需要自定義某些細節

在模版方法設計模式中存在一些核心概念:

  1. 抽象類(Abstract Class):定義了算法的整體框架,并包含一個或多個抽象方法(由子類實現)。此外,它還可能包含一些具體方法(默認實現),這些方法可以直接被子類復用
  2. 模板方法(Template Method):模板方法是一個具體方法,一般不允許被子類重寫。它定義了算法的執行步驟,調用了抽象方法和具體方法
  3. 具體類(Concrete Class):具體類(子類)繼承抽象類,并實現抽象方法,提供具體的實現邏輯

根據這個模式的基本介紹,下面考慮具體的設計思路:

首先是抽象類,定義為BaseSocket,其中提供一系列抽象方法和一個具體模版方法initSocket,在具體模版方法中需要調用抽象方法,當子類實現抽象方法后,通過向上轉型調用抽象類中的具體方法

接著是具體類,具體類一共有兩個,一個是UdpSocket,另外一個是TcpSocket,因為本次需要實現HttpServer,所以重點實現TcpSocket

在具體方法中,因為是針對TcpSocket,所以需要經過下面的三個步驟:

  1. 創建服務器監聽套接字
  2. 綁定服務器監聽套接字
  3. 服務器進入監聽狀態

根據這三個步驟,在initSocket函數中就需要對應的三個方法的調用

設計抽象類

根據上面的思路,可以設計出抽象類如下:

class BaseSocket
{
public:virtual ~BaseSocket() = default;// 創建套接字virtual void createSocket() = 0;// 綁定virtual void toBind() = 0;// 監聽virtual void toListen() = 0;// 具體實現方法void initSocket(){createSocket();toBind();toListen();}
};

設計具體類

本次以TCP為例,設計TcpServer類,需要繼承BaseServer并實現對應的抽象方法,需要注意,子類不允許實現initServer方法,實現方式與前面類似,只是將具體邏輯抽取到單獨的函數中,代碼如下:

// 默認端口
const uint16_t default_port = 8080;
// 默認最大支持排隊等待連接的客戶端個數
const int max_backlog = 8;class TcpSocket : public BaseSocket
{
public:TcpSocket(int port = default_port): _listen_socketfd(-1), _s_addr_in(port){}// 實現創建套接字void createSocket() override{_listen_socketfd = socket(AF_INET, SOCK_STREAM, 0);if (_listen_socketfd < 0){LOG(LogLevel::FATAL) << "監聽套接字創建失敗:" << strerror(errno);exit(static_cast<int>(ErrorNumber::SocketFail));}LOG(LogLevel::INFO) << "監聽套接字創建成功:" << _listen_socketfd;}// 實現綁定void toBind() override{int n = bind(_listen_socketfd, &_s_addr_in, _s_addr_in.getLength());if (n < 0){LOG(LogLevel::ERROR) << "綁定失敗:" << strerror(errno);exit(static_cast<int>(ErrorNumber::BindSocketFail));}LOG(LogLevel::INFO) << "綁定成功";}// 實現監聽void toListen() override{int ret = listen(_listen_socketfd, max_backlog);if (ret < 0){LOG(LogLevel::ERROR) << "監聽失敗:" << strerror(errno);exit(static_cast<int>(ErrorNumber::ListenFail));}LOG(LogLevel::INFO) << "監聽成功";}~TcpSocket(){}private:int _listen_socketfd;  // 監聽套接字SockAddrIn _s_addr_in; // 套接字結構
};

接著,因為TCP還需要一個接收過程,所以還需要一個接口用于接收,并且為了讓上層處理具體的客戶端信息,考慮將客戶端信息返回給上層

根據這個思路,首先在抽象類中定義接收抽象函數和獲取接收套接字函數:

// 接收
virtual SockAddrIn toAccept() = 0;

接著在具體類中實現:

// 實現接收
SockAddrIn toAccept() override
{struct sockaddr_in peer;socklen_t length = sizeof(peer);int ac_socketfd = accept(_listen_socketfd, reinterpret_cast<struct sockaddr *>(&peer), &length);if(ac_socketfd < 0){LOG(LogLevel::ERROR) << "接收失敗:" << strerror(errno);exit(static_cast<int>(ErrorNumber::AcceptFail));}LOG(LogLevel::INFO) << "接收成功:";// 向上層返回客戶端return peer;
}

實現TcpServer

有了基本接口的封裝后,因為HttpServer基于TCP,所以需要先實現TcpServer類,基本思路如下:

對于TcpServer類來說,需要考慮的事情就是執行任務,而因為是服務器,本次考慮只讓服務器做監聽和接收而不進行任何其他任務,所以考慮使用回調函數將任務交給上層。但是現在就遇到了一個問題,TcpSocketac_socketfd是一個局部變量,并且此時沒有相應的接口可以獲得該局部變量,所以為了解決這個問題,可以考慮對開始的toAccept函數進行改寫

首先,為了外部可以獲取到客戶端的信息,可以考慮設計一個輸出型參數:

void toAccept(SockAddrIn *client)
{// ...
}

接著,為了外部可以直接使用接收成功后的套接字,這里直接將函數返回值修改為int即可:

int toAccept(SockAddrIn *client)
{// ...
}

最后,整體修改toAccept函數的邏輯:

// 實現接收
int toAccept(SockAddrIn* client) override
{struct sockaddr_in peer;socklen_t length = sizeof(peer);int ac_socketfd = accept(_listen_socketfd, reinterpret_cast<struct sockaddr *>(&peer), &length);if (ac_socketfd < 0){LOG(LogLevel::ERROR) << "接收失敗:" << strerror(errno);exit(static_cast<int>(ErrorNumber::AcceptFail));}LOG(LogLevel::INFO) << "接收成功:";*client = peer;// 向上層返回客戶端return ac_socketfd;
}

既然服務器要做監聽和接收,那么少不了的就是調用TcpSocket中的監聽和接收接口,而因為監聽是在初始化就開始了,所以只需要在啟動時處理接收即可,接著將其他任務交給上層,但是,交給上層之前,需要處理好接收和任務分離,保證接收和執行任務可以獨立進行,此時就需要利用前面的線程或者進程,本次考慮使用線程,所以基本代碼如下:

using task_t = std::function<void(SockAddrIn, int)>;
using base_socket_ptr = std::shared_ptr<BaseSocket>;class TcpServer;struct data
{SockAddrIn client;int ac_socketfd;TcpServer* self;
};class TcpServer
{
public:TcpServer(uint16_t port = default_port):_bs(std::make_shared<TcpSocket>(port)),_isRunning(false){_bs->initSocket();}// 注冊方法void registerFunc(task_t handler){_handler = handler;}static void* routine(void* args){struct data* ptr = reinterpret_cast<struct data*>(args);ptr->self->_handler(ptr->client, ptr->ac_socketfd);return NULL;}void start(){_isRunning = true;while (_isRunning){SockAddrIn client;// 因為在SockAddrIn內部重載了&,此處注意使用addressof取出當前對象的實際地址int ac_socketfd = _bs->toAccept(std::addressof(client));pthread_t pid;struct data d = {client, ac_socketfd, this};pthread_create(&pid, NULL, routine, &d);}}// 獲取底層接口base_socket_ptr getSocketPtr(){return _bs;}private:std::shared_ptr<BaseSocket> _bs; // 向上轉型bool _isRunning;task_t _handler;
};

實現HTTP服務器

有了基本的TcpServer類后就可以開始實現基于TCP的HTTP服務器,為了能夠快速看到服務器運行效果,先搭建一個基本的服務器端。這個服務器需要實現的功能:當瀏覽器請求服務端時可以看到服務端給瀏覽器回顯的一行HTML內容

有了目標功能后,現在考慮如何實現這個功能。既然要給瀏覽器回顯一行HTML內容,那么服務器就必須要有給客戶端返回HTTP響應的功能。需要注意,本次客戶端只是發送一個不含有任何請求報頭和請求體的HTTP請求,所以服務器可以不用對客戶端的請求進行細致化處理,具體如何細致化處理會在深入HTTP序列化和反序列化

創建HttpServer

因為HTTP是基于TCP的,所以在HttpServer類中需要有一個TcpServer成員,接著根據前面的思路,需要提供處理HTTP請求的函數,所以整體結構如下:

class HttpServer
{
public:HttpServer(uint16_t port = default_port):_tp(std::make_shared<TcpServer>(port)){}void handleHttpRequest(SockAddrIn sock_addr_in, int ac_socketfd){}private:std::shared_ptr<TcpServer> _tp;
};

但是,上面的基本結構中還缺少一個關鍵的環節:啟動服務器。啟動HTTP服務器本質就是啟動TcpServer,啟動TcpServer時需要傳遞執行的任務,因為本次TcpServer執行的就是處理HTTP請求,所以執行的函數就是handleHttpRequest,所以實現如下:

void start()
{// 注冊方法_tp->registerFunc([this](SockAddrIn sock_addr_in, int ac_socketfd){this->handleHttpRequest(sock_addr_in, ac_socketfd);});_tp->start();
}

實現處理HTTP請求接口

根據前面提到的目標,服務器只需要根據HTTP響應結構向客戶端返回對應的內容即可。首先,構建一個內容作為HTTP響應結構的響應體:

std::string msg = "<h1>Hello Linux</h1>";
const std::string sep = "\r\n";
std::string body = msg + sep;
std::string status_line = "HTTP/1.1 200 OK" + sep + sep;std::string httpResponse = status_line + body;

接著要實現發送,就必須要調用對應的接口,即send,所以基本實現如下:

void handleHttpRequest(SockAddrIn sock_addr_in, int ac_socketfd)
{LOG(LogLevel::INFO) << "收到來自:" << sock_addr_in.getIp() << ":" << sock_addr_in.getPort() << "的連接";// 不對客戶端發送的HTTP請求進行處理// 向客戶端發送一個HTTP響應std::string msg = "<h1>Hello Linux</h1>";const std::string sep = "\r\n";std::string body = msg + sep;std::string status_line = "HTTP/1.1 200 OK" + sep + sep;std::string httpResponse = status_line + body;ssize_t ret = send(ac_socketfd, httpResponse.c_str(), httpResponse.size(), 0);(void)ret;
}

測試

測試代碼如下:

#include "HttpServer.hpp"
#include <memory>using namespace HttpServerModule;int main(int argc, char* argv[])
{uint16_t port = std::stoi(argv[1]);std::shared_ptr<HttpServer> hs = std::make_shared<HttpServer>(port);hs->start();return 0;
}

編譯運行后打開瀏覽器輸入對應的IP地址和端口號即可看到一個客戶端收到的內容:

在這里插入圖片描述

封裝發送和接收接口

在上面最后一步:向客戶端發送HTTP響應時使用的是底層send接口,既然是底層接口,同樣可以考慮對這個接口進行封裝,同時也對接收接口進行封裝。因此,封裝的基本思路為:首先在BaseSocket基類中創建接口和發送兩個接口的聲明,再在具體實現類TcpSocket中實現這兩個接口:

=== “BaseSocket

// 發送數據
virtual void sendData(std::string &in_data, int ac_socketfd) = 0;
// 接收數據
virtual void recvData(std::string &out_data, int ac_socketfd) = 0;

=== “TcpSocket

 // 接收數據void recvData(std::string &out_data, int ac_socketfd) override{char buffer[4096] = {0};ssize_t ret = recv(ac_socketfd, buffer, sizeof(buffer), 0);if(ret > 0){out_data = buffer;}}// 發送數據void sendData(std::string &in_data, int ac_socketfd) override{ssize_t ret = send(ac_socketfd, in_data.c_str(), in_data.size(), 0);(void)ret;}

但是,現在又遇到了第二個問題,因為接收數據接口和發送數據接口都是TcpSocket類中的,而在HttpServer中是TcpServer,無法直接調用到TcpSocket中的接收和發送接口,這里最直接的思路就是在TcpServer中提供一個接口返回底層的TcpSocket類或者其基類對象:

socket_ptr getSocketPtr()
{return _bs;
}

此時在上層就可以調用該接口獲取到調用對象的指針,對應的整體處理HTTP請求接口實現如下:

void handleHttpRequest(SockAddrIn sock_addr_in, int ac_socketfd)
{LOG(LogLevel::INFO) << "收到來自:" << sock_addr_in.getIp() << ":" << sock_addr_in.getPort() << "的連接";// 不對客戶端發送的HTTP請求進行處理// 向客戶端發送一個HTTP響應// ...std::shared_ptr<BaseSocket> bs = _tp->getSocketPtr();bs->sendData(httpResponse, ac_socketfd);
}

再次編譯運行后打開瀏覽器輸入對應的IP地址和端口號即可看到一個客戶端收到的內容:
在這里插入圖片描述

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

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

相關文章

Linux和RTOS簡析

以下是針對 Linux驅動開發、RTOS&#xff08;實時操作系統&#xff09;任務狀態&#xff08;就緒態&#xff09; 以及 互斥鎖 的詳細解釋&#xff1a; 一、Linux設備驅動 1. 什么是設備驅動&#xff1f; 定義&#xff1a;設備驅動是操作系統內核的一部分&#xff0c;用于管理…

docker 常用命令大全(二),docker 鏡像操作 ,持續更新

docker 相關的命令 在公共倉庫中下載 docker pull bitnami/postgresql:12.8.0查看鏡像 docker images |grep postgresql打tag推送到本地倉庫 docker tag postgresql:12.8.0 docker.公司域名.com/library/postgresql:12.8.0推送到本地倉庫 docker push docker.公司域名com…

Git使用和原理(3)

1.遠程操作 1.1分布式版本控制系統 我們?前所說的所有內容&#xff08;?作區&#xff0c;暫存區&#xff0c;版本庫等等&#xff09;&#xff0c;都是在本地&#xff01;也就是在你的筆記本或者 計算機上。?我們的 Git 其實是分布式版本控制系統&#xff01;什么意思呢&a…

[本周五題]Javascript面試常考題手撕場景UR緩存、new關鍵字、大數相加、最長遞增子序列、高并發請求、大文件上傳和WebWorks

LUR緩存實現 以下是 JavaScript 實現 LRU 緩存的詳細解析&#xff0c;結合核心原理與代碼實現&#xff0c;并標注來源&#xff1a; 一、LRU 緩存核心原理 LRU&#xff08;Least Recently Used&#xff09;緩存淘汰策略的核心思想是&#xff1a;當緩存容量滿時&#xff0c;優先…

c語言zixue

該文主要是記錄我學習中遇到的一些重點、易出問題的內容 教材p16.17 先從一個簡單的例子開始吧 #include <stdio.h> //編譯預處理命令 int main() //程序的主函數 {printf("To C"); //輸出語句return 0; //返回語句 } #include <stdio.h>是編譯預…

卷積神經網絡(CNN)的主要架構

卷積神經網絡&#xff08;CNN, Convolutional Neural Networks&#xff09;是深度學習中最重要的模型之一&#xff0c;廣泛應用于計算機視覺、目標檢測、語義分割等任務。自 LeNet 誕生以來&#xff0c;CNN 結構經歷了多個重要發展階段&#xff0c;出現了許多經典架構&#xff…

【開源代碼解讀】AI檢索系統R1-Searcher通過強化學習RL激勵大模型LLM的搜索能力

關于R1-Searcher的報告&#xff1a; 第一章&#xff1a;引言 - AI檢索系統的技術演進與R1-Searcher的創新定位 1.1 信息檢索技術的范式轉移 在數字化時代爆發式增長的數據洪流中&#xff0c;信息檢索系統正經歷從傳統關鍵詞匹配到語義理解驅動的根本性變革。根據IDC的統計…

從0到1入門Docker

一、快速入門 Docker run命令中的常見參數 -d&#xff1a;讓容器后臺運行--name&#xff1a;給容器命名&#xff08;唯一&#xff09;-e&#xff1a;環境變量-p&#xff1a;宿主機端口映射到容器內端口鏡像名稱結構&#xff1a;Repository &#xff1a;TAG&#xff08;鏡像名&…

接口自動化入門 —— Jmeter實現在接口工具中關聯接口處理方案

1. JMeter 接口關聯處理的核心概念 接口關聯是指在多個接口請求之間共享數據&#xff0c;例如將一個接口的返回值作為另一個接口的輸入參數。常見的場景包括&#xff1a; 使用登錄接口返回的 Token 作為后續接口的認證信息。 將一個接口返回的 ID 作為另一個接口的請求參數。…

Flink-學習路線

最近想學習一下Flink&#xff0c;公司的實時需求還是不少的&#xff0c;因此結合ai整理了一份學習路線&#xff0c;記錄一下。 當然&#xff0c;公司也有Scala版本Flink框架&#xff0c;也學習了一下。這里只說Java版本 1. Java基礎 目標: 掌握Java編程語言的基礎知識。 內容…

ranger集成starrock報錯

org.apache.ranger.plugin.client.HadoopException: initConnection: Unable to connect to StarRocks instance, please provide valid value of field : {jdbc.driverClassName}.. com.mysql.cj.jdbc.Driver. 可能的原因 JDBC 驅動缺失&#xff1a;運行環境中沒有安裝 MySQL …

python Jsonpath表達式語法取值

python Jsonpath 語法規則&#xff1a; 演示數據&#xff1a;{"status":"0","msg":"成功","data": [ {"foo1": "bar1"},{"foo1": "bar2"}]} 根節點&#xff1a;$ $.status $.…

Blender-MCP服務源碼2-依賴分析

Blender-MCP服務源碼2-依賴分析 有個大佬做了一個Blender-MCP源碼&#xff0c;第一次提交代碼是【2025年3月7號】今天是【2025年月15日】也就是剛過去一周的時間&#xff0c;所以想從0開始學習這個代碼&#xff0c;了解一下大佬們的開發思路 1-核心知識點 from mcp.server.fas…

反射(第三篇)、代理模式、靜態代理和動態代理、InvocationHandler實際應用

DAY11.3 Java核心基礎 反射&#xff08;第三篇&#xff09; 前兩篇我們學習了反射的概念和基本操作 實際開發中&#xff0c;反射應用于那里&#xff1f; 動態代理 java中的動態代理就是一個重要的作用 代理模式 代理模式是java中常用的設計模式 指的是在處理一個業務邏輯…

Unity 封裝一個依賴于MonoBehaviour的計時器(上) 基本功能

靈感來自下面這本書的協程部分,因此我就自己嘗試寫了一個 我的新書Unity3D游戲開發&#xff08;第3版&#xff09; | 雨松MOMO程序研究院 如果你不知道什么是協程:unity保姆級教程之協同程序_unity協同-CSDN博客 一句話概括:協程就是單線程的異步操作,其作用于Unity的主線程 1…

數學建模 第一節

目錄?????? 前言 一 優化模型的類型 二 線性規劃1 線性規劃2 三 0-1規劃 總結 前言 數學建模主要是將問題轉化為模型&#xff0c;然后再以編程的形式輸出出來 算法都知道&#xff0c;數學建模也需要用到算法&#xff0c;但是不是主要以編程形式展示&#xff0c;而是…

Vulkan視頻解碼decode顯示display之同步

在ReleaseDisplayedPicture函數中消耗圖片資源并且顯示display完成&#xff0c;設置兩個標志m_hasConsummerSignalFence true 和m_hasConsummerSignalSemaphore true virtual int32_t ReleaseDisplayedPicture(DecodedFrameRelease** pDecodedFramesRelease, uint32_t nu…

網絡空間安全(32)Kali MSF基本介紹

前言 Metasploit Framework&#xff08;簡稱MSF&#xff09;是一款功能強大的開源安全漏洞檢測工具&#xff0c;被廣泛應用于滲透測試中。它內置了數千個已知的軟件漏洞&#xff0c;并持續更新以應對新興的安全威脅。MSF不僅限于漏洞利用&#xff0c;還包括信息收集、漏洞探測和…

設計模式學習記錄

設計模式23種 創建型抽象工廠模式工廠模式生成器模式原型模式單例模式 結構型適配器模式橋接模式組合模式裝飾模式外觀模式享元模式代理模式 行為型責任鏈模式命令模式解釋器模式迭代器模式中介者模式備忘錄模式觀察者模式狀態模式策略模式模版方法模式訪問者模式 創建型 與對…

2.5 python接口編程

在現代軟件開發的復雜生態系統中&#xff0c;不同系統、模塊之間的交互協作至關重要。接口編程作為一種關鍵機制&#xff0c;定義了組件之間的通信規范與交互方式。Python 憑借其卓越的靈活性、豐富的庫資源以及簡潔易讀的語法&#xff0c;在接口編程領域占據了重要地位&#x…