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 总结分析
2.3.6 源码下载地址
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:ProjectCurlHighSpeedWin32ReleaseCurlHighSpeed.exe使用链式SPI[WSPConnect] Socket ip 127.0.0.1:60584[WSPConnect] Socket ip 127.0.0.1:60586WSPStartup ===> D:ProjectCurlHighSpeedWin32ReleaseCurlHighSpeed.exe“CurlHighSpeed.exe”(Win32): 已加载“C:Program Files (x86)SangforSSLClientComponent2_SangforNsp.dll”。模块已生成,不包含符号。 “CurlHighSpeed.exe”(Win32): 已卸载“C:Program Files (x86)SangforSSLClientComponent2_SangforNsp.dll” “CurlHighSpeed.exe”(Win32): 已加载“C:Program Files (x86)SangforSSLClientComponent2_SangforNsp.dll”。模块已生成,不包含符号。 “CurlHighSpeed.exe”(Win32): 已加载“C:WindowsSysWOW64dbghelp.dll”。“包括”/“排除”设置禁用了加载功能。 “CurlHighSpeed.exe”(Win32): 已加载“C:WindowsSysWOW64 asadhlp.dll”。“包括”/“排除”设置禁用了加载功能。 “CurlHighSpeed.exe”(Win32): 已加载“C:WindowsSysWOW64FWPUCLNT.DLL”。“包括”/“排除”设置禁用了加载功能。 “CurlHighSpeed.exe”(Win32): 已加载“C:WindowsSysWOW64crypt.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:WindowsSysWOW64uxtheme.dll”。“包括”/“排除”设置禁用了加载功能。
2.3.3 长连接调用url2
如下图所示调用不同的url2,调用1000次,用时40秒。所用的时间和url1是不同的,这个和请求的服务器以及请求的数据不一致,所以会有不同的耗时。
WSPStartup ===> D:ProjectCurlHighSpeedWin32ReleaseCurlHighSpeed.exe使用链式SPI[WSPConnect] Socket ip 127.0.0.1:58122[WSPConnect] Socket ip 127.0.0.1:58126
WSPStartup ===> D:ProjectCurlHighSpeedWin32ReleaseCurlHighSpeed.exe“CurlHighSpeed.exe”(Win32): 已加载“C:Program Files (x86)SangforSSLClientComponent2_SangforNsp.dll”。模块已生成,不包含符号。
2.3.4 长连接调用两次不同的url
如下图所示,长连接调用两个不同的url。会创建两个socket连接。不会因为切换不同的url,重新创建socket连接。对于每个url会对应一个socket连接。用时82秒,之前分别调用url1和url2所用的时间之和是27+40=67秒,多出来的15秒时间,应该是连接之间的切换时间,所以为了减少时间,可以一种url,用一个curl对象,避免切换。
WSPStartup ===> D:ProjectCurlHighSpeedWin32ReleaseCurlHighSpeed.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,可以用不用的对象和连接,避免切换。提高性能。
2.3.6 源码下载
https://download.csdn.net/download/baochunlei1/12863616
3. libcurl多线程高并发
3 curl线程池并发执行
多线程一直是提高性能和速度的关键技术,继承QT的QRunable类,定义一个线程任务,用QThreadPool线程池去调用url;
3.1 测试程序
采用如下的程序进行测试,采用毫秒计时。
3.1.1 测试主程序
#include <QtCore/QCoreApplication>
#include"RestClientPool.h"
#include "RestClient.h"
#include <QDateTime>
#include <string>
#include <QThreadPool>
#include "MultTask.h"
extern RestClientPool g_restpool;
using namespace std;
//RestClientPool g_restPool;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
///CHttpClient m_shotclient;
RestClientPool m_longClient;
QDateTime StartTime = QDateTime::currentDateTime();
qint64 istarttimems = StartTime.toMSecsSinceEpoch();
/*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(strUrl2, strResponse);
m_longClient.Get(strUrl, strResponse);
}*/
QThreadPool qThreadPool;
qThreadPool.setMaxThreadCount(10);
for (int i=0;i<1000;i++)
{
MultTask * p = new MultTask();
qThreadPool.start(p);
}
qThreadPool.waitForDone();
QDateTime timeEnd = QDateTime::currentDateTime();
qint64 iendtimems = timeEnd.toMSecsSinceEpoch();
int time = iendtimems - istarttimems;
//int time = timeEnd.toTime_t()- StartTime.toTime_t();
printf("using time %d ", time);
printf("curl number %d ",g_restpool.getcurlsize());
return a.exec();
}
3.1.2 线程类定义
线程类头文件
#ifndef MULTTASK_H
#define MULTTASK_H
#include <QObject>
#include <QRunnable>
#include "RestClientPool.h"
class MultTask : public QObject,public QRunnable
{
Q_OBJECT
public:
MultTask();
~MultTask();
void run();
private:
};
#endif // MULTTASK_H
线程类源文件
#include "MultTask.h"
#include<string>
using namespace std;
RestClientPool g_restpool;//全局变量
MultTask::MultTask()
{
setAutoDelete(true);
}
MultTask::~MultTask()
{
}
void MultTask::run()
{
string strUrl = "http://qt.gtimg.cn/q=sz002415";
string strResponse = "";
g_restpool.Get(strUrl,strResponse);
}
1.1.3 运行测试结果
如下图所示,采用10个线程去调用1000次url1。用时3868毫秒。创建curl的数量是10个,创建的socket连接的数量是10个。平均每次调用时间是0.003868秒。而单线程平均每次调用耗时0.027秒。按理说10个线程,每次调用应该是0.0027秒,但是0.003868秒大于0.0027秒。线程之间的资源竞争和切换也会耗时。而且1000次调用接口中会出现不定数量的错误6,错误码解释是CURLE_COULDNT_RESOLVE_HOST(6)无法bai解析主机。给定的远程主机没有得到解决。可能是多线程访问太快,服务器无法响应。
输出框中显示的创建的10个socket连接:
WSPStartup ===> D:ProjectCurlHighSpeedWin32ReleaseCurlHighSpeed.exe使用链式SPI[WSPConnect] Socket ip 127.0.0.1:54517[WSPConnect] Socket ip 127.0.0.1:54518[WSPConnect] Socket ip 127.0.0.1:54520[WSPConnect] Socket ip 127.0.0.1:54522[WSPConnect] Socket ip 127.0.0.1:54524[WSPConnect] Socket ip 127.0.0.1:54525[WSPConnect] Socket ip 127.0.0.1:54527[WSPConnect] Socket ip 127.0.0.1:54531[WSPConnect] Socket ip 127.0.0.1:54533[WSPConnect] Socket ip 127.0.0.1:54536WSPStartup ===> D:ProjectCurlHighSpeedWin32ReleaseCurlHighSpeed.exe[WSPConnect] Socket ip 127.0.0.1:54540[WSPConnect] Socket ip 127.0.0.1:54539[WSPConnect] Socket ip 127.0.0.1:54542[WSPConnect] Socket ip 127.0.0.1:54544[WSPConnect] Socket ip 127.0.0.1:54546[WSPConnect] Socket ip 127.0.0.1:54551[WSPConnect] Socket ip 127.0.0.1:54553“CurlHighSpeed.exe”
3.1.4 不同线程数量调用耗时
为了研究线程数量和调用耗时的关系,采用不同的线程数量去执行10000次的调用;每次消耗的时间如下所示。随着线程数量的增加,多线程处理速度和性能会大幅提高。但是当线程数量达到一定数量之后,线程池的性能反而下降,这是因为线程之间的竞争资源和线程CPU切换导致的。
线程数量 |
总用时(ms) |
每次用时(ms) |
Curl数量 |
错误数 |
10 |
27690 |
2.7690 |
10 |
6 |
20 |
18080 |
1.808 |
20 |
8 |
50 |
9593 |
0.9593 |
50 |
7 |
100 |
6200 |
0.62 |
100 |
9 |
200 |
7183 |
0.7183 |
200 |
19 |
300 |
12431 |
1.2431 |
300 |
11 |
400 |
11687 |
11687 |
400 |
12 |
500 |
21990 |
2.1990 |
500 |
13 |
3.1.5 测试程序源码下载地址