一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

基于Http和Tcp協(xié)議自主實(shí)現(xiàn)的WebServer

科技綠洲 ? 來(lái)源:Linux開發(fā)架構(gòu)之路 ? 作者:Linux開發(fā)架構(gòu)之路 ? 2023-11-09 11:11 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

項(xiàng)目介紹

該項(xiàng)目是一個(gè)基于Http和Tcp協(xié)議自主實(shí)現(xiàn)的WebServer,用于實(shí)現(xiàn)服務(wù)器對(duì)客戶端發(fā)送過(guò)來(lái)的GET和POST請(qǐng)求的接收、解析、處理,并返回處理結(jié)果給到客戶端。該項(xiàng)目主要背景知識(shí)涉及C++、網(wǎng)絡(luò)分層協(xié)議棧、HTTP協(xié)議、網(wǎng)絡(luò)套接字編程、CGI技術(shù)、單例模式、多線程編程、線程池等。

圖片

CGI技術(shù)

CGI技術(shù)可能大家比較陌生,單拎出來(lái)提下。

概念

CGI(通用網(wǎng)關(guān)接口,Common Gateway Interface)是一種用于在Web服務(wù)器上執(zhí)行程序并生成動(dòng)態(tài)Web內(nèi)容的技術(shù)。CGI程序可以是任何可執(zhí)行程序,通常是腳本語(yǔ)言,例如Perl或Python

CGI技術(shù)允許Web服務(wù)器通過(guò)將Web請(qǐng)求傳遞給CGI程序來(lái)執(zhí)行任意可執(zhí)行文件。CGI程序接收HTTP請(qǐng)求,并生成HTTP響應(yīng)以返回給Web服務(wù)器,最終返回給Web瀏覽器。這使得Web服務(wù)器能夠動(dòng)態(tài)地生成網(wǎng)頁(yè)內(nèi)容,與靜態(tài)HTML文件不同。CGI程序可以處理表單數(shù)據(jù)、數(shù)據(jù)庫(kù)查詢和其他任務(wù),從而實(shí)現(xiàn)動(dòng)態(tài)Web內(nèi)容。一些常見的用途包括創(chuàng)建動(dòng)態(tài)網(wǎng)頁(yè)、在線購(gòu)物車、用戶注冊(cè)、論壇、網(wǎng)上投票等。

原理

通過(guò)Web服務(wù)器將Web請(qǐng)求傳遞給CGI程序,CGI程序處理請(qǐng)求并生成響應(yīng),然后將響應(yīng)傳遞回Web服務(wù)器,最終返回給客戶端瀏覽器。這個(gè)過(guò)程可以概括為:

  1. 客戶端發(fā)送HTTP請(qǐng)求到Web服務(wù)器。
  2. Web服務(wù)器檢查請(qǐng)求類型,如果是CGI請(qǐng)求,Web服務(wù)器將環(huán)境變量和請(qǐng)求參數(shù)傳遞給CGI程序,并等待CGI程序的響應(yīng)。
  3. CGI程序接收請(qǐng)求參數(shù),并執(zhí)行相應(yīng)的操作,例如讀取數(shù)據(jù)庫(kù)或處理表單數(shù)據(jù)等。
  4. CGI程序生成HTTP響應(yīng),將響應(yīng)返回給Web服務(wù)器。
  5. Web服務(wù)器將響應(yīng)返回給客戶端瀏覽器。

圖片

在這個(gè)過(guò)程中,Web服務(wù)器和CGI程序之間通過(guò)標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出(建立管道并重定向到標(biāo)準(zhǔn)輸入輸出)進(jìn)行通信。Web服務(wù)器將請(qǐng)求參數(shù)通過(guò)環(huán)境變量傳遞給CGI程序,CGI程序?qū)⑸傻捻憫?yīng)通過(guò)標(biāo)準(zhǔn)輸出返回給Web服務(wù)器。此外,CGI程序還可以通過(guò)其他方式與Web服務(wù)器進(jìn)行通信,例如通過(guò)命令行參數(shù)或文件進(jìn)行交互。

設(shè)計(jì)框架

日志文件

用于記錄下服務(wù)器運(yùn)行過(guò)程中產(chǎn)生的一些事件。日志格式如下:

圖片

日志級(jí)別說(shuō)明:

  • INFO: 表示正常的日志輸出,一切按預(yù)期運(yùn)行。
  • WARNING: 表示警告,該事件不影響服務(wù)器運(yùn)行,但存在風(fēng)險(xiǎn)。
  • ERROR: 表示發(fā)生了某種錯(cuò)誤,但該事件不影響服務(wù)器繼續(xù)運(yùn)行。
  • FATAL: 表示發(fā)生了致命的錯(cuò)誤,該事件將導(dǎo)致服務(wù)器停止運(yùn)行。

文件名稱和行數(shù)可以通過(guò)C語(yǔ)言中的預(yù)定義符號(hào)__FILE__和__LINE__,分別可以獲取當(dāng)前文件的名稱和當(dāng)前的行數(shù)。

#define INFO 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

// #將宏參數(shù)level轉(zhuǎn)為字符串格式
#define LOG(level, message) Log(#level, message, __FILE__, __LINE__)

TCPServer

思路是:創(chuàng)建一個(gè)TCP服務(wù)器,并通過(guò)初始化、綁定和監(jiān)聽等步驟實(shí)現(xiàn)對(duì)外服務(wù)。

具體實(shí)現(xiàn)中,單例模式通過(guò)一個(gè)名為GetInstance的靜態(tài)方法實(shí)現(xiàn),該方法首先使用pthread_mutex_t保證線程安全,然后使用靜態(tài)變量 _svr指向單例對(duì)象,如果 _svr為空,則創(chuàng)建一個(gè)新的TcpServer對(duì)象并初始化,最后返回 _svr指針。由于 _svr是static類型的,因此可以確保整個(gè)程序中只有一個(gè)TcpServer實(shí)例。

Socket方法用于創(chuàng)建一個(gè)監(jiān)聽套接字,Bind方法用于將端口號(hào)與IP地址綁定,Listen方法用于將監(jiān)聽套接字置于監(jiān)聽狀態(tài),等待客戶端連接。Sock方法用于返回監(jiān)聽套接字的文件描述符。

#define BACKLOG 5

class TcpServer{
private:
int _port; // 端口號(hào)
int _listen_sock; // 監(jiān)聽套接字
static TcpServer* _svr; // 指向單例對(duì)象的static指針
private:
TcpServer(int port)
:_port(port)
,_listen_sock(-1)
{}
TcpServer(const TcpServer&) = delete;
TcpServer* operator=(const TcpServer&) = delete;
public:
static TcpServer* GetInstance(int port)// 單例
{
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
if (_svr == nullptr)
{
pthread_mutex_lock(&mtx);
if (_svr == nullptr)// 為什么要兩個(gè)if? 原因:當(dāng)首個(gè)拿鎖者完成了對(duì)象創(chuàng)建,之后的線程都不會(huì)通過(guò)第一個(gè)if了,而這期間阻塞的線程開始喚醒,它們則需要靠第二個(gè)if語(yǔ)句來(lái)避免再次創(chuàng)建對(duì)象。
{
_svr = new TcpServer(port);
_svr -> InitServer();
}
pthread_mutex_unlock(&mtx);
}
return _svr;
}
void InitServer()
{
Socket(); // 創(chuàng)建
Bind(); // 綁定
Listen(); // 監(jiān)聽
LOG(INFO, "TcpServer Init Success");
}
void Socket() // 創(chuàng)建監(jiān)聽套接字
{
_listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (_listen_sock < 0)
{
LOG(FATAL, "socket error!");
exit(1);
}
int opt = 1;// 將 SO_REUSEADDR 設(shè)置為 1 將允許在端口上快速重啟套接字
setsockopt(_listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
LOG(INFO, "creat listen_sock success");
}
void Bind() // 綁定端口
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;

if (bind(_listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
LOG(FATAL, "bind error");
exit(2);
}
LOG(INFO, "port bind listen_sock success");
}
void Listen() // 監(jiān)聽
{
if (listen(_listen_sock, BACKLOG) < 0) // 聲明_listen_sock處于監(jiān)聽狀態(tài),并且最多允許有backlog個(gè)客戶端處于連接等待狀態(tài),如果接收到更多的連接請(qǐng)求就忽略
{
LOG(FATAL, "listen error");
exit(3);
}
LOG(INFO, "listen listen_sock success");
}
int Sock() // 獲取監(jiān)聽套接字fd
{
return _listen_sock;
}
~TcpServer()
{
if (_listen_sock >= 0)
{
close(_listen_sock);
}
}
};

// 單例對(duì)象指針初始化
TcpServer* TcpServer::_svr = nullptr;

任務(wù)類

// 任務(wù)類
class Task{
private:
int _sock; // 通信套接字
CallBack _handler; // 回調(diào)函數(shù)
public:
Task()
{}
~Task()
{}

Task(int sock) // accept建立連接成功產(chǎn)生的通信套接字sock
:_sock(sock)
{}

// 執(zhí)行任務(wù)
void ProcessOn()
{
_handler(_sock); //_handler對(duì)象的運(yùn)算符()已經(jīng)重裝,直接調(diào)用重載的()
}
};

初始化與啟動(dòng)HttpServer

這部分包含一個(gè)初始化服務(wù)器的方法InitServer()和一個(gè)啟動(dòng)服務(wù)器的方法Loop()。其中InitServer()函數(shù)注冊(cè)了一個(gè)信號(hào)處理函數(shù),忽略SIGPIPE信號(hào)(避免寫入崩潰)。而Loop()函數(shù)則通過(guò)調(diào)用TcpServer類的單例對(duì)象獲取監(jiān)聽套接字,然后通過(guò)accept()函數(shù)等待客戶端連接,每當(dāng)有客戶端連接進(jìn)來(lái),就創(chuàng)建一個(gè)線程來(lái)處理該客戶端的請(qǐng)求,并把任務(wù)放入線程池中。這里的Task是一個(gè)簡(jiǎn)單的封裝,它包含一個(gè)處理客戶端請(qǐng)求的成員函數(shù),該成員函數(shù)讀取客戶端請(qǐng)求,解析請(qǐng)求,然后調(diào)用CGI程序來(lái)執(zhí)行請(qǐng)求,最后將響應(yīng)發(fā)送給客戶端。

#define PORT 8081

class HttpServer
{
private:
int _port;// 端口號(hào)
public:
HttpServer(int port)
:_port(port)
{}
~HttpServer()
{}

// 初始化服務(wù)器
void InitServer()
{
signal(SIGPIPE, SIG_IGN); // 直接粗暴處理cgi程序?qū)懭牍艿罆r(shí)崩潰的情況,忽略SIGPIPE信號(hào),避免因?yàn)橐粋€(gè)被關(guān)閉的socket連接而使整個(gè)進(jìn)程終止
}

// 啟動(dòng)服務(wù)器
void Loop()
{
LOG(INFO, "loop begin");
TcpServer* tsvr = TcpServer::GetInstance(_port); // 獲取TCP服務(wù)器單例對(duì)象
int listen_sock = tsvr->Sock(); // 獲取單例對(duì)象的監(jiān)聽套接字
while(true)
{
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);// 跟客戶端建立連接
if (sock < 0)
{
continue;
}

// 打印客戶端信息
std::string client_ip = inet_ntoa(peer.sin_addr);
int client_port = ntohs(peer.sin_port);
LOG(INFO, "get a new link:[" + client_ip + ":" + std::to_string(client_port) + "]");

// 搞個(gè)線程池,代替下面簡(jiǎn)單的線程分離方案
// 構(gòu)建任務(wù)并放入任務(wù)隊(duì)列
Task task(sock);
ThreadPool::GetInstance()->PushTask(task);
}
}
};

HTTP請(qǐng)求結(jié)構(gòu)

將HTTP請(qǐng)求封裝成一個(gè)類,這個(gè)類當(dāng)中包括HTTP請(qǐng)求的內(nèi)容、HTTP請(qǐng)求的解析結(jié)果以及是否需要使用CGI模式的標(biāo)志位。后續(xù)處理請(qǐng)求時(shí)就可以定義一個(gè)HTTP請(qǐng)求類,讀取到的HTTP請(qǐng)求的數(shù)據(jù)就存儲(chǔ)在這個(gè)類當(dāng)中,解析HTTP請(qǐng)求后得到的數(shù)據(jù)也存儲(chǔ)在這個(gè)類當(dāng)中。

class HttpRequest{
public:
// Http請(qǐng)求內(nèi)容
std::string _request_line; // 請(qǐng)求行
std::vector _request_header; // 請(qǐng)求報(bào)頭
std::string _blank; // 空行
std::string _request_body; // 請(qǐng)求正文

// 存放解析結(jié)果
std::string _method; // 請(qǐng)求方法
std::string _uri; // URI
std::string _version; // 版本號(hào)
std::unordered_map _header_kv; // 請(qǐng)求報(bào)頭的內(nèi)容是以鍵值對(duì)的形式存在的,用hash保存
int _content_length; // 正文長(zhǎng)度
std::string _path; // 請(qǐng)求資源的路徑
std::string _query_string; // URI攜帶的參數(shù)

// 是否使用CGI
bool _cgi;
public:
HttpRequest()
:_content_length(0) // 默認(rèn)請(qǐng)求正文長(zhǎng)度為0
,_cgi(false) // 默認(rèn)不適用CGI模式
{}
~HttpRequest()
{}
};,>

HTTP響應(yīng)結(jié)構(gòu)

類似的,HTTP響應(yīng)也封裝成一個(gè)類,這個(gè)類當(dāng)中包括HTTP響應(yīng)的內(nèi)容以及構(gòu)建HTTP響應(yīng)所需要的數(shù)據(jù)。構(gòu)建響應(yīng)需要使用的數(shù)據(jù)就存儲(chǔ)在這個(gè)類當(dāng)中,構(gòu)建后得到的響應(yīng)內(nèi)容也存儲(chǔ)在這個(gè)類當(dāng)中。

class HttpResponse{
public:
// Http響應(yīng)內(nèi)容
std::string _status_line; // 狀態(tài)行
std::vector _response_header; // 響應(yīng)報(bào)頭
std::string _blank; // 空行
std::string _response_body; // 響應(yīng)正文(如果CGI為true(即Get帶_query_string或者Post),響應(yīng)正文才存在)

// 所需數(shù)據(jù)
int _status_code; // 狀態(tài)碼
int _fd; // 響應(yīng)文件的fd
int _size; // 響應(yīng)文件的大小
std::string _suffix; // 響應(yīng)文件的后綴
public:
HttpResponse()
:_blank(LINE_END)
,_status_code(OK)
,_fd(-1)
,_size(0)
{}
~HttpResponse()
{}
};

線程回調(diào)

該回調(diào)函數(shù)實(shí)際上是一個(gè)函數(shù)對(duì)象,其重載了圓括號(hào)運(yùn)算符“()”。當(dāng)該函數(shù)對(duì)象被調(diào)用時(shí),會(huì)傳入一個(gè)int類型的套接字描述符作為參數(shù),代表與客戶端建立的連接套接字。該函數(shù)對(duì)象內(nèi)部通過(guò)創(chuàng)建一個(gè)EndPoint對(duì)象來(lái)處理該客戶端發(fā)來(lái)的HTTP請(qǐng)求,包括讀取請(qǐng)求、處理請(qǐng)求、構(gòu)建響應(yīng)和發(fā)送響應(yīng)。處理完畢后,該連接套接字將被關(guān)閉,EndPoint對(duì)象也會(huì)被釋放。

class CallBack{
public:
CallBack()
{}
~CallBack()
{}
// 重載運(yùn)算符 ()
void operator()(int sock)
{
HandlerRequest(sock);
}
void HandlerRequest(int sock)
{
LOG(INFO, "HandlerRequest begin");
EndPoint* ep = new EndPoint(sock);
ep->RecvHttpRequest(); //讀取請(qǐng)求
if (!ep->IsStop())
{
LOG(INFO, "RecvHttpRequest Success");
ep->HandlerHttpRequest(); //處理請(qǐng)求
ep->BulidHttpResponse(); //構(gòu)建響應(yīng)
ep->SendHttpResponse(); //發(fā)送響應(yīng)
if (ep->IsStop())
{
LOG(WARNING, "SendHttpResponse Error, Stop Send HttpResponse");
}
}
else
{
LOG(WARNING, "RecvHttpRequest Error, Stop handler Response");
}
close(sock); //響應(yīng)完畢,關(guān)閉與該客戶端建立的套接字
delete ep;
LOG(INFO, "handler request end");
}
};

EndPoint類

圖片

EndPoint主體框架

EndPoint類中包含三個(gè)成員變量:

  • sock:表示與客戶端進(jìn)行通信的套接字。
  • http_request:表示客戶端發(fā)來(lái)的HTTP請(qǐng)求。
  • http_response:表示將會(huì)發(fā)送給客戶端的HTTP響應(yīng)。
  • _stop:是否異常停止本次處理

EndPoint類中主要包含四個(gè)成員函數(shù):

  • RecvHttpRequest:讀取客戶端發(fā)來(lái)的HTTP請(qǐng)求。
  • HandlerHttpRequest:處理客戶端發(fā)來(lái)的HTTP請(qǐng)求。
  • BuildHttpResponse:構(gòu)建將要發(fā)送給客戶端的HTTP響應(yīng)。
  • SendHttpResponse:發(fā)送HTTP響應(yīng)給客戶端。
//服務(wù)端EndPoint
class EndPoint{
private:
int _sock; //通信的套接字
HttpRequest _http_request; //HTTP請(qǐng)求
HttpResponse _http_response; //HTTP響應(yīng)
bool _stop; //是否停止本次處理
public:
EndPoint(int sock)
:_sock(sock)
{}
//讀取請(qǐng)求
void RecvHttpRequest();
//處理請(qǐng)求
void HandlerHttpRequest();
//構(gòu)建響應(yīng)
void BuildHttpResponse();
//發(fā)送響應(yīng)
void SendHttpResponse();
~EndPoint()
{}
};

讀取HTTP請(qǐng)求

讀取HTTP請(qǐng)求的同時(shí)可以對(duì)HTTP請(qǐng)求進(jìn)行解析,這里我們分為五個(gè)步驟,分別是讀取請(qǐng)求行、讀取請(qǐng)求報(bào)頭和空行、解析請(qǐng)求行、解析請(qǐng)求報(bào)頭、讀取請(qǐng)求正文。

// 讀取請(qǐng)求:如果請(qǐng)求行和請(qǐng)求報(bào)頭正常讀取,那先解析請(qǐng)求行和請(qǐng)求報(bào)頭,然后讀取請(qǐng)求正文
void RecvHttpRequest()
{
if (!RecvHttpRequestLine() && !RecvHttpRequestHeader())// 請(qǐng)求行與請(qǐng)求報(bào)頭讀取均正常讀取
{
ParseHttpRequestLine();
ParseHttpRequestHeader();
RecvHttpRequestBody();
}
}

處理HTTP請(qǐng)求

首先判斷請(qǐng)求方法是否為GET或POST,如果不是則返回錯(cuò)誤信息;然后判斷請(qǐng)求是GET還是POST,設(shè)置對(duì)應(yīng)的cgi、路徑和查詢字符串;接著拼接web根目錄和請(qǐng)求資源路徑,并判斷路徑是否以/結(jié)尾,如果是則拼接index.html;獲取請(qǐng)求資源文件的屬性信息,并根據(jù)屬性信息判斷是否需要使用CGI模式處理;獲取請(qǐng)求資源文件的后綴,進(jìn)行CGI或非CGI處理。

圖片

// 處理請(qǐng)求
void HandlerHttpRequest()
{
auto& code = _http_response._status_code;

//非法請(qǐng)求
if (_http_request._method != "GET" && _http_request._method != "POST")
{
LOG(WARNING, "method is not right");
code = BAD_REQUEST;
return;
}

// 判斷請(qǐng)求是get還是post,設(shè)置cgi,_path,_query_string
if (_http_request._method == "GET")
{
size_t pos = _http_request._uri.find('?');
if (pos != std::string::npos)// uri中攜帶參數(shù)
{
// 切割uri,得到客戶端請(qǐng)求資源的路徑和uri中攜帶的參數(shù)
Util::CutString(_http_request._uri, _http_request._path, _http_request._query_string, "?");
LOG(INFO, "GET方法分割路徑和參數(shù)");
_http_request._cgi = true;// 上傳了參數(shù),需要使用CGI模式
}
else // uri中沒(méi)有攜帶參數(shù)
{
_http_request._path = _http_request._uri;// uri即是客戶端請(qǐng)求資源的路徑
}
}
else if (_http_request._method == "POST")
{
_http_request._path = _http_request._uri;// uri即是客戶端請(qǐng)求資源的路徑
_http_request._cgi = true; // 上傳了參數(shù),需要使用CGI模式
}
else
{
// 只是為了代碼完整性
}

// 為請(qǐng)求資源路徑拼接web根目錄
std::string path = _http_request._path;
_http_request._path = WEB_ROOT;
_http_request._path += path;

// 請(qǐng)求資源路徑以/結(jié)尾,說(shuō)明請(qǐng)求的是一個(gè)目錄
if (_http_request._path[_http_request._path.size() - 1] == '/')
{
_http_request._path += HOME_PAGE; // 拼接上該目錄下的index.html
}
LOG(INFO, _http_request._path);

//獲取請(qǐng)求資源文件的屬性信息
struct stat st;
if (stat(_http_request._path.c_str(), &st) == 0) // 屬性信息獲取成功,說(shuō)明該資源存在
{
if (S_ISDIR(st.st_mode)) // 該資源是一個(gè)目錄
{
_http_request._path += "/"; // 以/結(jié)尾的目錄前面已經(jīng)處理過(guò)了,這里處理不是以/結(jié)尾的目錄情況,需要拼接/
_http_request._path += HOME_PAGE; // 拼接上該目錄下的index.html
stat(_http_request._path.c_str(), &st); // 重新獲取資源文件的屬性信息
}
else if (st.st_mode&S_IXUSR||st.st_mode&S_IXGRP||st.st_mode&S_IXOTH) // 該資源是一個(gè)可執(zhí)行程序
{
_http_request._cgi = true; //需要使用CGI模式
}
_http_response._size = st.st_size; //設(shè)置請(qǐng)求資源文件的大小
}
else // 屬性信息獲取失敗,可以認(rèn)為該資源不存在
{
LOG(WARNING, _http_request._path + "NOT_FOUND");
code = NOT_FOUND;
return;
}

// 獲取請(qǐng)求資源文件的后綴
size_t pos = _http_request._path.rfind('.');
if (pos == std::string::npos)
{
_http_response._suffix = ".html";
}
else
{
_http_response._suffix = _http_request._path.substr(pos);// 把'.'也帶上
}

// 進(jìn)行CGI或非CGI處理
// CGI為true就三種情況,GET方法的uri帶參(_query_string),或者POST方法,又或者請(qǐng)求的資源是一個(gè)可執(zhí)行程序
if (_http_request._cgi == true)
{
code = ProcessCgi(); // 以CGI的方式進(jìn)行處理
}
else
{
code = ProcessNonCgi(); // 簡(jiǎn)單的網(wǎng)頁(yè)返回,返回靜態(tài)網(wǎng)頁(yè)
}
}

CGI處理

CGI處理時(shí)需要?jiǎng)?chuàng)建子進(jìn)程進(jìn)行進(jìn)程程序替換,但是在創(chuàng)建子進(jìn)程之前需要先創(chuàng)建兩個(gè)匿名管道。這里站在父進(jìn)程角度對(duì)這兩個(gè)管道進(jìn)行命名,父進(jìn)程用于讀取數(shù)據(jù)的管道叫做input,父進(jìn)程用于寫入數(shù)據(jù)的管道叫做output。

圖片

創(chuàng)建匿名管道并創(chuàng)建子進(jìn)程后,需要父子進(jìn)程各自關(guān)閉兩個(gè)管道對(duì)應(yīng)的讀寫端:

  • 對(duì)于父進(jìn)程來(lái)說(shuō),input管道是用來(lái)讀數(shù)據(jù)的,因此父進(jìn)程需要保留input[0]關(guān)閉input[1],而output管道是用來(lái)寫數(shù)據(jù)的,因此父進(jìn)程需要保留output[1]關(guān)閉output[0]。
  • 對(duì)于子進(jìn)程來(lái)說(shuō),input管道是用來(lái)寫數(shù)據(jù)的,因此子進(jìn)程需要保留input[1]關(guān)閉input[0],而output管道是用來(lái)讀數(shù)據(jù)的,因此子進(jìn)程需要保留output[0]關(guān)閉output[1]。

此時(shí)父子進(jìn)程之間的通信信道已經(jīng)建立好了,但為了讓替換后的CGI程序從標(biāo)準(zhǔn)輸入讀取數(shù)據(jù)等價(jià)于從管道讀取數(shù)據(jù),向標(biāo)準(zhǔn)輸出寫入數(shù)據(jù)等價(jià)于向管道寫入數(shù)據(jù),因此在子進(jìn)程進(jìn)行進(jìn)程程序替換之前,還需要對(duì)子進(jìn)程進(jìn)行重定向。

假設(shè)子進(jìn)程保留的input[1]和output[0]對(duì)應(yīng)的文件描述符分別是3和4,那么子進(jìn)程對(duì)應(yīng)的文件描述符表的指向大致如下:

圖片

現(xiàn)在我們要做的就是將子進(jìn)程的標(biāo)準(zhǔn)輸入重定向到output管道,將子進(jìn)程的標(biāo)準(zhǔn)輸出重定向到input管道,也就是讓子進(jìn)程的0號(hào)文件描述符指向output管道,讓子進(jìn)程的1號(hào)文件描述符指向input管道。

圖片

此外,在子進(jìn)程進(jìn)行進(jìn)程程序替換之前,還需要進(jìn)行各種參數(shù)的傳遞:

  • 首先需要將請(qǐng)求方法通過(guò)putenv函數(shù)導(dǎo)入環(huán)境變量,以供CGI程序判斷應(yīng)該以哪種方式讀取父進(jìn)程傳遞過(guò)來(lái)的參數(shù)。
  • 如果請(qǐng)求方法為GET方法,則需要將URL中攜帶的參數(shù)通過(guò)導(dǎo)入環(huán)境變量的方式傳遞給CGI程序。
  • 如果請(qǐng)求方法為POST方法,則需要將請(qǐng)求正文的長(zhǎng)度通過(guò)導(dǎo)入環(huán)境變量的方式傳遞給CGI程序,以供CGI程序判斷應(yīng)該從管道讀取多少個(gè)參數(shù)。

此時(shí)子進(jìn)程就可以進(jìn)行進(jìn)程程序替換了,而父進(jìn)程需要做如下工作:

  • 如果請(qǐng)求方法為POST方法,則父進(jìn)程需要將請(qǐng)求正文中的參數(shù)寫入管道中,以供被替換后的CGI程序進(jìn)行讀取。
  • 然后父進(jìn)程要做的就是不斷調(diào)用read函數(shù),從管道中讀取CGI程序?qū)懭氲奶幚斫Y(jié)果,并將其保存到HTTP響應(yīng)類的response_body當(dāng)中。
  • 管道中的數(shù)據(jù)讀取完畢后,父進(jìn)程需要調(diào)用waitpid函數(shù)等待CGI程序退出,并關(guān)閉兩個(gè)管道對(duì)應(yīng)的文件描述符,防止文件描述符泄露。
// CGI = true,處理cgi
int ProcessCgi()
{
int code = OK; // 要返回的狀態(tài)碼,默認(rèn)設(shè)置為200

auto& bin = _http_request._path; // 需要執(zhí)行的CGI程序
auto& method = _http_request._method; // 請(qǐng)求方法

//需要傳遞給CGI程序的參數(shù)
auto& query_string = _http_request._query_string; // GET
auto& request_body = _http_request._request_body; // POST

int content_length = _http_request._content_length; // 請(qǐng)求正文的長(zhǎng)度
auto& response_body = _http_response._response_body; // CGI程序的處理結(jié)果放到響應(yīng)正文當(dāng)中

// 1、創(chuàng)建兩個(gè)匿名管道(管道命名站在父進(jìn)程角度)
// 在調(diào)用 pipe 函數(shù)創(chuàng)建管道成功后,pipefd[0] 用于讀取數(shù)據(jù),pipefd[1] 用于寫入數(shù)據(jù)。
// 1.1 創(chuàng)建從子進(jìn)程到父進(jìn)程的通信信道
int input[2];
if(pipe(input) < 0){ // 管道創(chuàng)建失敗,pipe()返回-1
LOG(ERROR, "pipe input error!");
code = INTERNAL_SERVER_ERROR;
return code;
}
// 1.2 創(chuàng)建從父進(jìn)程到子進(jìn)程的通信信道
int output[2];
if(pipe(output) < 0){ // 管道創(chuàng)建失敗,pipe()返回-1
LOG(ERROR, "pipe output error!");
code = INTERNAL_SERVER_ERROR;
return code;
}

//2、創(chuàng)建子進(jìn)程
pid_t pid = fork();
if(pid == 0){ //child
// 子進(jìn)程關(guān)閉兩個(gè)管道對(duì)應(yīng)的讀寫端
close(input[0]);
close(output[1]);

//將請(qǐng)求方法通過(guò)環(huán)境變量傳參
std::string method_env = "METHOD=";
method_env += method;
putenv((char*)method_env.c_str());

if(method == "GET"){ //將query_string通過(guò)環(huán)境變量傳參
std::string query_env = "QUERY_STRING=";
query_env += query_string;
putenv((char*)query_env.c_str());
LOG(INFO, "GET Method, Add Query_String env");
}
else if(method == "POST"){ //將正文長(zhǎng)度通過(guò)環(huán)境變量傳參
std::string content_length_env = "CONTENT_LENGTH=";
content_length_env += std::to_string(content_length);
putenv((char*)content_length_env.c_str());
LOG(INFO, "POST Method, Add Content_Length env");
}
else{
//Do Nothing
}

//3、將子進(jìn)程的標(biāo)準(zhǔn)輸入輸出進(jìn)行重定向,子進(jìn)程會(huì)繼承了父進(jìn)程的所有文件描述符
dup2(output[0], 0); //標(biāo)準(zhǔn)輸入重定向到管道的輸入
dup2(input[1], 1); //標(biāo)準(zhǔn)輸出重定向到管道的輸出

//4、將子進(jìn)程替換為對(duì)應(yīng)的CGI程序,代碼、數(shù)據(jù)全部替換掉
execl(bin.c_str(), bin.c_str(), nullptr);
exit(1); // 替換失敗則exit(1)
}
else if(pid < 0){ //創(chuàng)建子進(jìn)程失敗,則返回對(duì)應(yīng)的錯(cuò)誤碼
LOG(ERROR, "fork error!");
code = INTERNAL_SERVER_ERROR;
return code;
}
else{ //father
//父進(jìn)程關(guān)閉兩個(gè)管道對(duì)應(yīng)的讀寫端
close(input[1]);
close(output[0]);

if(method == "POST") // 將正文中的參數(shù)通過(guò)管道傳遞給CGI程序
{
const char* start = request_body.c_str();
int total = 0;
int size = 0;
while(total < content_length && (size = write(output[1], start + total, request_body.size() - total)) > 0)
{
total += size;
}
}

// 讀取CGI程序的處理結(jié)果
char ch = 0;
while(read(input[0], &ch, 1) > 0)// 不會(huì)一直讀,當(dāng)另一端關(guān)閉后會(huì)繼續(xù)往下執(zhí)行
{
response_body.push_back(ch);
}

// 等待子進(jìn)程(CGI程序)退出
// status 保存退出狀態(tài)
int status = 0;
pid_t ret = waitpid(pid, &status, 0);
if(ret == pid){
if(WIFEXITED(status)){ // 子進(jìn)程正常退出
if(WEXITSTATUS(status) == 0){ // 子進(jìn)程退出碼結(jié)果正確
LOG(INFO, "CGI program exits normally with correct results");
code = OK;
}
else{
LOG(INFO, "CGI program exits normally with incorrect results");
code = BAD_REQUEST;
}
}
else{
LOG(INFO, "CGI program exits abnormally");
code = INTERNAL_SERVER_ERROR;
}
}

//關(guān)閉兩個(gè)管道對(duì)應(yīng)的文件描述符
close(input[0]);
close(output[1]);
}
return code; //返回狀態(tài)碼
}

非CGI處理

非CGI處理時(shí)只需要將客戶端請(qǐng)求的資源構(gòu)建成HTTP響應(yīng)發(fā)送給客戶端即可,理論上這里要做的就是打開目標(biāo)文件,將文件中的內(nèi)容讀取到HTTP響應(yīng)類的response_body中,以供后續(xù)發(fā)送HTTP響應(yīng)時(shí)進(jìn)行發(fā)送即可,但這種做法還可以優(yōu)化。

因?yàn)镠TTP響應(yīng)類的response_body屬于用戶層的緩沖區(qū),而目標(biāo)文件是存儲(chǔ)在服務(wù)器的磁盤上的,按照這種方式需要先將文件內(nèi)容讀取到內(nèi)核層緩沖區(qū),再由操作系統(tǒng)將其拷貝到用戶層緩沖區(qū),發(fā)送響應(yīng)正文的時(shí)候又需要先將其拷貝到內(nèi)核層緩沖區(qū),再由操作系統(tǒng)將其發(fā)送給對(duì)應(yīng)的網(wǎng)卡進(jìn)行發(fā)送。我們完全可以調(diào)用sendfile函數(shù)直接將磁盤當(dāng)中的目標(biāo)文件內(nèi)容讀取到內(nèi)核,再由內(nèi)核將其發(fā)送給對(duì)應(yīng)的網(wǎng)卡進(jìn)行發(fā)送。

sendfile函數(shù)是一個(gè)系統(tǒng)調(diào)用函數(shù),用于將一個(gè)文件描述符指向的文件內(nèi)容直接發(fā)送給另一個(gè)文件描述符指向的套接字,從而實(shí)現(xiàn)了零拷貝(Zero Copy)技術(shù)。這種技術(shù)避免了數(shù)據(jù)在用戶態(tài)和內(nèi)核態(tài)之間的多次拷貝,從而提高了數(shù)據(jù)傳輸效率。

sendfile函數(shù)的使用場(chǎng)景通常是在Web服務(wù)器中,用于將靜態(tài)文件直接發(fā)送給客戶端瀏覽器,從而避免了將文件內(nèi)容復(fù)制到用戶空間的過(guò)程。在Linux系統(tǒng)中,sendfile函數(shù)的原型為:

#include
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

其中,out_fd表示目標(biāo)文件描述符,in_fd表示源文件描述符,offset表示源文件偏移量,count表示要發(fā)送的字節(jié)數(shù)。函數(shù)返回值表示成功發(fā)送的字節(jié)數(shù),如果返回-1則表示出現(xiàn)了錯(cuò)誤。

// CGI = false
int ProcessNonCgi()
{
// 打開客戶端請(qǐng)求的資源文件,以供后續(xù)發(fā)送
_http_response._fd = open(_http_request._path.c_str(), O_RDONLY);
if(_http_response._fd >= 0){ // 打開文件成功
return OK;
}
return INTERNAL_SERVER_ERROR; // 打開文件失敗
}

構(gòu)建HTTP響應(yīng)

構(gòu)建 HTTP 響應(yīng)報(bào)文,首先根據(jù)響應(yīng)的狀態(tài)碼構(gòu)建狀態(tài)行(包含 HTTP 版本、狀態(tài)碼和狀態(tài)碼描述),然后根據(jù)狀態(tài)碼分別構(gòu)建不同的響應(yīng)報(bào)頭和響應(yīng)正文。如果狀態(tài)碼為 200 OK,則調(diào)用 BuildOkResponse() 函數(shù)構(gòu)建成功的響應(yīng)報(bào)頭和響應(yīng)正文;如果狀態(tài)碼為 404 NOT FOUND、400 BAD REQUEST 或 500 INTERNAL SERVER ERROR,則根據(jù)不同的狀態(tài)碼構(gòu)建相應(yīng)的錯(cuò)誤響應(yīng)報(bào)頭和響應(yīng)正文,并調(diào)用 HandlerError() 函數(shù)處理錯(cuò)誤。

// 構(gòu)建響應(yīng)
void BulidHttpResponse()
{
int code = _http_response._status_code;
//構(gòu)建狀態(tài)行
auto& status_line = _http_response._status_line;
status_line += HTTP_VERSION;
status_line += " ";
status_line += std::to_string(code);
status_line += " ";
status_line += CodeToDesc(code); //根據(jù)狀態(tài)碼獲取狀態(tài)碼描述
status_line += LINE_END;

//構(gòu)建響應(yīng)報(bào)頭
std::string path = WEB_ROOT;
path += "/";
switch(code){
case OK:
BuildOkResponse();
break;
case NOT_FOUND:
path += PAGE_404;
HandlerError(path);
break;
case BAD_REQUEST:
path += PAGE_400;
HandlerError(path);
break;
case INTERNAL_SERVER_ERROR:
path += PAGE_500;
HandlerError(path);
break;
default:
break;
}
}

發(fā)送HTTP響應(yīng)

發(fā)送HTTP響應(yīng)的步驟如下:

  • 調(diào)用send函數(shù),依次發(fā)送狀態(tài)行、響應(yīng)報(bào)頭和空行。
  • 發(fā)送響應(yīng)正文時(shí)需要判斷本次請(qǐng)求的處理方式,如果本次請(qǐng)求是以CGI方式成功處理的,那么待發(fā)送的響應(yīng)正文是保存在HTTP響應(yīng)類的response_body中的,此時(shí)調(diào)用send函數(shù)進(jìn)行發(fā)送即可。
  • 如果本次請(qǐng)求是以非CGI方式處理或在處理過(guò)程中出錯(cuò)的,那么待發(fā)送的資源文件或錯(cuò)誤頁(yè)面文件對(duì)應(yīng)的文件描述符是保存在HTTP響應(yīng)類的fd中的,此時(shí)調(diào)用sendfile進(jìn)行發(fā)送即可,發(fā)送后關(guān)閉對(duì)應(yīng)的文件描述符。
// 發(fā)送響應(yīng)
bool SendHttpResponse()
{
//發(fā)送狀態(tài)行
if(send(_sock, _http_response._status_line.c_str(), _http_response._status_line.size(), 0) <= 0)
{
_stop = true; //發(fā)送失敗,設(shè)置_stop
}
//發(fā)送響應(yīng)報(bào)頭
if(!_stop){
for(auto& iter : _http_response._response_header)
{
if(send(_sock, iter.c_str(), iter.size(), 0) <= 0)
{
_stop = true; //發(fā)送失敗,設(shè)置_stop
break;
}
}
}
//發(fā)送空行
if(!_stop)
{
if(send(_sock, _http_response._blank.c_str(), _http_response._blank.size(), 0) <= 0)
{
_stop = true; //發(fā)送失敗,設(shè)置_stop
}
}
//發(fā)送響應(yīng)正文
if(_http_request._cgi)
{
if(!_stop)
{
auto& response_body = _http_response._response_body;
const char* start = response_body.c_str();
size_t size = 0;
size_t total = 0;
while(total < response_body.size()&&(size = send(_sock, start + total, response_body.size() - total, 0)) > 0){
total += size;
}
}
}
else
{
if(!_stop)
{
// sendfile:這是一個(gè)系統(tǒng)調(diào)用,用于高效地從文件傳輸數(shù)據(jù)到套接字中。它避免了在內(nèi)核空間和用戶空間之間復(fù)制數(shù)據(jù)的需求,從而實(shí)現(xiàn)更快的數(shù)據(jù)傳輸。
if(sendfile(_sock, _http_response._fd, nullptr, _http_response._size) <= 0)
{
_stop = true; //發(fā)送失敗,設(shè)置_stop
}
}
//關(guān)閉請(qǐng)求的資源文件
close(_http_response._fd);
}
return _stop;
}

接入線程池

當(dāng)前多線程版服務(wù)器存在的問(wèn)題:

  • 每當(dāng)獲取到新連接時(shí),服務(wù)器主線程都會(huì)重新為該客戶端創(chuàng)建為其提供服務(wù)的新線程,而當(dāng)服務(wù)結(jié)束后又會(huì)將該新線程銷毀,這樣做不僅麻煩,而且效率低下。
  • 如果同時(shí)有大量的客戶端連接請(qǐng)求,此時(shí)服務(wù)器就要為每一個(gè)客戶端創(chuàng)建對(duì)應(yīng)的服務(wù)線程,而計(jì)算機(jī)中的線程越多,CPU壓力就越大,因?yàn)镃PU要不斷在這些線程之間來(lái)回切換。此外,一旦線程過(guò)多,每一個(gè)線程再次被調(diào)度的周期就變長(zhǎng)了,而線程是為客戶端提供服務(wù)的,線程被調(diào)度的周期變長(zhǎng),客戶端也就遲遲得不到應(yīng)答。

考慮接入線程池簡(jiǎn)單優(yōu)化下(其實(shí)也可以直接上epoll)

  • 在服務(wù)器端預(yù)先創(chuàng)建一批線程和一個(gè)任務(wù)隊(duì)列,每當(dāng)獲取到一個(gè)新連接時(shí)就將其封裝成一個(gè)任務(wù)對(duì)象放到任務(wù)隊(duì)列當(dāng)中。
  • 線程池中的若干線程就不斷從任務(wù)隊(duì)列中獲取任務(wù)進(jìn)行處理,如果任務(wù)隊(duì)列當(dāng)中沒(méi)有任務(wù)則線程進(jìn)入休眠狀態(tài),當(dāng)有新任務(wù)時(shí)再喚醒線程進(jìn)行任務(wù)處理。
#define NUM 6

//線程池
class ThreadPool{
private:
std::queue _task_queue; //任務(wù)隊(duì)列
int _num; //線程池中線程的個(gè)數(shù)
pthread_mutex_t _mutex; //互斥鎖
pthread_cond_t _cond; //條件變量
static ThreadPool* _inst; //指向單例對(duì)象的static指針
private:
//構(gòu)造函數(shù)私有
ThreadPool(int num = NUM)
:_num(num)
{
//初始化互斥鎖和條件變量
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_cond, nullptr);
}
// 刪除拷貝構(gòu)造函數(shù)(防拷貝)
ThreadPool(const ThreadPool&)=delete;

//判斷任務(wù)隊(duì)列是否為空
bool IsEmpty()
{
return _task_queue.empty();
}

//任務(wù)隊(duì)列加鎖
void LockQueue()
{
pthread_mutex_lock(&_mutex);
}

//任務(wù)隊(duì)列解鎖
void UnLockQueue()
{
pthread_mutex_unlock(&_mutex);
}

//讓線程在條件變量下進(jìn)行等待
void ThreadWait()
{
pthread_cond_wait(&_cond, &_mutex);
}

//喚醒在條件變量下等待的一個(gè)線程
void ThreadWakeUp()
{
pthread_cond_signal(&_cond);
}

public:
//獲取單例對(duì)象
static ThreadPool* GetInstance()
{
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; //定義靜態(tài)的互斥鎖
//雙檢查加鎖
if(_inst == nullptr){
pthread_mutex_lock(&mtx); //加鎖
if(_inst == nullptr){
//創(chuàng)建單例線程池對(duì)象并初始化
_inst = new ThreadPool();
_inst->InitThreadPool();
}
pthread_mutex_unlock(&mtx); //解鎖
}
return _inst; //返回單例對(duì)象
}

//線程的執(zhí)行例程
static void* ThreadRoutine(void* arg)
{
pthread_detach(pthread_self()); //線程分離
ThreadPool* tp = (ThreadPool*)arg;
while(true){
tp->LockQueue(); //加鎖
while(tp->IsEmpty()){
//任務(wù)隊(duì)列為空,線程進(jìn)行wait
tp->ThreadWait();
}
Task task;
tp->PopTask(task); //獲取任務(wù)
tp->UnLockQueue(); //解鎖

task.ProcessOn(); //處理任務(wù)
}
}

//初始化線程池
bool InitThreadPool()
{
//創(chuàng)建線程池中的若干線程
pthread_t tid;
for(int i = 0;i < _num;i++){
if(pthread_create(&tid, nullptr, ThreadRoutine, this) != 0){
LOG(FATAL, "create thread pool error!");
return false;
}
}
LOG(INFO, "create thread pool success");
return true;
}

//將任務(wù)放入任務(wù)隊(duì)列
void PushTask(const Task& task)
{
LockQueue(); //加鎖
_task_queue.push(task); //將任務(wù)推入任務(wù)隊(duì)列
UnLockQueue(); //解鎖
ThreadWakeUp(); //喚醒一個(gè)線程進(jìn)行任務(wù)處理
}

//從任務(wù)隊(duì)列中拿任務(wù)
void PopTask(Task& task)
{
//獲取任務(wù)
task = _task_queue.front();
_task_queue.pop();
}

~ThreadPool()
{
//釋放互斥鎖和條件變量
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
};
//單例對(duì)象指針初始化為nullptr
ThreadPool* ThreadPool::_inst = nullptr;

簡(jiǎn)單測(cè)試

默認(rèn)頁(yè)面測(cè)試:

圖片

帶query_string,CGI傳參測(cè)試:

圖片

項(xiàng)目擴(kuò)展

當(dāng)前項(xiàng)目的重點(diǎn)在于HTTP服務(wù)器后端的處理邏輯,主要完成的是GET和POST請(qǐng)求方法,以及CGI機(jī)制的搭建。還可以進(jìn)行不少擴(kuò)展,比如:

  • 當(dāng)前項(xiàng)目編寫的是HTTP1.0版本的服務(wù)器,每次連接都只會(huì)對(duì)一個(gè)請(qǐng)求進(jìn)行處理,當(dāng)服務(wù)器對(duì)客戶端的請(qǐng)求處理完畢并收到客戶端的應(yīng)答后,就會(huì)直接斷開連接??梢詫⑵鋽U(kuò)展為HTTP1.1版本,讓服務(wù)器支持長(zhǎng)連接,即通過(guò)一條連接可以對(duì)多個(gè)請(qǐng)求進(jìn)行處理,避免重復(fù)建立連接(涉及連接管理)。
  • 當(dāng)前項(xiàng)目雖然在后端接入了線程池,但是效果有限,可以將線程池?fù)Q成epoll版本,讓服務(wù)器的IO變得更高效。
  • 可以給當(dāng)前的HTTP服務(wù)器新增代理功能,也就是可以替代客戶端去訪問(wèn)某種服務(wù),然后將訪問(wèn)結(jié)果再返回給客戶端(比如課題中的數(shù)據(jù)備份、數(shù)據(jù)計(jì)算等等)。
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 服務(wù)器
    +關(guān)注

    關(guān)注

    13

    文章

    9796

    瀏覽量

    88016
  • HTTP
    +關(guān)注

    關(guān)注

    0

    文章

    525

    瀏覽量

    33544
  • TCP協(xié)議
    +關(guān)注

    關(guān)注

    1

    文章

    101

    瀏覽量

    12464
  • WebServer
    +關(guān)注

    關(guān)注

    0

    文章

    11

    瀏覽量

    3274
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    HTTP、TCP、QUIC協(xié)議詳解

    HTTP 3.0 是 HTTP 協(xié)議的第三個(gè)主要版本,前兩個(gè)分別是 HTTP 1.0 和 HTTP 2.0 ,但其實(shí)
    發(fā)表于 07-25 11:58 ?1893次閱讀

    HTTP協(xié)議簡(jiǎn)介

    HTTP協(xié)議簡(jiǎn)介HTTP即Hyper Text Transfer Protocol (超文本傳輸協(xié)議),是一種基于TCP/IP通信
    發(fā)表于 12-15 06:01

    怎樣使用基于TCP的socket編程去實(shí)現(xiàn)WebServer功能呢

    WebServer是什么?怎樣使用基于TCP的socket編程去實(shí)現(xiàn)WebServer功能呢?
    發(fā)表于 02-22 07:03

    簡(jiǎn)述基于HTTP協(xié)議實(shí)現(xiàn)WebClient軟件包的工作原理

    工作原理WebClient 軟件包主要用于在嵌入式設(shè)備上實(shí)現(xiàn) HTTP 協(xié)議,軟件包的主要工作原理基于 HTTP 協(xié)議
    發(fā)表于 08-15 14:27

    TCP-IP詳解_卷3_TCP事務(wù)協(xié)議,HTTP,NNTP

    TCP-IP詳解_卷3_TCP事務(wù)協(xié)議,HTTP,NNTP和UNIX域協(xié)議
    發(fā)表于 03-24 22:42 ?39次下載

    TCP-IP詳解卷3:TCP事務(wù)協(xié)議HTTP,NNTP和UNI

    TCP-IP詳解卷3:TCP事務(wù)協(xié)議HTTP,NNTP和UNIX域協(xié)議,個(gè)人收集整理了很久的資料,大家根據(jù)自己情況,有選擇性的下載吧~
    發(fā)表于 10-27 14:04 ?0次下載

    tcphttp的區(qū)別在哪里

    我一直以為HttpTcp是兩種不同的,但是地位對(duì)等的協(xié)議,雖然知道TCP是傳輸層,而http是應(yīng)用層今天學(xué)習(xí)了下,知道了
    發(fā)表于 12-08 12:32 ?2.7w次閱讀
    <b class='flag-5'>tcp</b>和<b class='flag-5'>http</b>的區(qū)別在哪里

    TCP/IP協(xié)議典型的優(yōu)化原則和方法

    嵌入式TCP/IP協(xié)議實(shí)現(xiàn)通常采用Linux中的TCP/IP網(wǎng)絡(luò)結(jié)構(gòu)層次。TCP/IP協(xié)議
    發(fā)表于 03-13 15:12 ?2264次閱讀
    <b class='flag-5'>TCP</b>/IP<b class='flag-5'>協(xié)議</b>典型的優(yōu)化原則和方法

    為了速度犧牲安全,下一代HTTP底層協(xié)議或?qū)⒎艞?b class='flag-5'>TCP協(xié)議

    據(jù)報(bào)道,國(guó)際互聯(lián)網(wǎng)工程任務(wù)組(Internet Engineering Task Force, IETF)將于近日商討下一代HTTP底層協(xié)議,可能不再使用已經(jīng)沿用多年的TCP協(xié)議,而有
    的頭像 發(fā)表于 08-06 15:31 ?7678次閱讀

    WIFI模塊通過(guò)TCP協(xié)議發(fā)送HTTP的詳細(xì)資料說(shuō)明

    本文檔的主要內(nèi)容詳細(xì)介紹的是WIFI模塊通過(guò)TCP協(xié)議發(fā)送HTTP的詳細(xì)資料說(shuō)明。
    發(fā)表于 08-14 10:45 ?42次下載

    基于FPGA的TCP/IP協(xié)議實(shí)現(xiàn)

    基于FPGA的TCP/IP協(xié)議實(shí)現(xiàn)說(shuō)明。
    發(fā)表于 04-28 11:19 ?53次下載

    通信協(xié)議中的HTTPTCP、UDP你了解多少(上)

    TCP HTTP UDP: 都是通信協(xié)議,也就是通信時(shí)所遵守的規(guī)則,只有雙方按照這個(gè)規(guī)則“說(shuō)話”,對(duì)方才能理解或?yàn)橹?wù)。
    的頭像 發(fā)表于 02-13 14:19 ?1204次閱讀
    通信<b class='flag-5'>協(xié)議</b>中的<b class='flag-5'>HTTP</b>、<b class='flag-5'>TCP</b>、UDP你了解多少(上)

    如何理解HTTP協(xié)議是無(wú)狀態(tài)的

    1、HTTP 協(xié)議TCP/IP 協(xié)議的關(guān)系 HTTP 的長(zhǎng)連接和短連接本質(zhì)上是 TCP 長(zhǎng)連
    的頭像 發(fā)表于 11-11 15:46 ?3452次閱讀
    如何理解<b class='flag-5'>HTTP</b><b class='flag-5'>協(xié)議</b>是無(wú)狀態(tài)的

    關(guān)于TCP、HTTP的知識(shí)科普

    要說(shuō)http就繞不開tcp,TCP協(xié)議對(duì)應(yīng)于傳輸層,而HTTP協(xié)議對(duì)應(yīng)于應(yīng)用層,從本質(zhì)上來(lái)說(shuō),二
    的頭像 發(fā)表于 12-21 09:31 ?1341次閱讀
    關(guān)于<b class='flag-5'>TCP</b>、<b class='flag-5'>HTTP</b>的知識(shí)科普

    你了解清楚了嘛-TCP、HTTP、MQTT協(xié)議

    TCP、HTTP 和 MQTT 是三種不同層級(jí)和用途的協(xié)議是進(jìn)行設(shè)備互聯(lián)和傳送數(shù)據(jù)的重要組成部分;TCP適用高可靠性傳送,HTTP適用Web
    的頭像 發(fā)表于 07-11 11:34 ?4179次閱讀
    你了解清楚了嘛-<b class='flag-5'>TCP</b>、<b class='flag-5'>HTTP</b>、MQTT<b class='flag-5'>協(xié)議</b>