文為轉載,原文地址: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, ¶m); 3 curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb); 4 curl_multi_setopt(multi, CURLMOPT_TIMERDATA, ¶m);
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跑一遍,看看打印出來的日志,還有詳細的參數設置,需要去看官網文檔。