libcurl主要提供了兩種發送http請求的方式,分別是Easy interface方式和multi interface方式,前者是采用阻塞的方式發送單條數據,后者采用組合的方式可以一次性發送多條數據
一、Easy interface
libcurl的easy interface是最基本的用法,簡要流程為:
1、在主線程中調用curl_global_init(CURL_GLOBAL_ALL)初始化
2、調用curl_easy_init獲取一個句柄;
3、調用curl_easy_setopt函數設置此次傳輸的一些基本參數,如url地址、http頭、cookie信息、發送超時時間等,其中,CURLOPT_URL是必設的選項;
4、設置完成后,調用curl_easy_perform函數發送數據;
5、數據發送完畢后,調用curl_easy_cleanup清空句柄;
6、調用curl_global_cleanup()做清理工作。
實現代碼:
- bool send_easy_hanler(char* post_url, req_t* req)
- {
- //easy handler的句柄
- CURL* curl = NULL;
- CURLcode res = CURLE_OK;
- //HTTP報文頭
- struct curl_slist* headers = NULL;
- char tmp_str[256] = { 0 };
- //構建HTTP報文頭
- snprintf(tmp_str, sizeof(tmp_str), "User-Agent: %s", req->user_agent_);
- headers = curl_slist_append(headers, tmp_str);
- snprintf(tmp_str, sizeof(tmp_str), "Accept-Language: %s", req->language_);
- headers = curl_slist_append(headers, tmp_str);
- snprintf(tmp_str, sizeof(tmp_str), "X-FORWORDED-FOR: %s", req->ip_.c_str());
- headers = curl_slist_append(headers, tmp_str);
- /*這個函數只能用一次,如果這個函數在curl_easy_init函數調用時還沒調用,
- 它講由libcurl庫自動調用,所以多線程下最好在主線程中調用一次該函數以防止在線程
- 中curl_easy_init時多次調用*/
- curl_global_init(CURL_GLOBAL_ALL);
- //初始化easy handler句柄
- curl = curl_easy_init();
- if (curl) {
- //設置post請求的url地址
- curl_easy_setopt(curl, CURLOPT_URL, post_url);
- //設置HTTP頭
- curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
- //設置發送超時時間
- curl_easy_setopt(curl, CURLOPT_TIMEOUT, 1);
- //執行單條請求
- res = curl_easy_perform(curl);
- if (res != CURLE_OK) {
- //curl_easy_strerror進行出錯打印
- LOG(WARNING) << "curl_easy_perform() failed:" << curl_easy_strerror(res);
- }
- curl_slist_free_all(headers);
- //這個調用用來結束一個會話.與curl_easy_init配合着用
- curl_easy_cleanup(curl);
- //在結束libcurl使用的時候,用來對curl_global_init做的工作清理。類似於close的函數
- curl_global_cleanup();
- }
二、multi interface
multi interface提供了多種easy interface沒有的特性,主要是
1、提供了pull接口,使用libcurl的程序能夠決定何時何處調用libcurl來get/send數據
2、在同一線程中實現多條數據同時發送,且並沒有使得程序更加復雜
3、程序可以在自己的文件描述符和curl的文件描述符中同時等待執行
4、提供基於事件的處理、擴大傳輸規模到數千個並發連接
multi接口的使用會比easy 接口稍微復雜點,畢竟multi接口是依賴easy接口的,簡要流程為:
1、使用curl_multi_init創建一個multi handle,這個handler會在后續的curl_multi_*函數中使用multi handler可以同時並發傳輸多條數據,每一條單獨的數據是由一個easy handler創建;
2、需要事先將需要傳輸的所有easyhandler創建好,並使用curl_easy_setopt設置各自屬性,接着調用curl_multi_add_handle函數逐個添加到multi handle中;
3、調用curl_multi_perform進程數據傳輸,傳輸過程中將會調用每一個easy handler設置的回調函數或者配置內容,程序通過函數curl_multi_fdset、select()提取信息來判斷何時進行數據傳輸等操作,函數curl_multi_perform的一個輸入參數儲存仍在進行傳輸的數據量,通過讀取該變量,可以判斷multi handles是否運行完畢,傳輸完畢不代表傳輸成功,可能有一個或多個傳輸失敗;
4、調用函數curl_multi_info_read可以獲取當前或之前傳輸的信息,重復調用該函數直到該消息隊列為空,每一條返回信息都包含對應的easl handler的傳輸情況;
5、當一個easy handler傳輸完成,此easy handler仍然仍然停留在multi stack中,需要調用curl_multi_remove_handle將其從multi stack中移除,然后調用curl_easy_cleanup將其關閉;
6、當multi stack中的所有傳輸都完成時,調用 curl_multi_cleanup關閉multi handler,需要注意的是事先要調用curl_easy_cleanup逐個清空所有easy handler。
源碼:
- #include <errno.h>
- #include <stdlib.h>
- #include <string.h>
- #include <curl/multi.h>
- static const char *urls[] = {
- "http://www.microsoft.com",
- "http://www.opensource.org",
- "http://www.google.com",
- "http://www.yahoo.com",
- "http://www.ibm.com",
- "http://www.mysql.com",
- "http://www.oracle.com",
- "http://www.ripe.net",
- };
- #define MAX 8 /* number of simultaneous transfers */
- #define CNT sizeof(urls)/sizeof(char*) /* total number of transfers to do */
- /*此函數讀取libcurl發送數據后的返回信息,如果不設置此函數,
- 那么返回值將會輸出到控制台,影響程序性能*/
- static size_t cb(char *d, size_t n, size_t l, void *p)
- {
- /* take care of the data here, ignored in this example */
- (void)d;
- (void)p;
- return n*l;
- }
- //設置單個easy handler的屬性添加單個easy handler到multi handler中,
- static void init(CURLM *cm, int i)
- {
- CURL *eh = curl_easy_init();
- curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, cb);
- curl_easy_setopt(eh, CURLOPT_HEADER, 0L);
- curl_easy_setopt(eh, CURLOPT_URL, urls[i]);
- curl_easy_setopt(eh, CURLOPT_PRIVATE, urls[i]);
- curl_easy_setopt(eh, CURLOPT_VERBOSE, 0L);
- //添加easy handler 到multi handler中
- curl_multi_add_handle(cm, eh);
- }
- int main(void)
- {
- CURLM *cm;
- CURLMsg *msg;
- long curl_timeo;
- unsigned int C=0;
- int max_fd, msgs_left, still_running = -1;//still_running判斷multi handler是否傳輸完畢
- fd_set fd_read, fd_write, fd_except;
- struct timeval T;
- curl_global_init(CURL_GLOBAL_ALL);
- cm = curl_multi_init();
- //現在multi handler的最大連接數
- curl_multi_setopt(cm, CURLMOPT_MAXCONNECTS, (long)MAX);
- for(C = 0; C < MAX; ++C) {
- init(cm, C);
- }
- do{
- curl_multi_perform(cm, &still_running);
- if(still_running) {
- FD_ZERO(&fd_read);
- FD_ZERO(&fd_write);
- FD_ZERO(&fd_except);
- //獲取multi curl需要監聽的文件描述符集合 fd_set
- if(!curl_multi_fdset(cm, &fd_read, &fd_write, &fd_except, &max_fd)) {
- fprintf(stderr, "E: curl_multi_fdset\n");
- return EXIT_FAILURE;
- }
- if(!curl_multi_timeout(cm, &curl_timeo)) {
- fprintf(stderr, "E: curl_multi_timeout\n");
- return EXIT_FAILURE;
- }
- if(curl_timeo == -1)
- curl_timeo = 100;
- //如果max_fd返回-1,休眠一段時間后繼續執行curl_multi_perform
- if(max_fd == -1) {
- sleep((unsigned int)curl_timeo / 1000);
- }
- else {
- T.tv_sec = curl_timeo/1000;
- T.tv_usec = (curl_timeo%1000)*1000;
- /* 執行監聽,當文件描述符狀態發生改變的時候返回
- * 返回0,程序調用curl_multi_perform通知curl執行相應操作
- * 返回-1,表示select錯誤
- */
- if(0 > select(max_fd+1, &fd_read, &fd_write, &fd_except, &T)) {
- fprintf(stderr, "E: select(%i,,,,%li): %i: %s\n",
- max_fd+1, curl_timeo, errno, strerror(errno));
- return EXIT_FAILURE;
- }
- }
- }
- while((msg = curl_multi_info_read(cm, &msgs_left))) {
- if(msg->msg == CURLMSG_DONE) {
- char *url;
- CURL *e = msg->easy_handle;
- curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &url);
- fprintf(stderr, "R: %d - %s <%s>\n",
- msg->data.result, curl_easy_strerror(msg->data.result), url);
- /*當一個easy handler傳輸完成,此easy handler仍然仍然停留在multi stack中,
- 調用curl_multi_remove_handle將其從multi stack中移除,然后調用curl_easy_cleanup將其關閉*/
- curl_multi_remove_handle(cm, e);
- curl_easy_cleanup(e);
- }
- else {
- fprintf(stderr, "E: CURLMsg (%d)\n", msg->msg);
- }
- }
- }while(still_running);
- //當multi stack中的所有傳輸都完成時,調用 curl_multi_cleanup關閉multi handler
- curl_multi_cleanup(cm);
- curl_global_cleanup();
- return EXIT_SUCCESS;
- }