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     总结分析

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:\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,可以用不用的对象和连接,避免切换。提高性能。

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\n", time);

    printf("curl number %d\n ",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:\Project\CurlHighSpeed\Win32\Release\CurlHighSpeed.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:\Project\CurlHighSpeed\Win32\Release\CurlHighSpeed.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 测试程序源码下载地址

 

posted @ 2020-09-19 12:11  一字千金  阅读(8583)  评论(0编辑  收藏  举报