[轉] libcurl異步方式使用總結(附流程圖)


文為轉載,原文地址:libcurl異步方式使用總結

 

實習期間用到了libcurl來做HTTPS雙向認證,用的是異步方式,簡單總結一下。

libcurl這個庫的同步方式很簡單,不做介紹,而異步方式很難理解,本博客參考官網的demo講解,剛開始看可能很蒙,最后會整合全流程。

使用步驟如下:

1.初始化創建一個multi句柄:

1 CURLM *multi = curl_multi_init();

2.對multi句柄設置socket回調和timer回調:

1 curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, multi_sock_cb);
2 curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, &param);
3 curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
4 curl_multi_setopt(multi, CURLMOPT_TIMERDATA, &param);

3.對multi句柄添加easy句柄,異步開始:

1 CURL *easy = curl_easy_init();
2 curl_easy_setopt(conn->easy, CURLOPT_URL, url);
3 curl_easy_setopt(conn->easy, CURLOPT_WRITEFUNCTION, write_cb);  // 負責讀入數據的函數
4 curl_easy_setopt(conn->easy, CURLOPT_WRITEDATA, &data);
5 curl_multi_add_handle(multi, easy);

先看看第三行設置的write_cb,該函數是你讀入數據的函數:

1 /*
2  * ptr 指向libcurl庫讀到的數據
3  * data 用戶自定義的緩沖區, 上面第四行設置
4  */
5 size_t write_cb(void *ptr, size_t size, size_t nmemb, void *data) {
6     // 把ptr指向的數據拷到data
7 }

curl_multi_add_handle運行結束的那一刻,第2步設置的multi_timer_cb馬上被拉起執行,讓我們看看multi_timer_cb的函數聲明:

1 /* 
2  * multi 第一步創建的句柄
3  * timeout_ms libcurl庫維護的一個超時時間,具體怎么算不清楚,回調時會自動賦值
4  * param 第二步設置的參數
5  * return 錯誤碼
6  */
7 int multi_timer_cb(CURLM *multi, long timeout_ms, void *param)

libcurl庫本身沒有定時器功能,只是告訴你一個定時時間timeout_ms,這就要求我們自己維護一個定時器和到期的回調函數timer_cb。 
偽代碼表示如下:

1 int multi_timer_cb(CURLM *multi, long timeout_ms, void *param) {
2     timer_.add(timer_cb, ms);  // ms后執行timer_cb
3 }

timer_cb主要調用libcurl的兩個函數:

 1 void timer_cb(param...) {
 2   CURLMcode rc;
 3   rc = curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0,
 4                                 &still_running);
 5   while((msg = curl_multi_info_read(multi, &msgs_left))) {   // 判斷數據是否讀完
 6     if(msg->msg == CURLMSG_DONE) {
 7         // 清理資源操作
 8     }
 9   }
10 }

multi_sock_cb類似如此:

1 /*
2  * e 第三步添加的easy句柄
3  * s libcurl創建維護的socket
4  * what 執行動作(讀或寫)
5  */
6 int multi_sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp)

在libcurl維護的socket描述符發生狀態改變時(變回可讀或可寫),multi_sock_cb才會被回調。注意,函數回調時,第二個參數是socket描述符,這是libcurl維護創建的,但是你把它添加到poller(代指epoll或poll的封裝類)或者libev等事件觸發器中去,並設置回調函數,偽代碼如下

1 int multi_sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp) {
2     poller.add(s, socket_cb);  // 當描述符可讀和可寫時,調用socket_cb
3 }

看到這里是不是懵逼,不要急,最后會講解全流程。socket_cb里也是調用兩個libcurl函數:

 1 void socket_cb(param...) {
 2   CURLMcode rc;
 3   rc = curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0,
 4                                 &still_running);
 5   while((msg = curl_multi_info_read(multi, &msgs_left))) {   // 判斷數據是否讀完
 6     if(msg->msg == CURLMSG_DONE) {
 7         // 清理資源操作
 8         }
 9     }
10 }

好了,函數寫成這樣就差不多了(都是偽代碼,具體用法還是看demo)。那么這代碼到底是怎么執行的呢,請看下圖。

1、在curl_multi_add_handle之后,multi_timer_cb會馬上被拉起調用,然后第一次調用的話timeout是0ms,所以timer_cb也會被拉起,然后調用curl_multi_socket_action。

2、此時,請注意在curl_multi_add_handle之前已經設置過了url了,所以此時是需要發起http請求,即寫請求,所以在curl_multi_socket_action中libcurl會創建一個socket描述符,然后狀態變為可寫。

3、此時,因為libcurl的socket描述符狀態發生改變,所以multi_sock_cb會被拉起,multi_sock_cb中就把socket描述符添加到poller中,設置寫事件的回調函數為socket_cb。

4、因為socket描述符是可寫的,所以poller會調用sock_cb,curl_multi_socket_action又被調用,而此函數就會發送http請求(即libcurl負責寫fd)。

5、等到http請求被發送完,就需要接收響應,所以libcurl會把socket描述符從寫狀態改為讀狀態。

6、因為socket描述符變為可讀,狀態改變,multi_sock_cb又被調用,此時在poller中,將socket描述符的讀事件回調函數設置為socket_cb。

7、當響應到來的時候,socket描述符可讀,調用socket_cb,從而調用curl_multi_socket_action,該函數就就會異步調用之前設置的、負責讀入數據的write_cb,從而讀入數據。

8、 不斷重復上一個步驟,直到數據被讀完,此時libcurl會把socket描述符設置為刪除狀態,所以multi_sock_cb會被回調,負責清理資源。而且,curl_multi_info_read會判斷已經讀完數據,可以在這里進行數據轉發,最終進行資源清理。注意,最終讀到的數據,會在write_cb設置的data中(前提是你有在write_cb中保存下來哈哈哈~)。

總結:
這庫使用起來十分奇怪,我看了幾天才看懂用法,我這篇博文寫得十分簡陋,最好的學習方法還是把demo跑一遍,看看打印出來的日志,還有詳細的參數設置,需要去看官網文檔。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM