程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> 使用multi curl進行http並發訪問

使用multi curl進行http並發訪問

編輯:關於C#
 

curl是一款利用URL語法進行文件傳輸的工具,它支持多種協議,包括FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET等,我們既可以在命令行上使用它,也可以利用 libcurl進行相關編程。相信大部分同學都應該使用過libcurl的easy 接口,easy接口的使用非常的簡單,curl_easy_init用來初始化一個easy curl對象,curl_easy_setopt對easy curl對象進行相關設置,最後curl_easy_perform執行curl請求,返回相應結果。easy接口是阻塞的,也就是說必須等到上一個curl請求執行完後,下一個curl請求才能繼續執行,在一般的應用場合,這種阻塞的訪問方式是沒有問題的,但是當程序需要進行多次curl並發請求的時候,easy接口就無能為力了,這個時候curl提供的multi接口就派上用場了,網上關於libcurl的multi接口的使用資料比較少(百度出來的大部分都是php multi curl的資料),curl官網上貌似也只有相關函數的說明,有實際demo才能讓我們更快速的上手使用,所以下面結合實際例子來講講multi curl接口的使用方法。

相比而言,multi接口的使用會比easy 接口稍微復雜點,畢竟multi接口是依賴easy接口的,首先粗略的講下其使用流程:curl_multi _init初始化一個multi curl對象,為了同時進行多個curl的並發訪問,我們需要初始化多個easy curl對象,使用curl_easy_setopt進行相關設置,然後調用curl_multi _add_handle把easy curl對象添加到multi curl對象中,添加完畢後執行curl_multi_perform方法進行並發的訪問,訪問結束後curl_multi_remove_handle移除相關easy curl對象,curl_easy_cleanup清除easy curl對象,最後curl_multi_cleanup清除multi curl對象。

上面的介紹只是給大家一個大概的印象,實際使用中還有很多細節需要注意,好了,代碼才能說明一切,下面的例子使用multi curl方式進行多次http並發訪問,並輸出訪問結果。

#include <string>
#include <iostream>

#include <curl/curl.h>
#include <sys/time.h>
#include <unistd.h>

using namespace std;

size_t curl_writer(void *buffer, size_t size, size_t count, void * stream)
{
std::string * pStream = static_cast<std::string *>(stream);
(*pStream).append((char *)buffer, size * count);

return size * count;
};

/**
* 生成一個easy curl對象,進行一些簡單的設置操作
*/
CURL * curl_easy_handler(const std::string & sUrl,
const std::string & sProxy,
std::string & sRsp,
unsigned int uiTimeout)
{
CURL * curl = curl_easy_init();

curl_easy_setopt(curl, CURLOPT_URL, sUrl.c_str());
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);

if (uiTimeout > 0)
{
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, uiTimeout);
}
if (!sProxy.empty())
{
curl_easy_setopt(curl, CURLOPT_PROXY, sProxy.c_str());
}

// write function //
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_writer);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &sRsp);

return curl;
}

/**
* 使用select函數監聽multi curl文件描述符的狀態
* 監聽成功返回0,監聽失敗返回-1
*/
int curl_multi_select(CURLM * curl_m)
{
int ret = 0;

struct timeval timeout_tv;
fd_set fd_read;
fd_set fd_write;
fd_set fd_except;
int max_fd = -1;

// 注意這裡一定要清空fdset,curl_multi_fdset不會執行fdset的清空操作 //
FD_ZERO(&fd_read);
FD_ZERO(&fd_write);
FD_ZERO(&fd_except);

// 設置select超時時間 //
timeout_tv.tv_sec = 1;
timeout_tv.tv_usec = 0;

// 獲取multi curl需要監聽的文件描述符集合 fd_set //
curl_multi_fdset(curl_m, &fd_read, &fd_write, &fd_except, &max_fd);

/**
* When max_fd returns with -1,
* you need to wait a while and then proceed and call curl_multi_perform anyway.
* How long to wait? I would suggest 100 milliseconds at least,
* but you may want to test it out in your own particular conditions to find a suitable value.
*/
if (-1 == max_fd)
{
return -1;
}

/**
* 執行監聽,當文件描述符狀態發生改變的時候返回
* 返回0,程序調用curl_multi_perform通知curl執行相應操作
* 返回-1,表示select錯誤
* 注意:即使select超時也需要返回0,具體可以去官網看文檔說明
*/
int ret_code = ::select(max_fd + 1, &fd_read, &fd_write, &fd_except, &timeout_tv);
switch(ret_code)
{
case -1:
/* select error */
ret = -1;
break;
case 0:
/* select timeout */
default:
/* one or more of curl's file descriptors say there's data to read or write*/
ret = 0;
break;
}

return ret;
}

#define MULTI_CURL_NUM 3

// 這裡設置你需要訪問的url //
std::string URL = "http://website.com";
// 這裡設置代理ip和端口 //
std::string PROXY = "ip:port";
// 這裡設置超時時間 //
unsigned int TIMEOUT = 2000; /* ms */

/**
* multi curl使用demo
*/
int curl_multi_demo(int num)
{
// 初始化一個multi curl 對象 //
CURLM * curl_m = curl_multi_init();

std::string RspArray[num];
CURL * CurlArray[num];

// 設置easy curl對象並添加到multi curl對象中 //
for (int idx = 0; idx < num; ++idx)
{
CurlArray[idx] = NULL;
CurlArray[idx] = curl_easy_handler(URL, PROXY, RspArray[idx], TIMEOUT);
if (CurlArray[idx] == NULL)
{
return -1;
}
curl_multi_add_handle(curl_m, CurlArray[idx]);
}

/*
* 調用curl_multi_perform函數執行curl請求
* url_multi_perform返回CURLM_CALL_MULTI_PERFORM時,表示需要繼續調用該函數直到返回值不是CURLM_CALL_MULTI_PERFORM為止
* running_handles變量返回正在處理的easy curl數量,running_handles為0表示當前沒有正在執行的curl請求
*/
int running_handles;
while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curl_m, &running_handles))
{
cout << running_handles << endl;
}

/**
* 為了避免循環調用curl_multi_perform產生的cpu持續占用的問題,采用select來監聽文件描述符
*/
while (running_handles)
{
if (-1 == curl_multi_select(curl_m))
{
cerr << "select error" << endl;
break;
} else {
// select監聽到事件,調用curl_multi_perform通知curl執行相應的操作 //
while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curl_m, &running_handles))
{
cout << "select: " << running_handles << endl;
}
}
cout << "select: " << running_handles << endl;
}

// 輸出執行結果 //
int msgs_left;
CURLMsg * msg;
while((msg = curl_multi_info_read(curl_m, &msgs_left)))
{
if (CURLMSG_DONE == msg->msg)
{
int idx;
for (idx = 0; idx < num; ++idx)
{
if (msg->easy_handle == CurlArray[idx]) break;
}

if (idx == num)
{
cerr << "curl not found" << endl;
} else
{
cout << "curl [" << idx << "] completed with status: "
<< msg->data.result << endl;
cout << "rsp: " << RspArray[idx] << endl;
}
}
}

// 這裡要注意cleanup的順序 //
for (int idx = 0; idx < num; ++idx)
{
curl_multi_remove_handle(curl_m, CurlArray[idx]);
}

for (int idx = 0; idx < num; ++idx)
{
curl_easy_cleanup(CurlArray[idx]);
}

curl_multi_cleanup(curl_m);

return 0;
}

/**
* easy curl使用demo
*/
int curl_easy_demo(int num)
{
std::string RspArray[num];

for (int idx = 0; idx < num; ++idx)
{
CURL * curl = curl_easy_handler(URL, PROXY, RspArray[idx], TIMEOUT);
CURLcode code = curl_easy_perform(curl);
cout << "curl [" << idx << "] completed with status: "
<< code << endl;
cout << "rsp: " << RspArray[idx] << endl;

// clear handle //
curl_easy_cleanup(curl);
}

return 0;
}

#define USE_MULTI_CURL

struct timeval begin_tv, end_tv;

int main(int argc, char * argv[])
{
if (argc < 2)
{
return -1;
}
int num = atoi(argv[1]);

// 獲取開始時間 //
gettimeofday(&begin_tv, NULL);
#ifdef USE_MULTI_CURL
// 使用multi接口進行訪問 //
curl_multi_demo(num);
#else
// 使用easy接口進行訪問 //
curl_easy_demo(num);
#endif
// 獲取結束時間 //
struct timeval end_tv;
gettimeofday(&end_tv, NULL);

// 計算執行延時並輸出,用於比較 //
int eclapsed = (end_tv.tv_sec - begin_tv.tv_sec) * 1000 +
(end_tv.tv_usec - begin_tv.tv_usec) / 1000;

cout << "eclapsed time:" << eclapsed << "ms" << endl;

return 0;
}


上面的代碼在關鍵位置都做了詳細的注釋,相信應該不難看懂。
 

 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved