天天看點

高性能 C++ HTTP 用戶端原理與實作

一、什麼是Http Client

Http協定,是全網際網路共同的語言,而Http Client,可以說是我們需要從網際網路世界擷取資料的最基本方法,它本質上是一個URL到一個網頁的轉換過程。而有了基本的Http用戶端功能,再搭配上我們想要的規則和政策,上至内容檢索下至資料分析都可以實作了。

繼上一次介紹用Workflow可以10行C++代碼實作一個高性能Http伺服器,今天繼續給大家用C++實作一個高性能的Http用戶端也同樣很簡單!

// [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,以上代碼即可以通過以下指令編譯出一個簡單的http_client:

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

根據Http協定,我們執行這個可執行程式

./http_client

,就會得到以下内容:

HTTP/1.1 200 OK
           

同理,我們還可以通過其他api來獲得傳回的其他Http header和Http body,一切内容都在這個

WFHttpTask

中。而因為Workflow是個異步排程架構,是以這個任務發出之後,不會阻塞目前線程,外加内部自帶的連接配接複用,從根本上保證了我們的Http Client的高性能。

接下來給大家詳細講解一下原理~

二、請求的過程

1. 建立Http任務

上述demo可以看到,請求是通過發起一個Workflow的Http異步任務來實作的,建立任務的接口如下:

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

第一個參數就是我們要請求的URL。對應的,在一開始的示例中,我們的重定向次數redirect_max是2次,而重試次數retry_max是3次。第四個參數是一個回調函數,示例中我們用了一個lambda,由于Workflow的任務都是異步的,是以我們處理結果這件事情是被動通知我們的,結果回來就會調起這個回調函數,格式如下:

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

2. 填寫header并發出

我們的網絡互動無非是請求-回複,對應到Http Client上,在我們建立好了task之後,我們有一些時機是處理請求的,在Http協定裡,就是在header裡填好協定相關的事情,比如我們可以通過Connection來指定希望得到建立Http的長連接配接,以節省下次建立連接配接的耗時,那麼我們可以把Connection設定為Keep-Alive。示例如下:

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

最後我們會把設定好請求的任務,通過

task->start();

發出。最開始的

http_client.cc

示例中,有一個

getchar();

語句,是因為我們的異步任務發出後是非阻塞的,目前線程不暫時停住就會退出,而我們希望等到回調函數回來,是以我們可以用多種暫停的方式。

3. 處理傳回結果

一個傳回結果,根據Http協定,會包含三部分:消息行、消息頭header、消息正文body。如果我們想要擷取body,可以這樣:

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

三、高性能的基本保證

我們使用C++來寫Http Client,最香的就是可以利用其高性能。Workflow對高并發是如何保證的呢?其實就兩點:

  • 純異步;
  • 連接配接複用;

前者是對線程資源的重複利用、後者是對連接配接資源的重複利用,這些架構層級都為使用者管理好了,充分減少開發者的心智負擔。

1. 異步排程模式

同步和異步的模式直接決定了我們的Http Client可以有多大的并發度。為什麼呢?通過下圖可以先看看同步架構發起三個Http任務,線程模型是怎樣的:

高性能 C++ HTTP 用戶端原理與實作

網絡延遲往往非常大,如果我們在同步等待任務回來的話,線程就會一直被占用。這時候我們需要看看異步架構是如何實作的:

高性能 C++ HTTP 用戶端原理與實作

如圖所示,隻要任務發出之後,線程即可做其他事情,我們傳入了一個回調函數做異步通知,是以等任務的網絡回複收完之後,再讓線程執行這個回調函數即可拿到Http請求的結果,期間多個任務并發出去的時候,線程是可以複用的,輕松達到幾十萬的QPS并發度。

2. 連接配接複用

我們剛才有提到,隻要我們建立了長連接配接,即可提高效率。為什麼呢?因為架構對連接配接有複用。我們先來看看如果一個請求就建立一個連接配接,會是什麼樣的情況:

高性能 C++ HTTP 用戶端原理與實作

很顯然,占用大量的連接配接是對系統資源的浪費,而且每次都要做connect以及close是非常耗時的,除了TCP常見的握手以外,許多應用層協定建立連接配接的過程也會相對複雜。但使用Workflow就不會有這樣的煩惱,Workflow會在任務發出的時候自動查找目前可以複用的連接配接,如果沒有才會自動建立,完全不需要開發者關心連接配接如何複用的細節:

高性能 C++ HTTP 用戶端原理與實作

3. 解鎖其他功能

當然,除了以上的高性能以外,一個高性能的Http Client往往還有許多其他的需求,這裡可以結合實際情況與大家分享:

  1. 結合workflow的串并聯任務流,實作超大規模并行抓取;
  2. 按順序或者按指定速度請求某個站點的内容,避免請求過猛被封禁;
  3. Http Client遇到redirect可以自動幫我做跳轉,一步到位請求到最終結果;
  4. 希望通過proxy代理通路

    HTTP

    HTTPS

    資源;

以上這些需求,要求架構對于Http任務的編排有超高的靈活性,以及對實際需求(比如redirect、ssl代理等功能)有非常接地氣的支援,這些Workflow都已經實作。

項目位址

https://github.com/sogou/workflow

歡迎使用 workflow 并 star 支援一下!

繼續閱讀