C++ 使用CURL开源库实现Http/Https的get/post请求进行字串和文件传输
CURL开源库介绍
CURL 是一个功能强大的开源库,用于在各种平台上进行网络数据传输。它支持众多的网络协议,像 HTTP、HTTPS、FTP、SMTP 等,能让开发者方便地在程序里实现与远程服务器的通信。
CURL 可以在 Windows、Linux、macOS 等多种操作系统上使用;
CURL 支持多种网络协议,能处理复杂的网络请求,如设置请求头、处理 cookies、上传和下载文件等。
使用前,编译CURL 源码,生成动态库,引入时包括头文件一起,网上也有别人已经编译好的现成的库可以下载使用。
生成的库:
在你要使用的项目里加上curl的头文件
#include <curl\curl.h>
这下你就可以使用CURL里的接口完成功能了。
CURL常用的接口说明:
- 初始化与清理相关的接口:
- curl_easy_init()
功能:创建并初始化一个新的 CURL 句柄,用于后续的网络操作。这个句柄就像一个会话的载体,后续的请求设置和执行都围绕它展开。
返回值:成功时返回一个指向新创建的 CURL 句柄的指针;失败则返回 NULL。 - curl_easy_cleanup(CURL *handle)
功能:释放 CURL 句柄占用的所有资源,包括内存、网络连接等。在使用完 CURL 句柄后,必须调用此函数以避免资源泄漏。
参数:handle 是之前通过 curl_easy_init() 得到的 CURL 句柄。
- 请求选项设置相关的接口:
- curl_easy_setopt(CURL *handle, CURLoption option, …)
功能:设置 CURL 句柄的各种选项,这些选项可以控制请求的各个方面,如请求的 URL、请求方法、请求头、回调函数等。
参数:
handle:CURL 句柄。
option:CURLoption 类型的常量,指定要设置的选项。
可变参数:根据不同的 option,需要传入相应的参数值。
常用 option 有:
CURLOPT_URL:设置请求的 URL。
CURLOPT_POST:将请求方法设置为 POST。
CURLOPT_HTTPHEADER:设置请求头。
CURLOPT_POSTFIELDS:设置 POST 请求的数据。 - curl_slist_append(struct curl_slist *list, const char *string)
功能:用于构建一个链表来存储请求头信息。每次调用该函数可以将一个新的请求头字符串添加到链表中。
参数:
list:指向 curl_slist 链表的指针,如果是第一次添加,可传入 NULL。
string:要添加的请求头字符串,格式为 Header-Name: Header-Value。
返回值:返回更新后的链表指针。
- 回调函数设置相关的接口:
- CURLOPT_WRITEFUNCTION 和 CURLOPT_WRITEDATA
功能:CURLOPT_WRITEFUNCTION:设置一个回调函数,当服务器返回响应数据时,CURL 会调用该回调函数来处理响应数据。
CURLOPT_WRITEDATA:传递一个用户自定义的指针给回调函数,用于在回调函数中存储或处理数据。 - CURLOPT_READFUNCTION 和 CURLOPT_READDATA
功能:CURLOPT_READFUNCTION:设置一个回调函数,用于在发送数据时从用户提供的数据源中读取数据。
CURLOPT_READDATA:传递一个用户自定义的指针给回调函数,用于标识数据源。
- 多线程与异步操作相关的接口:
- curl_multi_init()
功能:初始化一个 CURLM 句柄,用于多线程或异步的网络操作。该句柄可以管理多个 CURL 句柄,实现并发请求。
返回值:成功时返回一个新的 CURLM 句柄指针;失败返回 NULL。 - curl_multi_add_handle(CURLM *multi_handle, CURL *easy_handle)
功能:将一个 CURL 句柄添加到 CURLM 句柄管理的句柄列表中,以便进行并发处理。
参数:
multi_handle:CURLM 句柄。
easy_handle:要添加的 CURL 句柄。 - curl_multi_perform(CURLM *multi_handle, int *still_running)
功能:在 CURLM 句柄管理的所有 CURL 句柄上执行网络操作。该函数会尝试处理尽可能多的请求,并返回仍在运行的请求数量。
参数:
multi_handle:CURLM 句柄。
still_running:指向一个整数的指针,用于存储仍在运行的请求数量。 - curl_multi_remove_handle(CURLM *multi_handle, CURL *easy_handle)
功能:从 CURLM 句柄管理的句柄列表中移除一个 CURL 句柄。
参数:
multi_handle:CURLM 句柄。
easy_handle:要移除的 CURL 句柄。 - curl_multi_cleanup(CURLM *multi_handle)
功能:释放 CURLM 句柄占用的所有资源。在使用完 CURLM 句柄后,必须调用此函数进行清理。
参数:multi_handle 是 CURLM 句柄。
接口流程使用:
简单的同步调用模式的使用流程:
- 调用curl_global_init()初始化libcurl ;
- 调用curl_easy_init()函数得到 easy;
- interface型指针 调用curl_easy_setopt()设置传输选项;
- 根据curl_easy_setopt()设置的传输选项,实现回调函数以完成用户特定任务;
- 调用curl_easy_perform()函数完成传输任务,返回错误码 ;
- 调用curl_easy_cleanup()释放内存;
- 调用curl_global_cleanup() (可以不用调用);
在整过过程中设置curl_easy_setopt()参数是最关键的,了解相关参数及对应作用很重要。
举例说明CURL的调用实现
//http回调写函数
static size_t CurlWriteBuffer(char *buffer, size_t size, size_t nmemb, std::string* stream)
{
//第二个参数为每个数据的大小,第三个为数据个数,最后一个为接收变量
size_t sizes = size*nmemb;
if(stream == NULL)
return 0;
stream->append(buffer,sizes);
return sizes;
}
//http发送封装
int HttpClient::posturl(std::string& msg, std::string& url, bool IsSSL)
{
CURL* pCurl=NULL; //一个libcurl的handle
CURLcode res; //返回状态码
std::string response; //返回信息
curl_global_init(CURL_GLOBAL_ALL); //全局初始化
pCurl = curl_easy_init(); //创建一个handle
//设置请求头
struct curl_slist* pHeader = NULL;
//传json格式,字符编码为utf8
header_ = curl_slist_append(pHeader ,"Content-Type: application/json;charset=utf-8");
//添加请求头到handle
curl_easy_setopt(pCurl, CURLOPT_HTTPHEADER, pHeader );
//设置URL
curl_easy_setopt(pCurl, CURLOPT_URL, url.c_str());
CURLOPT_WRITEFUNCTION 将后继的动作交给write_data函数处理
curl_easy_setopt(pCurl,CURLOPT_POSTFIELDS,msg.c_str()); //post请求消息数据
curl_easy_setopt(pCurl,CURLOPT_POSTFIELDSIZE,msg.length()); //消息长度
curl_easy_setopt(pCurl, CURLOPT_WRITEFUNCTION, CurlWriteBuffer); //回调函数
curl_easy_setopt(pCurl,CURLOPT_WRITEDATA,&response); //数据接收变量
curl_easy_setopt(pCurl,CURLOPT_TIMEOUT,m_settinginfo.m_http_timeout); //连接超时时间
//不支持ssl验证
if(m_settinginfo.m_ssl == 0)
{
curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, 0);//设定为不验证证书和HOST
curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYHOST, 0);
}
else
{
// 配置 https 请求所需证书
if (m_settinginfo.m_ssl == 1) //ssl单向验证,不验证服务器
{
curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, 0L);
}else
{//双向验证
curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYHOST, 0);
curl_easy_setopt(pCurl,CURLOPT_CAINFO,ca_info.ca_path.c_str());
}
//设置客户端信息
curl_easy_setopt(pCurl, CURLOPT_SSLCERT, ca_info.client_cert_path.c_str());
curl_easy_setopt(pCurl,CURLOPT_SSLCERTTYPE,"PEM");
curl_easy_setopt(pCurl, CURLOPT_SSLKEY, ca_info.client_key_path.c_str());
curl_easy_setopt(pCurl,CURLOPT_SSLKEYTYPE,"PEM");
//如果客户端证书密钥使用密码加密,设置加密密码
//curl_easy_setopt(pCurl, CURLOPT_KEYPASSWD, "your_key_password");
}
//执行http连接
res = curl_easy_perform(pCurl);
//清除消息头
curl_slist_free_all(pHeader);
//清除handle
curl_easy_cleanup(pCurl);
return 0;
}
再看一段完整的CURL封装成get,post等形式,进行字串传输和文件上传的请求,可以直接拿去使用。
//Http的Get请求
int HttpClient::ExecuteGetRequestCURL(const char* strUrl, const char* pszGet, const char* pszCookie, int nTimeOut)
{
CURLcode res;
m_strResponse = "";
struct curl_slist* headers = NULL;
CURL* curl = curl_easy_init();
if (NULL == curl)
{
return CURLE_FAILED_INIT;
}
std::string strOutData = strUrl;
strOutData += pszGet;
curl_easy_setopt(curl, CURLOPT_URL, strOutData.c_str());
curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWriteData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&m_strResponse);
/**
* 当多个线程都使用超时处理的时候,同时主线程中有sleep或是wait等操作。
* 如果不设置这个选项,libcurl将会发信号打断这个wait从而导致程序退出。
*/
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
//连接超时设置10s,数据请求超时设置60s
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, nTimeOut);
res = curl_easy_perform(curl);
if (res != CURLE_OK)
m_strResponse = "";
curl_easy_cleanup(curl);
return res;
}
//Http的post请求
int HttpClient::ExecutePostRequestCURL(const char* strUrl, const char* pszKey, const char* pszPost, const char* pszCookie, int nTimeOut)
{
CURLcode res;
m_strResponse = "";
CURL* curl = curl_easy_init();
if (NULL == curl)
{
return CURLE_FAILED_INIT;
}
///
struct curl_slist* headerlist = NULL;
struct curl_httppost* formpost = NULL;
struct curl_httppost* last = NULL;
//headerlist = curl_slist_append(headerlist, "Content-Type:application/json;charset=UTF-8");
//curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
res = curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip, deflate, br");//设置CURLOPT_ACCEPT_ENCODING (7.21.8之前为CURLOPT_ENCODING )
if (CURLE_OK != res)
{
curl_easy_cleanup(curl);
return CURLE_FAILED_INIT;
}
curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);
if (CURLE_OK != res)
{
curl_easy_cleanup(curl);
return CURLE_FAILED_INIT;
}
curl_formadd(&formpost, &last, CURLFORM_PTRNAME, pszKey, CURLFORM_PTRCONTENTS, pszPost, CURLFORM_END); //以这种方式上传可以避免特殊字符被改变
if (CURLE_OK != res)
{
curl_easy_cleanup(curl);
return CURLE_FAILED_INIT;
}
curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); //构造post参数
if (CURLE_OK != res)
{
curl_easy_cleanup(curl);
return CURLE_FAILED_INIT;
}
/
curl_easy_setopt(curl, CURLOPT_URL, strUrl);
if (CURLE_OK != res)
{
curl_easy_cleanup(curl);
return CURLE_FAILED_INIT;
}
//curl_easy_setopt(curl, CURLOPT_POST, 1);
//curl_easy_setopt(curl, CURLOPT_POSTFIELDS, pszPost);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
if (CURLE_OK != res)
{
curl_easy_cleanup(curl);
return CURLE_FAILED_INIT;
}
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWriteData);
if (CURLE_OK != res)
{
curl_easy_cleanup(curl);
return CURLE_FAILED_INIT;
}
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&m_strResponse);
if (CURLE_OK != res)
{
curl_easy_cleanup(curl);
return CURLE_FAILED_INIT;
}
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
if (CURLE_OK != res)
{
curl_easy_cleanup(curl);
return CURLE_FAILED_INIT;
}
if ((pszCookie != NULL)&&(strlen(pszCookie)>0))
{
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, (void *)&pszCookie);
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, (void *)&pszCookie);
}
//连接超时设置,数据请求超时设置
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, nTimeOut);
res = curl_easy_perform(curl);
if (res != CURLE_OK)
{
m_strResponse = "";
// 获取详细错误信息
const char* szErr = curl_easy_strerror(res);
fprintf(stderr, "curl_easy_perform() failed: %s\n", szErr);
}
// 清空
curl_easy_cleanup(curl);
// 释放表单
curl_formfree(formpost);
// 释放表头
curl_slist_free_all (headerlist);
return res;
}
//https的Get请求
int HttpClient::ExecuteHttpsGetCURL(const std::string & strUrl, const char* pszGet, const char * pCaPath, int nTimeOut)
{
CURLcode res;
CURL* curl = curl_easy_init();
if (NULL == curl)
{
return CURLE_FAILED_INIT;
}
std::string strOutData = strUrl;
strOutData += pszGet;
curl_easy_setopt(curl, CURLOPT_URL, strOutData.c_str());
curl_easy_setopt(curl, CURLOPT_HEADER, 0 );
curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWriteData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&m_strResponse);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, nTimeOut);
if (NULL == pCaPath)
{
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false);
}
else
{
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true);
curl_easy_setopt(curl, CURLOPT_CAINFO, pCaPath);
}
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10);
//curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3);
//curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
//curl_easy_setopt(curl,CURLOPT_TRANSFERTEXT,1);
res = curl_easy_perform(curl);
if (res != CURLE_OK)
m_strResponse = "";
curl_easy_cleanup(curl);
return res;
}
//https的Post请求
int HttpClient::ExecuteHttpsPostCURL(const std::string& strUrl, const std::string& strKey, const std::string& strPost, const std::string& strCookie, const char* pCaPath, int nTimeOut)
{
CURLcode res;
CURL* curl = curl_easy_init();
if (NULL == curl)
{
return CURLE_FAILED_INIT;
}
///
struct curl_slist* headers = NULL;
struct curl_httppost* post = NULL;
struct curl_httppost* last = NULL;
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip, deflate, br");//设置CURLOPT_ACCEPT_ENCODING (7.21.8之前为CURLOPT_ENCODING )
curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);
curl_formadd(&post, &last, CURLFORM_PTRNAME, strKey.c_str(), CURLFORM_PTRCONTENTS, strPost.c_str(), CURLFORM_END); //以这种方式上传可以避免特殊字符被改变
curl_easy_setopt(curl, CURLOPT_HTTPPOST, post); //构造post参数
curl_easy_setopt(curl, CURLOPT_URL, strUrl.c_str());
// curl_easy_setopt(curl, CURLOPT_POST, 1);
// curl_easy_setopt(curl, CURLOPT_POSTFIELDS, strPost.c_str());
curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWriteData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&m_strResponse);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
//curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60*5);
//curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60*5);
if (strCookie.length()>0)
{
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, (void *)&strCookie);
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, (void *)&strCookie);
}
if (NULL == pCaPath)
{
//curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
//curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
}
else
{
//缺省情况就是PEM,所以无需设置,另外支持DER
//curl_easy_setopt(curl,CURLOPT_SSLCERTTYPE,"PEM");
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true);
curl_easy_setopt(curl, CURLOPT_CAINFO, pCaPath);
}
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 20);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, nTimeOut);
curl_easy_setopt(curl, CURLOPT_SSLVERSION, 3);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
return res;
}
static size_t OnWriteData(void* buffer, size_t size, size_t nmemb, void* lpVoid)
{
std::string* str = reinterpret_cast<std::string*>(lpVoid);
if (NULL == str || NULL == buffer)
{
return -1;
}
char* pData = reinterpret_cast<char*>(buffer);
str->append(pData, size * nmemb);
return nmemb;
}
long HttpClient::GetRespone(char* pszResponse, long &lRespLen)
{
long lRet = 0;
if (m_strResponse.length() <= 0)
{
lRet = 1;
goto err;
}
if (pszResponse == NULL)
{
lRespLen = m_strResponse.length();
goto err;
}
if (lRespLen < m_strResponse.length())
{
lRet = 1;
goto err;
}
memset(pszResponse, 0, lRespLen);
memcpy(pszResponse, m_strResponse.c_str(), m_strResponse.length());
lRespLen = m_strResponse.length();
err:
return lRet;
}
long HttpClient::GetFileNameAndExt(const std::string & strFilePath,
std::string & strFileName,
std::string & strFileExt)
{
long lRet = 0;
strFileExt = "";
strFileName = "";
int nPos = strFilePath.rfind('.');
if (nPos == -1)
{
lRet = CURLE_FAILED_INIT; //文件不存在
goto err;
}
strFileExt = strFilePath.substr(nPos+1, strFilePath.length());
nPos = strFilePath.rfind('\\');
if (nPos == -1)
{
nPos = strFilePath.rfind('/');
if (nPos == -1)
{
strFileName = strFilePath;
goto err;
}
}
strFileName = strFilePath.substr(nPos+1, strFilePath.length());
err:
return lRet;
}
//Http的post传文件请求
//strParamName是"bindingSeal",strParamVal值是对应的json包,strParamName2是"sealImages",strParamVal2值是图片名称
long HttpClient::HttpUploadFileCURL(LPCTSTR strUrl, const string& strFilePath, const string& strParamName, const string& strParamVal, const string& strParamName2, const string& strParamVal2, string& strResponse)
{
CURL* curl;
CURLcode res;
long lRet = 0;
std::string strFileName;
std::string strFileExt;
StringTool strTool;
string strCRUL = strTool.WideToAsc(strUrl);
lRet = GetFileNameAndExt(strFilePath, strFileName, strFileExt);
if (lRet != 0)
goto err;
curl = curl_easy_init();
struct curl_httppost* post = NULL;
struct curl_httppost* last = NULL;
if (curl == NULL)
{
lRet = CURLE_FAILED_INIT;
goto err;
}
curl_easy_setopt(curl, CURLOPT_URL, (char *)strCRUL.c_str()); //指定url
//form-data key(path) 和 value(device_cover)
curl_formadd(&post, &last, CURLFORM_PTRNAME, strParamName.c_str()/*"parma"*/, CURLFORM_PTRCONTENTS, strParamVal.c_str(), CURLFORM_END);
curl_formadd(&post, &last, CURLFORM_PTRNAME, strParamName2.c_str(), CURLFORM_FILE, strFilePath.c_str(),CURLFORM_FILENAME, strFileName.c_str(),CURLFORM_CONTENTTYPE,"image/png", CURLFORM_END);
//curl_formadd(&post, &last, CURLFORM_PTRNAME, strParamName2.c_str(), CURLFORM_FILE, strFilePath.c_str(),CURLFORM_FILENAME, strFileName.c_str(),CURLFORM_CONTENTTYPE,"image/PNG", CURLFORM_END);
curl_easy_setopt(curl, CURLOPT_HTTPPOST, post); //构造post参数
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWriteData); //绑定相应
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&strResponse); //绑定响应内容的地址
res = curl_easy_perform(curl); //执行请求
if(res == 0){
curl_easy_cleanup(curl);
lRet = res;
goto err;
}
else{
lRet = res;
goto err;
}
lRet = res;
err:
return lRet;
}