高性能 C++ HTTP 客戶端原理與實(shí)現(xiàn)

一玫膀、什么是Http Client

Http協(xié)議矛缨,是全互聯(lián)網(wǎng)共同的語(yǔ)言,而Http Client帖旨,可以說(shuō)是我們需要從互聯(lián)網(wǎng)世界獲取數(shù)據(jù)的最基本方法箕昭,它本質(zhì)上是一個(gè)URL到一個(gè)網(wǎng)頁(yè)的轉(zhuǎn)換過(guò)程。而有了基本的Http客戶端功能解阅,再搭配上我們想要的規(guī)則和策略盟广,上至內(nèi)容檢索下至數(shù)據(jù)分析都可以實(shí)現(xiàn)了。

繼上一次介紹用Workflow可以10行C++代碼實(shí)現(xiàn)一個(gè)高性能Http服務(wù)器瓮钥,今天繼續(xù)給大家用C++實(shí)現(xiàn)一個(gè)高性能的Http客戶端也同樣很簡(jiǎn)單!

// [http_client.cc]
#include "stdio.h"
#include "workflow/HttpMessage.h"
#include "workflow/WFTaskFactory.h"

int main (int argc, char *argv[])
{
    const char *url = "https://github.com/sogou/workflow";
    WFHttpTask *task = WFTaskFactory::create_http_task (url, 2, 3,
            [](WFHttpTask * task) { 
                fprintf(stderr, "%s %s %s\r\n",
                        task->get_resp()->get_http_version(),
                        task->get_resp()->get_status_code(),
                        task->get_resp()->get_reason_phrase());
    });
    task->start();
    getchar(); // press "Enter" to end.
    return 0;
}

只要安裝好了Workflow烹吵,以上代碼即可以通過(guò)以下命令編譯出一個(gè)簡(jiǎn)單的http_client:

g++ -o http_client http_client.cc --std=c++11 -lworkflow -lssl -lcrypto -lpthread

根據(jù)Http協(xié)議碉熄,我們執(zhí)行這個(gè)可執(zhí)行程序 ./http_client,就會(huì)得到以下內(nèi)容:

HTTP/1.1 200 OK

同理肋拔,我們還可以通過(guò)其他api來(lái)獲得返回的其他Http header和Http body锈津,一切內(nèi)容都在這個(gè) WFHttpTask 中。而因?yàn)?strong>Workflow是個(gè)異步調(diào)度框架凉蜂,因此這個(gè)任務(wù)發(fā)出之后琼梆,不會(huì)阻塞當(dāng)前線程性誉,外加內(nèi)部自帶的連接復(fù)用,從根本上保證了我們的Http Client的高性能茎杂。

接下來(lái)給大家詳細(xì)講解一下原理~

二错览、請(qǐng)求的過(guò)程

1. 創(chuàng)建Http任務(wù)

上述demo可以看到,請(qǐng)求是通過(guò)發(fā)起一個(gè)Workflow的Http異步任務(wù)來(lái)實(shí)現(xiàn)的煌往,創(chuàng)建任務(wù)的接口如下:

WFHttpTask *create_http_task(const std::string& url,
                             int redirect_max, int retry_max,
                             http_callback_t callback);

第一個(gè)參數(shù)就是我們要請(qǐng)求的URL倾哺。對(duì)應(yīng)的,在一開(kāi)始的示例中刽脖,我們的重定向次數(shù)redirect_max是2次羞海,而重試次數(shù)retry_max是3次。第四個(gè)參數(shù)是一個(gè)回調(diào)函數(shù)曲管,示例中我們用了一個(gè)lambda却邓,由于Workflow的任務(wù)都是異步的,因此我們處理結(jié)果這件事情是被動(dòng)通知我們的院水,結(jié)果回來(lái)就會(huì)調(diào)起這個(gè)回調(diào)函數(shù)腊徙,格式如下:

using http_callback_t = std::function<void (WFHttpTask *)>;

2. 填寫header并發(fā)出

我們的網(wǎng)絡(luò)交互無(wú)非是請(qǐng)求-回復(fù),對(duì)應(yīng)到Http Client上衙耕,在我們創(chuàng)建好了task之后昧穿,我們有一些時(shí)機(jī)是處理請(qǐng)求的,在Http協(xié)議里橙喘,就是在header里填好協(xié)議相關(guān)的事情时鸵,比如我們可以通過(guò)Connection來(lái)指定希望得到建立Http的長(zhǎng)連接,以節(jié)省下次建立連接的耗時(shí)厅瞎,那么我們可以把Connection設(shè)置為Keep-Alive饰潜。示例如下:

protocol::HttpRequest *req = task->get_req();
req->add_header_pair("Connection", "Keep-Alive");
task->start();

最后我們會(huì)把設(shè)置好請(qǐng)求的任務(wù),通過(guò) task->start(); 發(fā)出和簸。最開(kāi)始的 http_client.cc 示例中彭雾,有一個(gè) getchar(); 語(yǔ)句,是因?yàn)槲覀兊漠惒饺蝿?wù)發(fā)出后是非阻塞的锁保,當(dāng)前線程不暫時(shí)停住就會(huì)退出薯酝,而我們希望等到回調(diào)函數(shù)回來(lái),因此我們可以用多種暫停的方式爽柒。

3. 處理返回結(jié)果

一個(gè)返回結(jié)果吴菠,根據(jù)Http協(xié)議,會(huì)包含三部分:消息行浩村、消息頭header做葵、消息正文body。如果我們想要獲取body心墅,可以這樣:

const void *body;
size_t body_len;
task->get_resp()->get_parsed_body(&body, &body_len); 

三酿矢、高性能的基本保證

我們使用C++來(lái)寫Http Client榨乎,最香的就是可以利用其高性能。Workflow對(duì)高并發(fā)是如何保證的呢瘫筐?其實(shí)就兩點(diǎn):

  • 純異步蜜暑;
  • 連接復(fù)用;

前者是對(duì)線程資源的重復(fù)利用严肪、后者是對(duì)連接資源的重復(fù)利用史煎,這些框架層級(jí)都為用戶管理好了,充分減少開(kāi)發(fā)者的心智負(fù)擔(dān)驳糯。

1. 異步調(diào)度模式

同步和異步的模式直接決定了我們的Http Client可以有多大的并發(fā)度篇梭。為什么呢?通過(guò)下圖可以先看看同步框架發(fā)起三個(gè)Http任務(wù)酝枢,線程模型是怎樣的:

image

網(wǎng)絡(luò)延遲往往非常大恬偷,如果我們?cè)谕降却蝿?wù)回來(lái)的話,線程就會(huì)一直被占用帘睦。這時(shí)候我們需要看看異步框架是如何實(shí)現(xiàn)的:

image

如圖所示袍患,只要任務(wù)發(fā)出之后,線程即可做其他事情竣付,我們傳入了一個(gè)回調(diào)函數(shù)做異步通知诡延,因此等任務(wù)的網(wǎng)絡(luò)回復(fù)收完之后,再讓線程執(zhí)行這個(gè)回調(diào)函數(shù)即可拿到Http請(qǐng)求的結(jié)果古胆,期間多個(gè)任務(wù)并發(fā)出去的時(shí)候肆良,線程是可以復(fù)用的,輕松達(dá)到幾十萬(wàn)的QPS并發(fā)度逸绎。

2. 連接復(fù)用

我們剛才有提到惹恃,只要我們建立了長(zhǎng)連接,即可提高效率棺牧。為什么呢巫糙?因?yàn)榭蚣軐?duì)連接有復(fù)用。我們先來(lái)看看如果一個(gè)請(qǐng)求就建立一個(gè)連接颊乘,會(huì)是什么樣的情況:

image

很顯然参淹,占用大量的連接是對(duì)系統(tǒng)資源的浪費(fèi),而且每次都要做connect以及close是非常耗時(shí)的乏悄,除了TCP常見(jiàn)的握手以外承二,許多應(yīng)用層協(xié)議建立連接的過(guò)程也會(huì)相對(duì)復(fù)雜。但使用Workflow就不會(huì)有這樣的煩惱纲爸,Workflow會(huì)在任務(wù)發(fā)出的時(shí)候自動(dòng)查找當(dāng)前可以復(fù)用的連接,如果沒(méi)有才會(huì)自動(dòng)創(chuàng)建妆够,完全不需要開(kāi)發(fā)者關(guān)心連接如何復(fù)用的細(xì)節(jié):

image

3. 解鎖其他功能

當(dāng)然,除了以上的高性能以外,一個(gè)高性能的Http Client往往還有許多其他的需求症杏,這里可以結(jié)合實(shí)際情況與大家分享:

  1. 結(jié)合workflow的串并聯(lián)任務(wù)流层亿,實(shí)現(xiàn)超大規(guī)模并行抓取
  2. 按順序或者按指定速度請(qǐng)求某個(gè)站點(diǎn)的內(nèi)容钠龙,避免請(qǐng)求過(guò)猛被封禁;
  3. Http Client遇到redirect可以自動(dòng)幫我做跳轉(zhuǎn),一步到位請(qǐng)求到最終結(jié)果伤极;
  4. 希望通過(guò)proxy代理訪問(wèn)HTTPHTTPS資源;

以上這些需求姨伤,要求框架對(duì)于Http任務(wù)的編排有超高的靈活性哨坪,以及對(duì)實(shí)際需求(比如redirect、ssl代理等功能)有非常接地氣的支持乍楚,這些Workflow都已經(jīng)實(shí)現(xiàn)当编。

項(xiàng)目地址

https://github.com/sogou/workflow

歡迎使用 workflowstar 支持一下!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末徒溪,一起剝皮案震驚了整個(gè)濱河市忿偷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌臊泌,老刑警劉巖鲤桥,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異渠概,居然都是意外死亡茶凳,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門高氮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)慧妄,“玉大人,你說(shuō)我怎么就攤上這事剪芍∪停” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵罪裹,是天一觀的道長(zhǎng)饱普。 經(jīng)常有香客問(wèn)我,道長(zhǎng)状共,這世上最難降的妖魔是什么套耕? 我笑而不...
    開(kāi)封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮峡继,結(jié)果婚禮上冯袍,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好康愤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布儡循。 她就那樣靜靜地躺著,像睡著了一般征冷。 火紅的嫁衣襯著肌膚如雪择膝。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天检激,我揣著相機(jī)與錄音肴捉,去河邊找鬼。 笑死叔收,一個(gè)胖子當(dāng)著我的面吹牛齿穗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播今穿,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼缤灵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蓝晒?” 一聲冷哼從身側(cè)響起腮出,我...
    開(kāi)封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎芝薇,沒(méi)想到半個(gè)月后胚嘲,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡洛二,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年馋劈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晾嘶。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡妓雾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出垒迂,到底是詐尸還是另有隱情械姻,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布机断,位于F島的核電站楷拳,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏吏奸。R本人自食惡果不足惜欢揖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望奋蔚。 院中可真熱鬧她混,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至晋涣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間沉桌,已是汗流浹背谢鹊。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留留凭,地道東北人佃扼。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蔼夜,于是被迫代替她去往敵國(guó)和親兼耀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容