1. 程式人生 > 實用技巧 >libcurl長連線高併發多執行緒

libcurl長連線高併發多執行緒

libcurl長連線高併發高效能

自己開發了一個股票智慧分析軟體,功能很強大,需要的點選下面的連結獲取:

https://www.cnblogs.com/bclshuai/p/11380657.html

掃碼關注公眾號

目錄

1 背景介紹

2 長短連線實測分析

2.1 長連線引數設定說明

2.2 長短連線區別

2.2.1 短連線

2.2.2 長連線

2.3 長短連線測試分析

2.3.1 短連線呼叫url1

2.3.2 長連線呼叫url1

2.3.3 長連線呼叫url2

2.3.4 長連線呼叫兩次不同的url

2.3.5 總結分析

3.原始碼下載地址

1 背景介紹

專案中需要用到Curl頻繁呼叫的情況,發現curl介面呼叫速度緩慢。為了實現curl高效能,高併發,需要研究如何實現高效能高併發。研究方向有三個。

(1) 長連線。考慮採用長連線的方式去開發。首先研究下長連線和短連線的效能區別。curl內部是通過socket去連線通訊。socket每次連線最為耗時,如果能夠複用連線,長時間連線,減少每次socket連線的時間,則可以大大減少時間,提高效率。

(2) 多執行緒。單個執行緒下載速度畢竟有限,使用多執行緒去呼叫介面。實現高併發高效能,需要考慮資源分配和衝突的問題。

(3) 非同步呼叫。和socket非同步呼叫的原理類似。同步呼叫會阻塞等待,造成CPU佔用率高,電腦卡死等問題。非同步呼叫則是資料接收完成後才會取通知呼叫成功,處理資料。

2 長短連線實測分析

2.1 長連線引數設定說明

Curl提供了三個引數來設定

/* 設定TCP連線為長連線 */

curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);

/* 設定長連線的休眠時間*/

curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 120L);

/* 設定心跳傳送時間,心使得socket長時間保活,小於KEEPIDLE時間 */

curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 20L);

/* 設定連線的超時時間,大於心跳時間*/

curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30);

2.2 長短連線區別

2.2.1 短連線

短連線一般分為4步驟:初始化、設定引數、執行請求、清理資源。即使用curl_easy_setopt設定該curl為長連線,因為最後被curl_easy_cleanup(curl),所以這個socket連線會被中斷銷燬,不會保持長連線。具體步驟如下:

(1)CURL* curl = curl_easy_init();//建立一個curl物件

(2)curl_easy_setopt(curl,……);//可以設定多個引數url,result

(3)res = curl_easy_perform(curl);//執行請求

(4)curl_easy_cleanup(curl);//清除curl

例項程式碼如下:

int CHttpClient::Get(const std::string & strUrl, std::string & strResponse)

{

int res;

CURL* curl = curl_easy_init();

if (NULL == curl)

{

return CURLE_FAILED_INIT;

}

if (m_bDebug)

{

curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);

curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, OnDebug);

}

curl_easy_setopt(curl, CURLOPT_URL, strUrl.c_str());

curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);

curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWriteData);

curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&strResponse);

/* enable TCP keep-alive for this transfer */

curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);

/* keep-alive idle time to 120 seconds */

curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 120L);

/* interval time between keep-alive probes: 60 seconds */

curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 20L);

curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30);

/**

* 當多個執行緒都使用超時處理的時候,同時主執行緒中有sleep或是wait等操作。

* 如果不設定這個選項,libcurl將會發訊號打斷這個wait從而導致程式退出。

*/

//curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);

curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 20);

res = curl_easy_perform(curl);

if (res != 0)

{

//FIRE_ERROR(" Get error %d", res);

}

//CurlMutiTreadMutex::GetInstance()->muti_curl_easy_cleanup(curl);

curl_easy_cleanup(curl);

return res;

}

2.2.2 長連線

長連線是我們建立了curl物件之後,不立刻使用curl_easy_cleanup清理掉,而是儲存起來,下一個請求,只要重新設定url,執行請求,就可以複用以前的socket連線。

示例程式碼如下

標頭檔案

CURL* GetCurl();

CURL* CreateCurl();

void PutCurl(CURL* curl);

QVector<CURL*> m_VectCurl;

QMutex m_mutex;

原始檔

CURL* RestClientPool::GetCurl()

{

CURL* curl = NULL;

m_mutex.lock();

if (m_VectCurl.size()>0)

{

curl = m_VectCurl.front();

m_VectCurl.pop_front();

}

m_mutex.unlock();

if(curl==NULL)

{

curl = CreateCurl();

}

return curl;

}

CURL* RestClientPool::CreateCurl()

{

CURL* curl = curl_easy_init();

if (NULL == curl)

{

return NULL;

}

if (m_bDebug)

{

curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);

curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, OnDebug);

}

//curl_easy_setopt(curl, CURLOPT_URL, strUrl.c_str());

curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);

curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWriteData);

//curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&strResponse);

/* enable TCP keep-alive for this transfer */

curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);

/* keep-alive idle time to 120 seconds */

curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 300L);

/* interval time between keep-alive probes: 60 seconds */

curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 20L);

curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30);

/**

* 當多個執行緒都使用超時處理的時候,同時主執行緒中有sleep或是wait等操作。

* 如果不設定這個選項,libcurl將會發訊號打斷這個wait從而導致程式退出。

*/

curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);

curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 20);

return curl;

}

void RestClientPool::PutCurl(CURL* curl)

{

m_mutex.lock();

m_VectCurl.push_back(curl);

m_mutex.unlock();

}

int RestClientPool::Get(const std::string & strUrl, std::string & strResponse)

{

int res;

//CURL* curl = CurlMutiTreadMutex::GetInstance()->muti_curl_easy_init();

CURL* curl = GetCurl();

if (NULL == curl)

{

return CURLE_FAILED_INIT;

}

curl_easy_setopt(curl, CURLOPT_URL, strUrl.c_str());

curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&strResponse);

res = curl_easy_perform(curl);

if (res != 0)

{

printf("req error %d",res);

}

PutCurl(curl);

return res;

}

2.3 長短連線測試分析

用上述的長連線和短連線進行測試,分四種情況進行測試分析。

(1) shot連線迴圈呼叫1000次url1;

(2) long連線迴圈呼叫1000次url1;

(3) long連線迴圈呼叫1000次url2;

(4) long連線迴圈呼叫1000次,每次迴圈中各呼叫一次url1和一次url2;

測試程式程式碼

#include <QtCore/QCoreApplication>

#include"RestClientPool.h"

#include "RestClient.h"

#include <QDateTime>

#include <string>

using namespace std;

int main(int argc, char *argv[])

{

QCoreApplication a(argc, argv);

CHttpClient m_shotclient;

RestClientPool m_longClient;

QDateTime StartTime = QDateTime::currentDateTime();

string strUrl = "http://qt.gtimg.cn/q=sz002415";

string strUrl2= "http://hq.sinajs.cn/list=sz002415";

string strResponse = "";

for (int i=0;i<1000;i++)

{

m_longClient.Get(strUrl, strResponse);

m_longClient.Get(strUrl, strResponse);

}

QDateTime timeEnd = QDateTime::currentDateTime();

int time = timeEnd.toTime_t()- StartTime.toTime_t();

printf("using time %d", time);

return a.exec();

}

2.3.1 短連線呼叫url1

如下圖所示,短連線每次呼叫都會建立一個socket連線。

輸出

[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60102[WSPConnect] Socket ip 127.0.0.1:60104執行緒 0x89b4 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60107[WSPConnect] Socket ip 127.0.0.1:60109執行緒 0x8de8 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60112[WSPConnect] Socket ip 127.0.0.1:60114執行緒 0x7d20 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60118[WSPConnect] Socket ip 127.0.0.1:60120執行緒 0x7e1c 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60124[WSPConnect] Socket ip 127.0.0.1:60126執行緒 0xa328 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60129[WSPConnect] Socket ip 127.0.0.1:60132執行緒 0x9a68 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60137[WSPConnect] Socket ip 127.0.0.1:60140執行緒 0xbd80 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60151[WSPConnect] Socket ip 127.0.0.1:60153執行緒 0x7360 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60156[WSPConnect] Socket ip 127.0.0.1:60158執行緒 0xbfac 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60161[WSPConnect] Socket ip 127.0.0.1:60163執行緒 0xd18 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60166[WSPConnect] Socket ip 127.0.0.1:60168執行緒 0x8ca8 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60171[WSPConnect] Socket ip 127.0.0.1:60174執行緒 0xbc88 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60177[WSPConnect] Socket ip 127.0.0.1:60179執行緒 0x90b0 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60183[WSPConnect] Socket ip 127.0.0.1:60185執行緒 0x8c38 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60189[WSPConnect] Socket ip 127.0.0.1:60191執行緒 0xa8d0 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60194[WSPConnect] Socket ip 127.0.0.1:60196執行緒 0x76a0 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60200[WSPConnect] Socket ip 127.0.0.1:60202執行緒 0x7c6c 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60205[WSPConnect] Socket ip 127.0.0.1:60208執行緒 0x8618 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60211[WSPConnect] Socket ip 127.0.0.1:60213執行緒 0xa300 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60218[WSPConnect] Socket ip 127.0.0.1:60220執行緒 0xa3f8 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60223[WSPConnect] Socket ip 127.0.0.1:60225執行緒 0xb81c 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60228[WSPConnect] Socket ip 127.0.0.1:60230執行緒 0xa554 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 127.0.0.1:60233[WSPConnect] Socket ip 127.0.0.1:60235執行緒 0xa0f0 已退出,返回值為 0 (0x0)。

2.3.2 長連線呼叫url1

如下圖所示,長連線呼叫1000次url,只建立了一個socket連線。所用的時間也大幅減少,只有27秒的時間。

輸出

WSPStartup ===> D:\Project\CurlHighSpeed\Win32\Release\CurlHighSpeed.exe使用鏈式SPI[WSPConnect] Socket ip 127.0.0.1:60584[WSPConnect] Socket ip 127.0.0.1:60586WSPStartup ===> D:\Project\CurlHighSpeed\Win32\Release\CurlHighSpeed.exe“CurlHighSpeed.exe”(Win32): 已載入“C:\Program Files (x86)\Sangfor\SSL\ClientComponent\2_SangforNsp.dll”。模組已生成,不包含符號。
“CurlHighSpeed.exe”(Win32): 已解除安裝“C:\Program Files (x86)\Sangfor\SSL\ClientComponent\2_SangforNsp.dll”
“CurlHighSpeed.exe”(Win32): 已載入“C:\Program Files (x86)\Sangfor\SSL\ClientComponent\2_SangforNsp.dll”。模組已生成,不包含符號。
“CurlHighSpeed.exe”(Win32): 已載入“C:\Windows\SysWOW64\dbghelp.dll”。“包括”/“排除”設定禁用了載入功能。
“CurlHighSpeed.exe”(Win32): 已載入“C:\Windows\SysWOW64\rasadhlp.dll”。“包括”/“排除”設定禁用了載入功能。
“CurlHighSpeed.exe”(Win32): 已載入“C:\Windows\SysWOW64\FWPUCLNT.DLL”。“包括”/“排除”設定禁用了載入功能。
“CurlHighSpeed.exe”(Win32): 已載入“C:\Windows\SysWOW64\bcrypt.dll”。“包括”/“排除”設定禁用了載入功能。
執行緒 0x9adc 已退出,返回值為 0 (0x0)。
[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 120.204.10.232:80[WSPConnect] Socket ip 120.204.10.232:80“CurlHighSpeed.exe”(Win32): 已載入“C:\Windows\SysWOW64\uxtheme.dll”。“包括”/“排除”設定禁用了載入功能。

2.3.3 長連線呼叫url2

如下圖所示呼叫不同的url2,呼叫1000次,用時40秒。所用的時間和url1是不同的,這個和請求的伺服器以及請求的資料不一致,所以會有不同的耗時。

WSPStartup ===> D:\Project\CurlHighSpeed\Win32\Release\CurlHighSpeed.exe使用鏈式SPI[WSPConnect] Socket ip 127.0.0.1:58122[WSPConnect] Socket ip 127.0.0.1:58126

WSPStartup ===> D:\Project\CurlHighSpeed\Win32\Release\CurlHighSpeed.exe“CurlHighSpeed.exe”(Win32): 已載入“C:\Program Files (x86)\Sangfor\SSL\ClientComponent\2_SangforNsp.dll”。模組已生成,不包含符號。

2.3.4 長連線呼叫兩次不同的url

如下圖所示,長連線呼叫兩個不同的url。會建立兩個socket連線。不會因為切換不同的url,重新建立socket連線。對於每個url會對應一個socket連線。用時82秒,之前分別呼叫url1和url2所用的時間之和是27+40=67秒,多出來的15秒時間,應該是連線之間的切換時間,所以為了減少時間,可以一種url,用一個curl物件,避免切換。

WSPStartup ===> D:\Project\CurlHighSpeed\Win32\Release\CurlHighSpeed.exe使用鏈式SPI[WSPConnect] Socket ip 127.0.0.1:58345[WSPConnect] Socket ip 127.0.0.1:58347

2.3.5 總結分析

呼叫情況

用時

connect連線次數

請求次數

單次用時

Shot連線url1

147秒

1000次

1000

0.147

Long連線url1

27秒

1次

1000

0.027

Long 呼叫url2

40秒

1次

1000

0.04

Long url1和url2

82秒

2次

2000

0.041

綜上所述可以得出結論:

(1) curl初始化,設定引數、呼叫url、清理cleanup,整個過程會建立一個socket連線。可以先建立,設定為長連線,不清理cleanup,重複使用該curl物件,複用已建立的curl物件和socket連線。可以提高5倍的速度。

(2) 呼叫不同的url,會因為伺服器效能和請求資料量,耗時也會不同。

(3) 一個長連線curl呼叫兩個不同的url(不同的網址),會建立兩個socket連線。保持兩個socket長連線。不會因為切換不同的url,而重複建立socket連線。切換連線會造成耗時,降低速度20%左右。所以對不同的url,可以用不用的物件和連線,避免切換。提高效能。

3.原始碼下載連結

https://download.csdn.net/download/baochunlei1/12863616

https://download.csdn.net/download/baochunlei1/12863616