日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢(xún)
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
利用多線程和C++實(shí)現(xiàn)一個(gè)簡(jiǎn)單的HTTP服務(wù)器

前言:服務(wù)器是現(xiàn)代軟件不可或缺的一部分,而服務(wù)器的技術(shù)也是非常復(fù)雜和有趣的方向。隨著操作系統(tǒng)不斷地發(fā)展,服務(wù)器的底層架構(gòu)也在不斷變化。本文介紹一種使用 C++ 和 多線程實(shí)現(xiàn)的簡(jiǎn)單 HTTP 服務(wù)器。

創(chuàng)新互聯(lián)公司專(zhuān)注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于成都網(wǎng)站制作、網(wǎng)站建設(shè)、淶水網(wǎng)絡(luò)推廣、小程序開(kāi)發(fā)、淶水網(wǎng)絡(luò)營(yíng)銷(xiāo)、淶水企業(yè)策劃、淶水品牌公關(guān)、搜索引擎seo、人物專(zhuān)訪、企業(yè)宣傳片、企業(yè)代運(yùn)營(yíng)等,從售前售中售后,我們都將竭誠(chéng)為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);創(chuàng)新互聯(lián)公司為所有大學(xué)生創(chuàng)業(yè)者提供淶水建站搭建服務(wù),24小時(shí)服務(wù)熱線:18980820575,官方網(wǎng)址:www.cdcxhl.com

首先我們先來(lái)看一下如何創(chuàng)建一個(gè)服務(wù)器。

int main() 
{
int server_fd;
struct sockaddr_in server_addr;
server_fd = socket(AF_INET, SOCK_STREAM, 0);
int on = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (server_fd < 0) {
perror("create socket error");
goto EXIT;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8888);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind address error");
goto EXIT;
}
if (listen(server_fd, 511) < 0) {
perror("listen port error");
goto EXIT;
}
while(1) {
int connfd = accept(server_fd, nullptr, nullptr);
if (connfd < 0)
{
perror("accept error");
continue;
}
// 處理
}
close(server_fd);
return 0;
EXIT:
exit(1);
}

我們看到根據(jù)操作系統(tǒng)提供的 API,創(chuàng)建一個(gè) TCP 服務(wù)器非常簡(jiǎn)單 ,只需要調(diào)用幾個(gè)函數(shù)就行。最后進(jìn)程會(huì)阻塞在 accept 等待連接的到來(lái),我們?cè)谝粋€(gè)死循環(huán)中串行地處理每個(gè)請(qǐng)求。顯然,這樣的效率肯定非常低,因?yàn)槿绻覀兪褂脗鹘y(tǒng)的 read / write 函數(shù)的話,它是會(huì)引起進(jìn)程阻塞的,這樣就會(huì)導(dǎo)致多個(gè)請(qǐng)求需要排隊(duì)進(jìn)行處理。我們?cè)诖嘶A(chǔ)上利用多線程提高一下效率。

std::thread threads[MAX_THREAD];
std::condition_variable condition_variable;
std::deque requests;
std::mutex mutex;
for (int i = 0; i < MAX_THREAD; i++) {
threads[i] = std::thread(worker, &mutex, &condition_variable, &requests);
}

多線程就會(huì)涉及到并發(fā) / 同步的問(wèn)題,所以需要使用互斥變量和條件變量來(lái)處理這些問(wèn)題。上面的代碼創(chuàng)建了幾個(gè)線程,然后在每個(gè)線程中執(zhí)行 worker 函數(shù)來(lái)處理請(qǐng)求,除此之外,用 requests 變量來(lái)表示請(qǐng)求隊(duì)列,該變量會(huì)由主線程和子線程一起訪問(wèn)。具體是由主線程生產(chǎn)任務(wù),子線程消費(fèi)。在了解子線程邏輯之前先看看主線程代碼的改動(dòng)。

while(1) {
int connfd = accept(server_fd, nullptr, nullptr);
if (connfd < 0)
{
perror("accept error");
continue;
}
{
std::lock_guard lock(mutex);
requests.push_back(connfd);
condition_variable.notify_one();
}
}

我們看到當(dāng)主線程收到請(qǐng)求時(shí),自己不處理,而是添加到請(qǐng)求隊(duì)列讓子線程處理,因?yàn)樽泳€程沒(méi)有任務(wù)處理時(shí)會(huì)自我阻塞,所以主線程需要喚醒一個(gè)線程來(lái)處理新的請(qǐng)求。接下來(lái)看看子線程的邏輯。

void worker(std::mutex *mutex,
std::condition_variable *condition_variable,
std::deque *requests) {
int connfd;
while (true) {
{
std::unique_lock lock(*mutex);
// 沒(méi)有任務(wù)則等待,否則取出任務(wù)處理
while ((*requests).size() == 0)
{
(*condition_variable).wait(lock);
}
connfd = (*requests).front();
(*requests).pop_front();
}
char buf[4096];
int ret;
while (1) {
memset(buf, 0, sizeof(buf));
int bytes = read(connfd, buf, sizeof(buf));
if (bytes <= 0) {
close(connfd);
} else {
write(connfd, buf, bytes);
}
}
}
}

子線程不斷從任務(wù)隊(duì)列中取出任務(wù),具體來(lái)說(shuō)就是連接對(duì)應(yīng)的文件描述符,然后不斷讀取里面的數(shù)據(jù),最后返回給客戶端。但是這樣的功能顯然沒(méi)有太大意義,所以我們基于這個(gè)基礎(chǔ)上實(shí)現(xiàn)一個(gè) HTTP 服務(wù),讓它可以處理 HTTP 請(qǐng)求。當(dāng)然我們手寫(xiě)一個(gè)優(yōu)秀的 HTTP 解析器并非易事,所以我們直接使用開(kāi)源的就好,這里選擇的是 llhttp,這是 Node.js 所使用的 HTTP 解析器。這里就不具體羅列細(xì)節(jié),大概介紹一下 llhttp 的用法。

typedef void (*p_on_headers_complete)(on_headers_complete_info, parser_callback);
typedef void (*p_on_body_complete)(on_body_complete_info, parser_callback);
typedef void (*p_on_body)(on_body_info, parser_callback);
struct parser_callback {
void * data;
p_on_headers_complete on_headers_complete;
p_on_body on_body;
p_on_body_complete on_body_complete;
};
class HTTP_Parser {
public:
HTTP_Parser(llhttp_type type, parser_callback callbacks = {});
int on_message_begin(llhttp_t* parser);
int on_status(llhttp_t* parser, const char* at, size_t length);
int on_url(llhttp_t* parser, const char* at, size_t length);
int on_header_field(llhttp_t* parser, const char* at, size_t length);
int on_header_value(llhttp_t* parser, const char* at, size_t length);
int on_headers_complete(llhttp_t* parser);
int on_body(llhttp_t* parser, const char* at, size_t length);
int on_message_complete(llhttp_t* parser);
int parse(const char* data, int len);
int finish();
void print();
};

HTTP_Parser 是我自己實(shí)現(xiàn)的 HTTP Parser Wrapper,主要是對(duì) llhttp 的封裝,我們看到 HTTP_Parser 里有很多回調(diào)鉤子,對(duì)應(yīng)的就是 llhttp 提供的,另外 HTTP_Parser 支持調(diào)用方傳入鉤子,也就是 parser_callback 所定義的。當(dāng) llhttp 回調(diào) HTTP_Parser 時(shí),HTTP_Parser 在合適的時(shí)機(jī)就會(huì)調(diào)用 parser_callback 里的回調(diào),比如在解析完 HTTP Header 時(shí),或者解析完整個(gè)報(bào)文時(shí)。具體的解析過(guò)程是當(dāng)調(diào)用方收到數(shù)據(jù)時(shí),執(zhí)行 parse 函數(shù),然后 llhttp 就會(huì)不斷地調(diào)用我們傳入的鉤子。了解了 HTTP 解析器的大致使用,我們來(lái)看看怎么在項(xiàng)目里使用。

parser_callback callback = {
&connfd,
[](on_body_complete_info info, parser_callback callback) {
int* connfd = (int *)callback.data;
const char * data = "HTTP/1.1 200 OK\r\nServer: multi-thread-server\r\ncontent-length: 11\r\n\r\nhello:world\r\n\r\n";
write(*connfd, data, strlen(data));
close(*connfd);
},
};
HTTP_Parser parser(HTTP_REQUEST, callback);
char buf[4096];
int ret;
while (1) {
memset(buf, 0, sizeof(buf));
int error = 0;
ret = read(connfd, buf, sizeof(buf));
parser.parse(buf, ret);
}

這里只列出關(guān)鍵的代碼,當(dāng)我們收到數(shù)據(jù)時(shí),我們通過(guò) parser.parse(buf, ret) 調(diào)用 llhttp 進(jìn)行解析,llhttp 就會(huì)不斷地回調(diào)鉤子函數(shù),當(dāng)解析完一個(gè)報(bào)文后,on_body_complete 回調(diào)就會(huì)被執(zhí)行,在這里我們就可以對(duì) HTTP 請(qǐng)求進(jìn)行響應(yīng),比如這里返回一個(gè) 200 的響應(yīng)報(bào)文,然后關(guān)閉連接。因?yàn)橥ㄟ^(guò) llhttp 我們可以拿到具體的請(qǐng)求 url,所以我們還可以進(jìn)一步拓展,根據(jù) url 進(jìn)行不同的處理。

到此為止,就實(shí)現(xiàn)了一個(gè) HTTP 服務(wù)器了 ,在早期的時(shí)候,服務(wù)器也是采用這種多進(jìn)程 / 多線程的處理方式,現(xiàn)在有了多路復(fù)用等技術(shù)后,很多服務(wù)器都是基于事件驅(qū)動(dòng)來(lái)實(shí)現(xiàn)了。但是主線程接收請(qǐng)求,分發(fā)給子線程處理這種思想在有些服務(wù)器也還是存在的,比如 Node.js,只不過(guò) Node.js 中是進(jìn)程間進(jìn)行傳遞。本文大概介紹到這里,服務(wù)器技術(shù)是非常復(fù)雜、有趣的方向,上層的架構(gòu)也隨著操作系統(tǒng)的能力不斷在變化,本文只是作一個(gè)簡(jiǎn)單的探索和興趣罷了,具體代碼在 https://github.com/theanarkh/multi-thread-server。下面是架構(gòu)圖。


新聞標(biāo)題:利用多線程和C++實(shí)現(xiàn)一個(gè)簡(jiǎn)單的HTTP服務(wù)器
分享地址:http://m.5511xx.com/article/djigoih.html