介绍  

    主页https://curl.haxx.se/libcurl/ 

libcurl是一个免费,易用的客户端传输库,支持DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, Telnet and TFTP等协议。libcurl支持SSL认证,HTTP POST, HTTP PUT, FTP 上传, HTTP form based upload, proxies, cookies,用户名+密码认证(Basic, Digest, NTLM, Negotiate, Kerberos),文件断点续传,http 代理转发。

相比于Boost asio网络库,具有如下的优点
1)超时时间设置简单
2)HTTP的摘要认证已经实现
3)HTTPS支持的很好
4)HTTP交互实现的非常好


Linux编译

git clone https://github.com/curl/curl.git
 mkdir build
 cd build
 cmake ..  -DCMAKE_INSTALL_PREFIX=/opt/cmms/3thrdparty/libcurl/
cmake -DCMAKE_INSTALL_PREFIX=/opt/cmms/3thrdparty/libcurl/ -DOPENSSL_ROOT_DIR=/opt/cmms/3thrdparty/openssl/ -DOPENSSL_LIBRARIES="/opt/cmms/3thrdparty/openssl/lib/libssl.so;/opt/cmms/3thrdparty/openssl/lib/libcrypto.so" -DOPENSSL_INCLUDE_DIR=/opt/cmms/3thrdparty/openssl/include ..

cmake -DCMAKE_INSTALL_PREFIX=/opt/cmms/3thrdparty/libcurl/ -DOPENSSL_ROOT_DIR=/opt/cmms/3thrdparty/openssl/ -DOPENSSL_LIBRARIES="/opt/cmms/3thrdparty/openssl/lib/libssl.so;/opt/cmms/3thrdparty/openssl/lib/libcrypto.so" -DOPENSSL_INCLUDE_DIR=/opt/cmms/3thrdparty/openssl/include ..

指定openssl的库文件路径,当前采用的是1.0.2h版本

指定RPATH路径

项目工程依赖特定版本的OpenSSL,其依赖的libssl.so和libcrypto.so,放置在可执行程序同级目录,因此需要指定RPATH

打开CMakeList.txt文件,修改如下;

project(CURL C)
#set(CMAKE_SKIP_BUILD_RPATH FALSE)
#set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
set(CMAKE_INSTALL_RPATH "./")

在project(CURL C)下一行,添加set(CMAKE_INSTALL_RPATH "./"),其中

set(CMAKE_SKIP_BUILD_RPATH FALSE)

set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)

可加可不加,不影响

校验编译结果:

root@aaa-pc:/libcurl/lib# readelf -d libcurl.so

Dynamic section at offset 0xf8c60 contains 30 entries:
  标记        类型                         名称/值
 0x0000000000000001 (NEEDED)             共享库:[libpthread.so.0]
 0x0000000000000001 (NEEDED)             共享库:[libssl.so.1.1]
 0x0000000000000001 (NEEDED)             共享库:[libcrypto.so.1.1]
 0x0000000000000001 (NEEDED)             共享库:[libc.so.6]
 0x0000000000000001 (NEEDED)             共享库:[ld-linux-aarch64.so.1]
 0x000000000000000e (SONAME)             Library soname: [libcurl.so.4]
 0x000000000000001d (RUNPATH)            Library runpath: [./]

Library runpath: [./] 说明修改成功


HTTPS支持

     curl-7.32.0(2013-08-12 00:17)版本不支持HTTPS,因此需要升级到curl-7.61.0(2018-07-11 08:01)版本。目前在进行HTTPS开发的时候,遇到curl_easy_perform函数执行之后,返回CURLE_UNSUPPORTED_PROTOCOL错误,
使用的SSL代码协议是curl_easy_setopt(hnd, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);


小知识

    curl 命令行依赖 openssl 库才能使用 ssl 和 TLS。当前一般认为 TLSv1.1 及 TLSv1.2 才是安全的,很多 https 服务器仅支持这2个协议,不再支持 TLSv1.0 及 ssl。但是 openssl 是从 1.0.1 才支持 TLSv1.1 及 TLSv1.2。
当在代码中只是出现如下的SSL定义的宏
enum {
  CURL_SSLVERSION_DEFAULT,
  CURL_SSLVERSION_TLSv1,
  CURL_SSLVERSION_SSLv2,
  CURL_SSLVERSION_SSLv3,
  CURL_SSLVERSION_LAST /* never use, keep last */
};
说明该版本不支持HTTPS协议,需要升级curl版本

利用Postman快速生成代码


curl_global_init初始化

如果调用curl库函数的时候,没有调用curl_global_init进行curl全局变量的初始化,会在curl_easy_init函数中,进行初始化调用


curl_easy_escapeURL编码

前言

不仅仅是URL存在中文需要进行编码,而且表单中存在中文的,也需要进行编码,还有编码前的中文必须是UTF-8编码,如果是GBK中文编码,还需要转为UTF-8编码

httpheader要求中文必须为iso8859-1编码解读


场景

        对接HTTP服务器,对方要求将UTF-8编码的字符串浙FN358Y转换ISO8859-1编码

有时候,为了让中文字符适应某些特殊要求(如httpheader要求其内容必须为iso8859-1编码),可能会通过将中文字符按照字节方式来编码的情况,如:

String s_iso88591 = newString("中".getBytes("UTF-8"),"ISO8859-1"),这样得到的s_iso8859-1字符串实际是三个在ISO8859-1中的字符,在将这些字符传递到目的地后,目的地程序再通过相反的方式Strings_utf8 = newString(s_iso88591.getBytes("ISO8859-1"),"UTF-8")来得到正确的中文汉字"中",这样就既保证了遵守协议规定、也支持中文。

1)iconv指令

-f指的是原始文件编码,-t是输出编码 ,-c 指的是从输出中忽略无效的字符, --verbose指的是打印进度信息   -o是输出文件

1.指令转码

[root@taishan-atlas home]# iconv  --verbose -f utf-8 -t ISO_8859-1 input.txt -o output.txt

input.txt:

iconv: illegal input sequence at position 0

2.假如忽略无效字符,直接把前面的浙中文忽略了

[root@taishan-atlas home]# iconv -c   --verbose -f utf-8 -t ISO_8859-1 input.txt -o output.txt

input.txt:

[root@taishan-atlas home]# cat output.txt 

FN358Y[root@taishan-atlas home]# 

3不用转换.GB2312就用ISO8859-1

4 Java编码异常

new String(byte[],decode)方法

new String(byte[],decode)方法

而与getBytes相对的,可以通过new String(byte[], decode)的方式来还原这个"中"字,

这个new String(byte[],decode)实际是使用指定的编码decode来将byte[]解析成字符串.

String s_gbk = new String(b_gbk,"GBK");

String s_utf8 = new String(b_utf8,"UTF-8");

String s_iso88591 = new String(b_iso88591,"ISO8859-1");

通过输出s_gbk、s_utf8和s_iso88591,会发现s_gbk和s_utf8都是"中",而只有s_iso88591是一个不被识别的字符(可以理解为乱码),为什么使用ISO8859-1编码再组合之后,无法还原"中"字?原因很简单,因为ISO8859-1编码的编码表根本就不包含汉字字符,当然也就无法通过"中".getBytes("ISO8859-1");来得到正确的"中"字在ISO8859-1中的编码值了,所以,再通过newString()来还原就更是无从谈起。

因此,通过String.getBytes(Stringdecode)方法来得到byte[]时,一定要确定decode的编码表中确实存在String表示的码值,这样得到的byte[]数组才能正确被还原。


解决方案

            实际上iso8859-1编码就是针对中文进行URL编码,通过调用

char* pszEnCodeFilter = curl_easy_escape(NULL, strFilterContent.c_str(), strFilterContent.length());

curl_free(pszEnCodeFilter);

解决问题

注意:在百度搜索中文的时候,实际上也行了URL编码

延申 :

实际URL包含特殊符号,也需要进行URL编码,例如#号


简单函数

string urlEncode(string str){
    string new_str = "";
    char c;
    int ic;
    const char* chars = str.c_str();
    char bufHex[10];
    int len = strlen(chars);

    for(int i=0;i<len;i++){
        c = chars[i];
        ic = c;
        // uncomment this if you want to encode spaces with +
        /*if (c==' ') new_str += '+';   
        else */if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') new_str += c;
        else {
            sprintf(bufHex,"%X",c);
            if(ic < 16) 
                new_str += "%0"; 
            else
                new_str += "%";
            new_str += bufHex;
        }
    }
    return new_str;
 }

string urlDecode(string str){
    string ret;
    char ch;
    int i, ii, len = str.length();

    for (i=0; i < len; i++){
        if(str[i] != '%'){
            if(str[i] == '+')
                ret += ' ';
            else
                ret += str[i];
        }else{
            sscanf(str.substr(i + 1, 2).c_str(), "%x", &ii);
            ch = static_cast<char>(ii);
            ret += ch;
            i = i + 2;
        }
    }
    return ret;
}


Libcurl自带URL编码和解码函数

/*
 * Unescapes the given URL escaped string of given length. Returns a
 * pointer to a malloced string with length given in *olen.
 * If length == 0, the length is assumed to be strlen(string).
 * If olen == NULL, no output length is stored.
 */
char *curl_easy_unescape(struct Curl_easy *data, const char *string,
                         int length, int *olen)
char *curl_easy_escape(struct Curl_easy *data, const char *string,
                       int inlength)


处理Set-Cookie报头

利用libcurl模拟Postman发送数据给服务器,代码是直接点击Postman的Code按钮自动生成的代码,Wireshark抓包数据显示需要进行重定向

POST /proj/assets/list HTTP/1.1
Host: 127.0.0.1:7000
Accept: */*
Content-Type: application/json
userId: 55684e2cd21245299949af67ecabdd20
Content-Length: 58
{
"page":{
  "current":1,
  "size":10
},
"data":{}
}HTTP/1.1 302 
Set-Cookie: JSESSIONID=6B8313FECDDF057CEDBCA4BE9925B4EB; Path=/proj; HttpOnly
Content-Type: application/json;charset=UTF-8
Content-Length: 81
Date: Tue, 25 May 2021 01:55:13 GMT
{"data":"http://127.0.0.1:7000/proj/login.html","msg":"重定向","status":"302"}

通过Postman工具发送数据,Wireshark抓包,成功返回数据

POST /proj/assets/list HTTP/1.1
userId: 55684e2cd21245299949af67ecabdd20
Content-Type: application/json
cache-control: no-cache
Postman-Token: 7aa702be-390d-4eda-a75a-03758791e0ec
User-Agent: PostmanRuntime/7.6.0
Accept: */*
Host: 127.0.0.1:7000
cookie: JSESSIONID=6BA1E3E9C57EB191089EFA62A048FF60
accept-encoding: gzip, deflate
content-length: 58
Connection: keep-alive
{
"page":{
  "current":1,
  "size":10
},
"data":{}
}

解决方案

            经过不断的添加头条件,最终确认只需要添加cookie: JSESSIONID=6BA1E3E9C57EB191089EFA62A048FF60,就能够成功获取数据

基础知识:

web开发者应该都知道,http协议是无状态的,浏览器发送请求,服务器返回响应报文给浏览器,响应的Response Header中的Set-Cookie就是告诉浏览器为当前页面设置cookie。比如:响应头中有Set-Cookie: username=JasonChi,那么浏览器会在当前页面所在域名设置cookie字符串。当浏览器再次发送请求时,浏览器默认会自动将cookie中的字符串放在请求头中的Cookie项中发送给Web服务器。

解决方案

            当服务器返回Set-Cookie报头字段,需要获取该字段的值,每一次发送给服务器的信息中,报头必须携带,否则返回302重定向错误


curl_easy_perform重连机制

curl_easy_perform内部实现了服务器连接,数据发送,重连等机制,前面调用的curl_easy_init也仅仅是简单的初始化一些变量。如果服务器在发送数据之后,关闭了连接,curl_easy_perform在下一次调用的时候,会重新建立连接。如果服务器在发送数据之后,继续保持当前的连接状态,curl_easy_perform可以一直使用已有的连接处理信息


代码

void CAnXunShiIPCController::TestLibCurlHTTPBasicAuth()

{

 CURL *pCurlHandle = curl_easy_init();


 curl_easy_setopt(pCurlHandle, CURLOPT_CUSTOMREQUEST, "GET");

 curl_easy_setopt(pCurlHandle, CURLOPT_URL, "http://192.168.18.84/axis-cgi/com/ptz.cgi?camera=1&query=position");

 curl_easy_setopt(pCurlHandle, CURLOPT_USERPWD, "root:admin12345");

 curl_easy_setopt(pCurlHandle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);

 curl_easy_setopt(pCurlHandle, CURLOPT_WRITEFUNCTION, WriteResponseBody);//设置回调函数                   //curl_easy_setopt(pCurlHandle, CURLOPT_HEADER, 1);//保存HTTP头部信息到strResponseData

 curl_easy_setopt(pCurlHandle, CURLOPT_WRITEDATA, &strResponseData);//设置回调函数的参数,获取反馈信息

 curl_easy_setopt(pCurlHandle, CURLOPT_TIMEOUT, 15);//接收数据时超时设置,如果10秒内数据未接收完,直接退出

 curl_easy_setopt(pCurlHandle, CURLOPT_MAXREDIRS, 1);//查找次数,防止查找太深

 curl_easy_setopt(pCurlHandle, CURLOPT_CONNECTTIMEOUT, 5);//连接超时,这个数值如果设置太短可能导致数据请求不到就断开了


 while (1)

 {

 CURLcode nRet = curl_easy_perform(pCurlHandle);

 if (0 == nRet)

 {

  std::cout << strResponseData << std::endl;

 }

 else

 {

  std::cout << "执行失败,开始退出" << std::endl;

  break;

 }

 ::Sleep(100);

 }

 curl_easy_cleanup(pCurlHandle);

}


指定返回报文格式

请求数据的时候,都应该携带上返回报文的格式,否则无法返回有效的数据
 struct curl_slist *pCurlHeadList = NULL;
 pCurlHeadList = curl_slist_append(pCurlHeadList, "content-type: application/json");
 curl_easy_setopt(pCurlHandle, CURLOPT_HTTPHEADER, pCurlHeadList);
当前在对接ASP网页后端的时候,必须指定传输文本格式


multipart/formdata表单使用

场景
         multipart/form-data是浏览器用表单上传文件的方式。最常见的情境是:在写邮件时,向邮件后添加附件,附件通常使用表单添加,也就是用multipart/form-data格式上传到服务器。Http服务器定义了上传数据的格式,接口地址 http://10.10.10.10:80/restful/personInfo,参数如下:
msg:{
   "name" : "fengyuzaitu",
   "data" : {
      "id" : "9191"
   },
   "sex" : "1",
   "type" : "worker"
}

代码
int PostHttpFormDataByLibCurl()
{
 Json::Value root;
 root["type"] = "worker";
 root["sex"] = "1";
 root["name"] = "fengyuzaitu";
 Json::Value data;
 data["id"] = "9191";
 root["data"] = data;
 std::string strPostData = root.toStyledString();
 CURL *pCurlHandle = curl_easy_init();
 std::string strResponseData;
 curl_easy_setopt(pCurlHandle, CURLOPT_CUSTOMREQUEST, "POST");
 curl_easy_setopt(pCurlHandle, CURLOPT_URL, "http://10.10.10.10:80/restful/personInfo");
 curl_easy_setopt(pCurlHandle, CURLOPT_WRITEFUNCTION, WriteResponseBody);//设置回调函数
 curl_easy_setopt(pCurlHandle, CURLOPT_WRITEDATA, &strResponseData);//设置回调函数的参数,获取反馈信息
 struct curl_httppost *pFormPost = 0;
 struct curl_httppost *pLastPtrFormPost = 0;
 curl_formadd(&pFormPost, &pLastPtrFormPost, CURLFORM_COPYNAME, "msg", CURLFORM_COPYCONTENTS, strPostData.c_str(), CURLFORM_END);
 curl_easy_setopt(pCurlHandle, CURLOPT_HTTPPOST, pFormPost);
 CURLcode nRet = curl_easy_perform(pCurlHandle);
 if (0 == nRet)
 {
  std::cout << strResponseData << std::endl;
 }
 curl_formfree(pFormPost);
 curl_easy_cleanup(pCurlHandle);
 return nRet;
}

报文
POST /restful/personInfo HTTP/1.1
Host: 10.10.10.10:80
Accept: */*
Content-Length: 254
Expect: 100-continue
Content-Type: multipart/form-data; boundary=------------------------2630a8c6c773b062
HTTP/1.1 100
--------------------------2630a8c6c773b062
Content-Disposition: form-data; name="msg"
{
   "name" : "fengyuzaitu",
   "data" : {
      "id" : "9191"
   },
   "sex" : "1",
   "type" : "worker"
}
--------------------------2630a8c6c773b062--

备注
        这种表单上传数据的方式,也可以通过Content-Type: application/x-www-form-urlencoded的方式进行上传
代码
int PostFormDataByUrlEncode()
{
 Json::Value root;
 root["type"] = "worker";
 root["sex"] = "1";
 root["name"] = "fengyuzaitu";
 Json::Value data;
 data["id"] = "9191";
 root["data"] = data;
 std::string strUrl = root.toStyledString();
 CURL *pCurlHandle = curl_easy_init();
 std::string strResponseData;
 curl_easy_setopt(pCurlHandle, CURLOPT_CUSTOMREQUEST, "POST");
 curl_easy_setopt(pCurlHandle, CURLOPT_URL, "http://10.10.10.10:80/restful/personInfo");
 struct curl_slist *pCurlList = NULL;
 //指定文本url编码
 pCurlList = curl_slist_append(pCurlList, "Content-Type: application/x-www-form-urlencoded");
 curl_easy_setopt(pCurlHandle, CURLOPT_HTTPHEADER, headers);
 curl_easy_setopt(pCurlHandle, CURLOPT_WRITEFUNCTION, WriteResponseBody);//设置回调函数
 curl_easy_setopt(pCurlHandle, CURLOPT_WRITEDATA, &strResponseData);//设置回调函数的参数,获取反馈信息
 char* pszEncodeAuth = curl_easy_escape(pCurlHandle, strUrl.c_str(), strUrl.length());
 std::string strEncodeAuth = pszEncodeAuth;
 //释放申请的内存
 curl_free(pszEncodeAuth);
 std::string strPostUrlEncodeData = "msg=" + strEncodeAuth;
 curl_easy_setopt(pCurlHandle, CURLOPT_POSTFIELDS, strPostUrlEncodeData.c_str());
 CURLcode nRet = curl_easy_perform(pCurlHandle);
 std::cout << strResponseData << std::endl;
 curl_slist_free_all(pCurlList);
 curl_easy_cleanup(pCurlHandle);
 return nRet;
}

注意:
 std::string strPostUrlEncodeData = "msg=" + strEncodeAuth; 这里的=不能使用:,否则无法解析通过


x-www-form-urlencoded使用

背景

将键值对的参数用&连接起来,如果有空格,将空格转换为+加号;有特殊符号,将特殊符号转换为ASCII HEX值 。参数的格式为key=value&key=value

场景

    当HTTP交互中,服务器端指定了application/x-www-form-urlencoded的Content-Type类型,需要对Body报文实体进行url编码。libcurl提供了curl_easy_escape


内容

没有经过url编码的原始数据如下:

authorJson={
   "loginAccount" : "root"
}
&parmJson={
   "operatorname" : "PostName",
   "params" : {
      "name" : "fengyuzaitu"
   }
}

代码

static void TestPostOfRestfulInterfaceByUrlEncode()
 {
  CURL *pCurlHandle = curl_easy_init();
  curl_easy_setopt(pCurlHandle, CURLOPT_CUSTOMREQUEST, "POST");
  curl_easy_setopt(pCurlHandle, CURLOPT_URL, "http://192.168.10.85:8080/cs/restfull/operationRestfullApi/excuteSqlByCode");
  struct curl_slist *pCurlList = NULL;
  pCurlList = curl_slist_append(pCurlList, "Content-Type: application/x-www-form-urlencoded");//指定文本url编码
  curl_easy_setopt(pCurlHandle, CURLOPT_HTTPHEADER, pCurlList);
  Json::Value jsonAuthen;
  jsonAuthen["loginAccount"] = "root";
  std::string strAuth = jsonAuthen.toStyledString();
//对参数authorJson的内容:{ "loginAccount" : "root"}  ,进行url编码
char* pszEncodeAuth = curl_easy_escape(pCurlHandle, strAuth.c_str(), strAuth.length());
  std::string strEncodeAuth = pszEncodeAuth;
//释放申请的内存
  curl_free(pszEncodeAuth);
  Json::Value jsonContent;
  jsonContent["operatorname"] = "PostName";
  Json::Value jsonParams;
  jsonParams["name"] = "fengyuzaitu";
  jsonContent["params"] = jsonParams;
  std::string strParams = jsonContent.toStyledString();
  char* pszParams = curl_easy_escape(pCurlHandle, strParams.c_str(), strParams.length());
  std::string strEncodeParams = pszParams;
  curl_free(pszParams);
  std::string strPostData = "authorJson=" + strAuth + "&parmJson=" + strParams;
  curl_easy_setopt(pCurlHandle, CURLOPT_POSTFIELDS, strPostUrlEncodeData.c_str());
  CURLcode nRet = curl_easy_perform(pCurlHandle);
  if (0 == nRet)
  {
   std::cout << "Post message successfully" << std::endl;
  }
  curl_slist_free_all(pCurlList);
  curl_easy_cleanup(pCurlHandle);
 }


  std::string strPostUrlEncodeData = "authorJson=" + strEncodeAuth + "&parmJson=" + strEncodeParams;

编码结果

经过url编码的数据如下:authorJson=%7B%0A%20%20%20%22loginAccount%22%20%3A%20%22root%22%0A%7D%0A&parmJson=%7B%0A%20%20%20%22operatorname%22%20%3A%20%22PostName%22%2C%0A%20%20%20%22params%22%20%3A%20%7B%0A%20%20%20%20%20%20%22name%22%20%3A%20%22fengyuzaitu%22%0A%20%20%20%7D%0A%7D%0A


注意

目前在某个现场将2020-05/2020052715490000-0.jpg_2020-05/2020052715490000-0.jpg字符串通过urlencode编码之后,写库失败,一旦/编码之后入库失败,所以单独写了一个编码函数,取消对/的编码

编码说明

1编码说明

网页中的表单使用POST方法提交时,数据内容的类型是 application/x-www-form-urlencoded,这种类型会:

1.字符"a"-"z","A"-"Z","0"-"9",".","-","*",和"_"都不会被编码;

2.将空格转换为加号 (+) ;

3.将非文本内容转换成"%xy"的形式,xy是两位16进制的数值;

4.在每个 name=value 对之间放置 & 符号。

*/

 诸如字符: / & ? @ # ; $ + = 和 %也可以被使用,但是它们各有其特殊的用途,如果一个文件名包括了这些字符( / & ? @ # ; $ + = %),

这些字符和所有其他字符就应该被编码。

 编码过程非常简单,任何字符只要不是ASCII码数字,字母,或者前面提到的标点符,它们都将被转换成字节形式,每个字节都写成这种形式:

一个“%”后面跟着两位16进制的数值。空格是一个特殊情况,因为它们太平常了。它除了被编码成“%20”以外,还能编码为一个“+”。

加号(+)本身被编码为%2B。当/ # = & 和?作为名字的一部分来使用时,而不是作为URL部分之间的分隔符来使用时,它们都应该被编码。


Content-Length的添加机制

场景

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Length Required</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Length Required</h2>
<hr><p>HTTP Error 411. The request must be chunked or have a content length.</p>
</BODY></HTML>


分析

    说明发送的报文中没有携带Content-Length,但是发送的报文中实体其实是0字节的


原因

    libcurl库只有在调用curl_easy_setopt(pCurlHandle, CURLOPT_POSTFIELDS, "");,才会去自动生成Content-Length

解决方案一 手动指定Content-Length  pCurlHeadList = curl_slist_append(pCurlHeadList, "Content-length: 0");
解决方案二 curl_easy_setopt(pCurlHandle, CURLOPT_POSTFIELDS, "");


libcurl发送达到1秒延时

当POST数据长度大于1024字节,libcurl不会直接发送POST请求,而是会分成两步执行:

  1. 发送一个请求,该请求头部包含一个Expect: 100-continue的字段,用来询问server是否愿意接收数据
  2. 当接收到从server返回的100-continue的应答后,才会真正的发起POST请求,将数据发送给server.

解决方案

  struct curl_slist *pCurlList = NULL;
  pCurlList = curl_slist_append(pCurlList, "expect: ");
  curl_easy_setopt(pCurlHandle, CURLOPT_HTTPHEADER, pCurlList);


TIME_WAIT过多的问题

默认情况下libcurl完成一个任务以后,出于重用连接的考虑不会马上关闭
如果没有新的TCP请求来重用这个连接,那么只能等到CLOSE_WAIT超时,这个时间默认在7200秒甚至更高,太多的CLOSE_WAIT连接会导致性能问题

解决方法:

curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1);


抓包失败分析

场景

       同事使用Postman测试HTTP请求成功,通过Postman工具生成自动化代码,调用libcurl,通过抓包发现无法接收任何回复,报文发送成功。编译代码的时候甚至提示启动程序不存在。

排查

       发现编译的程序不存在,后台查看启动360杀毒软件,禁用杀毒软件,解决问题


内存增长分析

在实际的测试环境中,内存在不断的增长,尽管不是很明显

代码申请内存分析

struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */

                                    int chashsize) /* connection hash */

该函数创建了Curl_muti结构体,并且申请了好几块内存,分别是hostcache,sockhash,conn_cache

void curl_easy_cleanup(struct Curl_easy *data)

Curl_close(data);

内存检测

           采用vld的方式检测

在调用curl_global_cleanup();之后,没有检测到有任何的内存泄漏,目前没有知道是哪里的内存一直保存

curl_global_cleanup 执行两部操作

1)卸载iphlpapi.dll(Windows IP辅助API应用程序接口模块)

 2)卸载Win32Sock : WSACleanup();


0xC0000005: 读取位置 0x00006464 时发生访问冲突

长时间调用libcurl获取数据,异常崩溃,提示如下:

0x7298464D (ucrtbased.dll) (yushivehicleservice.exe.dmp 中)处有未经处理的异常: 0xC0000005: 读取位置 0x00006464 时发生访问冲突。

如有适用于此异常的处理程序,该程序便可安全地继续运行。

libcurl LInux编译使用记录_libcurl

错误码分析

CURLE_URL_MALFORMAT(3)

该网址的格式不正确。 当前项目代码测试返回错误码3,通过postman测试是正常的,根据错误码,查看网址url编写是否正确: http:// 41.190.2.165:8080/jcltfk/api/interface/hzjcARHkgsList?sourceid=9514aebf-3e59-45c3-83b3-5a6f590f2f95 通过仔细分析,发现是http://后面多了空格导致的问题

同事使用LibCurl发送https报文(由于https抓包没有直观看到url是否不合法),返回错误码3,提示网址格式不正确。网址: http:// 41.190.2.165:8080/jcltfk/api/interface/hzjcARHkgsList?sourceid=9514aebf-3e59-45c3-83b3-5a6f590f2f95 &hzjcARHkgsListsourceid=9514aebf-3e59-45c3-83b3-5a6f590f2f95&hzjcARHkgsListsourceid=9514aebf-3e59-45c3-83b3-5a6f590f2f95 &hzjcARHkgsListsourceid=9514aebf-3e59-45c3-83b3-5a6f590f2f95,单纯从字符串看不出来是什么问题,然后将url复制到notpad++,发现中间有空格,导致的问题


CURLE_NOT_BUILT_IN(4)

由于构建时决定,未在此libcurl中找到所请求的功能,协议或选项。这意味着在构建libcurl时未启用或显式禁用某个功能或选项,并且为了使其正常运行,您必须获得重建的libcurl。

调用接口curl_easy_setopt(pCurlHandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);出现上面错误,然后通过调整TLS的版本为最低解决问题:curl_easy_setopt(pCurlHandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_DEFAULT);



CURLE_COULDNT_CONNECT(7)

connect()的主机或代理失败。存在如下可能:1)双方网络不通 2)对方端口没有在监听


CURLE_OPERATION_TIMEDOUT (28)

Operation timeout. The specified time-out period was reached according to the conditions

操作超时,说明连接上服务器,等待接收数据超时


CURLE_SSL_CONNECT_ERROR (35)

A problem occurred somewhere in the SSL/TLS handshake. You really want the error buffer and read the message there as it pinpoints the problem slightly more. Could be certificates (file formats, paths, permissions), passwords, and others.

该错误主要原因是服务器支持的ssl/tls算法较老,导致握手失败,需要编译一个OpenSSL+libcurl版本



CURLE_GOT_NOTHING(52)

服务器连接成功,但是没有从服务器返回任何数据,被认为是一个错误


7)

libcurl通过https访问图片,返回错误码56,错误信息:Recv failure: Connection reset by peer

(48) An unknown option was passed in to libcurl

通过VPN,浏览器远程执行libcurl指令,提示如上,重新打开一个新的远程命令行解决问题


301 Moved Permanently

下载图片遇到301重定向,301是永久重定向,302是临时重定向。需要提取重定向的地址,重新请求图片


项目经验分析:

搭建的HTTP服务器,在通过libcurl多次返回查询的时候,刚开始出现错误码28,说明服务器在处理数据的过程中,处于阻塞的状态无法正常返回,直到超时,当服务器耗尽资源,这个时候返回错误码7,可以通过netstat -ano |findstr 7003查看端口正在监听