借着curl 7.75.0版本更新, 最近又下载下来玩了玩, 在此做个简单记录

1.环境搭建

首先是libcurl动态库, 自己下载源码编译的话如果要使用https协议还要下载OpenSSL和libssh的源码一起编译, 我嫌麻烦, 所以直接官网下载的官方编译好的动态库

linux一般自带的有或者直接apt get都很方便了

这里放个windows环境的下载地址 : https://curl.se/windows/

红框部分是curl部分功能的依赖库, 这里我建议都下载下来扔到项目目录里

下载下来解压后curl目录结构如上图, 其中bin放的是动态库, lib是静态库, include里是头文件, 需要提及的是lib中两个静态库都是.a结尾的, 较小且带dll的应该是windows版本的, 我在编译自己的程序时将这个静态库名称改成了libcurl.lib

最后我将有可能用到的动态库, 静态库, 证书, 头文件整合了一下, 内容如下:

之后在自己的程序中链接libcurl, 包含curl目录下的头文件, 将dll放在可执行程序同目录下就可以开始使用了.

2. 调用接口进行http通讯

下面先列一下curl请求的基本流程和重要变量

(1) CURLcode curl_global_init() : 该接口用于初始化curl库, 应该在所有curl操作之前被调用

(2) CURL* curl_easy_init() : 该接口返回一个curl句柄, 类型为CURL*, 一次会话的相关操作都在这个返回句柄上进行

(3) void curl_easy_cleanup(CURL*) : 该接口用于释放给定curl句柄, 每一次会话结束都应该调用此接口释放对应的curl句柄

(4) CURLcode curl_easy_setopt(CURL*, CURLoption, ...) : 该接口通过传入不同的宏可以设置指定curl句柄的相关属性, 以此控制会话的各种属性内容

这里给一个官方链接可以查询OPT的含义, 其中也包含官方的example : https://curl.se/libcurl/c/curl_easy_setopt.html

(5) CURLcode curl_easy_perform(CURL*) : 通过给定句柄执行通讯会话

(6) CURLcode : 几乎所有的curl接口的返回值都为此类型, 这个code定义了所有curl操作时的状态, 这里给一个官方链接可以查询code的含义 : https://curl.se/libcurl/c/libcurl-errors.html

(7) const char* curl_easy_strerror(CURLcode) : 将CURLcode转为对应含义的字符串方便进行日志输出等操作

以上是我经常使用到的curl接口, curl功能强大, 支持的协议与内容远不止http/https

官方自己给出的评价是 : libcurl is probably the most portable, most powerful and most often used network transfer library on this planet.

这里我封装了两个功能, 分别是http GET请求网页和http GET下载文件, 过程中启用了cookie.

上代码 :

自己封装的curl类

 1 class CHttpClient
2 {
3 public:
4 CHttpClient();
5 ~CHttpClient();
6
7 long http_enable_cookie(const char *path);
8 long http_post(const char *url);
9 long http_submit(const char *url, std::vector<std::string> &form);
10 long http_get(const char *url, std::string &body);
11 long http_download(const char *url, const char *fullpath);
12 long http_add_header(const char *header);
13 long http_add_multi_header(std::vector<std::string> &list);
14
15 private:
16 bool prepare_curl(const char *url);
17 bool exec_curl();
18 bool try_cleanup_curl();
19
20 private:
21 CURL *m_pCurl;
22 struct curl_slist *m_pHeader;
23 bool m_bSetCookie;
24 bool m_bSetHeader;
25 char m_szCookiePath[MAX_PATH];
26 };

最主要的curl_global_init放在了构造函数中, 这里不再展示, 其中prepare_curl, exec_curl, try_cleanup_curl为我对curl http通讯流程的基本封装

下面展示上述三个接口

 1 bool CHttpClient::prepare_curl(const char *url)
2 {
3 m_pCurl = curl_easy_init();
4 if (nullptr == m_pCurl) return false;
5
6 curl_easy_setopt(m_pCurl, CURLOPT_URL, url);
7 curl_easy_setopt(m_pCurl, CURLOPT_FOLLOWLOCATION, 1L);
8 curl_easy_setopt(m_pCurl, CURLOPT_SSL_VERIFYPEER, 0L);
9 curl_easy_setopt(m_pCurl, CURLOPT_SSL_VERIFYHOST, 0L);
10
11 if (m_bSetHeader)
12 {
13 curl_easy_setopt(m_pCurl, CURLOPT_HTTPHEADER, m_pHeader);
14 }
15
16 if (m_bSetCookie)
17 {
18 curl_easy_setopt(m_pCurl, CURLOPT_COOKIEJAR, m_szCookiePath); //set-cookie将会修改此路径对应cookie缓存文件
19 curl_easy_setopt(m_pCurl, CURLOPT_COOKIEFILE, m_szCookiePath); //发送请求时将会从此文件中读取cookie
20 }
21
22 #ifdef DEBUG
23 curl_easy_setopt(m_pCurl, CURLOPT_VERBOSE, 1L);
24 curl_easy_setopt(m_pCurl, CURLOPT_DEBUGFUNCTION, cb_dbg);
25 #endif
26
27 return true;
28 }
29
30 bool CHttpClient::exec_curl()
31 {
32 CURLcode retCode = curl_easy_perform(m_pCurl);
33 try_cleanup_curl();
34
35 #ifdef DEBUG
36 print_dbg_msg();
37 #endif
38
39 if (CURLE_OK != retCode)
40 {
41 LOG_MSG(LOG_ERROR, "curl execute with code[%d] msg[%s]", retCode, curl_easy_strerror(retCode));
42 return false;
43 }
44 return true;
45 }
46
47 bool CHttpClient::try_cleanup_curl()
48 {
49 if (nullptr != m_pCurl)
50 {
51 curl_easy_cleanup(m_pCurl);
52 m_pCurl = nullptr;
53 }
54
55 if (m_bSetHeader)
56 {
57 curl_slist_free_all(m_pHeader);
58 m_pHeader = nullptr;
59 m_bSetHeader = false;
60 }
61
62 return true;
63 }

prepare_curl主要进行curl句柄的初始化, 设置http通用的参数

exec_curl执行curl通讯, 通讯完成后调用try_cleanup_curl进行内存释放, 并打印debug通讯信息

针对prepare_curl中curl_easy_setopt的参数, 这里展开解释一下

(1)CURLOPT_URL : http通讯的地址, 可以解析域名

(2)CURLOPT_FOLLOWLOCATION : 跟随网页重定向

(3)CURLOPT_SSL_VERIFYPEER & CURLOPT_SSL_VERIFYHOST : 双端是否进行SSL安全验证, 此处我把这个功能关掉了, 正常生产环境是不会这样做的, curl库中也带的有证书, 老版本可能需要更新一下证书防止有些网页不能访问, 这里我只做调试, 就比较随意了

(4)CURLOPT_HTTPHEADER : 设置http header, 这里传入curl_slist结构体, 使用curl_slist_append可以直接把const char*类型字符串加入这个结构体, 如果不设置, curl默认请求头只有GET, Accept,Host

(5)CURLOPT_COOKIEJAR : 指定本次通讯cookie保存的路径, 保存操作在对应的curl句柄执行curl_easy_cleanup时执行

(6)CURLOPT_COOKIEFILe : 指定本次通讯cookie读取的路径

(7)CURLOPT_VERBOSE : 设置是否回显通讯内容, 开启后如果不指定回调函数, 则使用stderr

(8)CURLOPT_DEBUGFUNCTION : 设置回显时调用的回调函数, 回调函数的参数列表应为(CURL *curl, curl_infotype type, char *data, size_t size, void *usr_ptr), 其中type指示了当前data的类型, 类型包括CURLINFO_TEXT, CURLINFO_HEADER_IN, CURLINFO_HEADER_OUT, CURLINFO_DATA_IN, CURLINFO_DATA_OUT, CURLINFO_SSL_DATA_IN, CURLINFO_SSL_DATA_OUT, CURLINFO_END, 具体含义参考官方文档实际调试一下比较好理解

下面展示CURLOPT_DEBUGFUNCTION对应的回调函数以及print_dbg_msg打印函数

 1 static std::string g_sHeaderOut;
2 static std::string g_sHeaderIn;
3 static std::string g_sDataOut;
4 static std::string g_sDataIn;
5
6 int cb_dbg(CURL *curl, curl_infotype type, char *data, size_t size, void *usr_ptr)
7 {
8 switch (type)
9 {
10 case CURLINFO_HEADER_OUT:
11 g_sHeaderOut.append(data, size);
12 break;
13 case CURLINFO_DATA_OUT:
14 g_sDataOut.append(data, size);
15 break;
16 case CURLINFO_HEADER_IN:
17 g_sHeaderIn.append(data, size);
18 break;
19 case CURLINFO_DATA_IN:
20 g_sDataIn.append(data, size);
21 break;
22 default:
23 break;
24 }
25 return 0;
26 }
27
28 void print_dbg_msg()
29 {
30 if (!g_sHeaderOut.empty())
31 {
32 str_replace(g_sHeaderOut, "%", "%%");
33 LOG_MSG(LOG_DEBUG, "%s", g_sHeaderOut.c_str());
34 g_sHeaderOut.clear();
35 }
36
37 if (!g_sDataOut.empty())
38 {
39 str_replace(g_sDataOut, "%", "%%");
40 LOG_MSG(LOG_DEBUG, "%s", g_sDataOut.c_str());
41 g_sDataOut.clear();
42 }
43
44 if (!g_sHeaderIn.empty())
45 {
46 str_replace(g_sHeaderIn, "%", "%%");
47 LOG_MSG(LOG_DEBUG, "%s", g_sHeaderIn.c_str());
48 g_sHeaderIn.clear();
49 }
50
51 if (!g_sDataIn.empty())
52 {
53 str_replace(g_sDataIn, "%", "%%");
54 LOG_MSG(LOG_DEBUG, "%s", g_sDataIn.c_str());
55 g_sDataIn.clear();
56 }
57 }

这里因为我自己写的日志打印使用vsprinf遇到%会报错, 这里我又封装了一个string的replace函数把%替换成%%, 打印的时候可能不太美观, 暂时还没花时间优化

下面展示GET请求和GET download请求

 1 long CHttpClient::http_get(const char *url, std::string &res)
2 {
3 if (!prepare_curl(url)) return TSI_INTERNAL_ERR;
4
5 // CURLOPT_WRITEDATA后的参数会传给回调函数的usrdata
6 curl_easy_setopt(m_pCurl, CURLOPT_WRITEFUNCTION, cb_get);
7 curl_easy_setopt(m_pCurl, CURLOPT_WRITEDATA, &res);
8
9 if (!exec_curl())
10 {
11 LOG_MSG(LOG_ERROR, "http get fail");
12 return TSI_INTERNAL_ERR;
13 }
14 return TSI_NO_ERR;
15 }
16
17 long CHttpClient::http_download(const char *url, const char *fullpath)
18 {
19 if (!prepare_curl(url)) return TSI_INTERNAL_ERR;
20
21 //二进制写入模式创建下载文件
22 FILE *download_file = fopen(fullpath, "wb");
23 if (nullptr == download_file)
24 {
25 try_cleanup_curl();
26 return TSI_INTERNAL_ERR;
27 }
28
29 //将文件句柄设置到下载回调中, curl内部会将大文件分割并多次调用回调写入数据
30 curl_easy_setopt(m_pCurl, CURLOPT_WRITEFUNCTION, cb_download);
31 curl_easy_setopt(m_pCurl, CURLOPT_WRITEDATA, download_file);
32
33 //TODO:要确定一下下载过程是否是异步的, 防止文件还没下载完毕, 后面就fclose了
34 if (!exec_curl())
35 {
36 LOG_MSG(LOG_ERROR, "http download [%s] fail", fullpath);
37 fclose(download_file);
38 std::remove(fullpath);
39 return TSI_INTERNAL_ERR;
40 }
41
42 LOG_MSG(LOG_INFO, "http download [%s] success", fullpath);
43 fclose(download_file);
44 return TSI_NO_ERR;
45 }

其中主要涉及两个CURLOPT, 此处展开解释

(1)CURLOPT_WRITEFUNCTION : 该参数指定get请求到的内容的写入方法, curl默认使用fwrite, 该回调函数参数列表必须为(char *data, size_t size, size_t nmemb, void *usrdata)

(2)CURLOPT_WRITEDATA : 该参数将后跟的数据作为参数传入指定的writefunction中

下面展示两个回调函数cb_get和cb_download

 1 size_t cb_get(char *data, size_t size, size_t nmemb, void *usrdata)
2 {
3 size_t data_size = size * nmemb;
4 static_cast<std::string*>(usrdata)->append(data, data_size);
5 return data_size;
6 }
7
8 size_t cb_download(char *data, size_t size, size_t nmemb, void *usrdata)
9 {
10 size_t data_size = size * nmemb;
11 fwrite(data, size, nmemb, (FILE*)usrdata);
12 return data_size;
13 }

因为download功能涉及具体网站的分析, 这里就不展示调试内容了

以上是http get请求的简单实例, 常用功能应该还有form POST, 暂时没写, 有空补上.

如有错误疏漏, 请务必指出, 十分感谢, 同时欢迎一起探讨相关问题, 转载请注明, 感谢!

C++使用libcurl进行http通讯的更多相关文章

  1. C++ 用libcurl库进行http通讯网络编程

    使用libcurl完成http通讯,很方便而且是线程安全,转载一篇比较好的入门文章 转载自 http://www.cnblogs.com/moodlxs/archive/2012/10/15/2724 ...

  2. 【swupdate文档 三】SWUpdate: 嵌入式系统的软件升级

    SWUpdate: 嵌入式系统的软件升级 概述 本项目被认为有助于从存储媒体或网络更新嵌入式系统.但是,它应该主要作为一个框架来考虑,在这个框架中可以方便地向应用程序添加更多的协议或安装程序(在SWU ...

  3. (转)利用libcurl和国内著名的两个物联网云端通讯的例程, ubuntu和openwrt下调试成功(四)

    1. libcurl 的参考文档如下 CURLOPT_HEADERFUNCTION Pass a pointer to a function that matches the following pr ...

  4. C/C++ 用libcurl库进行http通讯网络编程

    C/C++ 用libcurl库进行http通讯网络编程 目录索引: 一.LibCurl基本编程框架 二.一些基本的函数 三.curl_easy_setopt函数部分选项介绍 四.curl_easy_p ...

  5. C++ 用libcurl库进行http通讯网络编程 【转】

    http://www.cnblogs.com/moodlxs/archive/2012/10/15/2724318.html C++ 用libcurl库进行http通讯网络编程 目录索引: 一.Lib ...

  6. 客户端技术的一点思考(数据存储用SQLite, XMPP通讯用Gloox, Web交互用LibCurl, 数据打包用Protocol Buffer, socket通讯用boost asio)

    今天看到CSDN上这么一篇< 彻底放弃没落的MFC,对新人的忠告!>, 作为一个一直在Windows上搞客户端开发的C++程序员,几年前也有过类似的隐忧(参见 落伍的感觉), 现在却有一些 ...

  7. C++ 用libcurl库进行http通讯网络编程(转)

    转载:http://www.cnblogs.com/moodlxs/archive/2012/10/15/2724318.html 目录索引: 一.LibCurl基本编程框架 二.一些基本的函数 三. ...

  8. C++ 用libcurl库进行http通讯网络编程[转]

    http://www.cnblogs.com/moodlxs/archive/2012/10/15/2724318.html 目录索引: 一.LibCurl基本编程框架 二.一些基本的函数 三.cur ...

  9. C++ 用libcurl库进行http 网络通讯编程

      一.LibCurl基本编程框架libcurl是一个跨平台的网络协议库,支持http, https, ftp, gopher, telnet, dict, file, 和ldap 协议.libcur ...

随机推荐

  1. Centos7 yum 安装 oracle-rdbms-server-11gR2-pre

    Oracleyum官方网站 http://yum.oracle.com/ 一.下载yum源 根据自己需求下载相应的yum源 http://yum.oracle.com/getting-started. ...

  2. 7.DHCP的相关命令

    1.Get-DhcpServerv4Scope :查看所有作用域状态 PS C:\Users\xinghen> Get-DhcpServerv4Scope ScopeId SubnetMask ...

  3. 如何安装Python 3.9.1?

    首先打开浏览器输入网址:https://www.python.org或者通过百度搜索python进入Python官网. 选择Downloads,弹出最新版本下载链接,当前版本为3.9.1,如图所示: ...

  4. 《Proxy系列专题》:代理模式(静态、JDK、CGLib)

    <Proxy系列专题>:代理模式(静态.JDK.CGLib)使用 现象:在如今互联网时代,项目的复杂度不断的提升,有些场景下需要一定的设计优化来支撑业务的扩展,如为了不改动原始类,但需要对 ...

  5. 回文树(回文自动机PAM)小结

    回文树学习博客:lwfcgz    poursoul 边写边更新,大概会把回文树总结在一个博客里吧... 回文树的功能 假设我们有一个串S,S下标从0开始,则回文树能做到如下几点: 1.求串S前缀0~ ...

  6. BZOJ1150 [CTSC2007]数据备份Backup 链表+小根堆

    BZOJ1150 [CTSC2007]数据备份Backup 题意: 给定一个长度为\(n\)的数组,要求选\(k\)个数且两两不相邻,问最小值是多少 题解: 做一个小根堆,把所有值放进去,当选择一个值 ...

  7. Codeforces Round #627 (Div. 3) E - Sleeping Schedule(递推)

    题意: 每天有 h 小时,有一序列 an,每次可以选择 ai 或 ai - 1 小时后睡觉,问从 0 次 0 时开始,最多在 l ~ r 时间段入睡多少次. 思路: 如果此时可达,计算此时可达的时间点 ...

  8. 【noi 2.7_413】Calling Extraterrestrial Intelligence Again(算法效率--线性筛素数+二分+测时)

    题意:给3个数M,A,B,求两个质数P,Q.使其满足P*Q<=M且A/B<=P/Q<=1,并使P*Q最大.输入若干行以0,0,0结尾. 解法:先线性筛出素数表,再枚举出P,二分出对应 ...

  9. 牛客的两道dfs

    1.传送门:牛客13594-选择困难症 题意:给你k类物品,每类物品有a[i]个每个物品都有一个value,每类物品最多选一个,要求有多少种选法使得总value>m(没要求每类物品都必须选) 题 ...

  10. 洛谷-P1469 找筷子 (位运算)

    题意:给你一组数,求数组中唯一的出现次数为奇数的那个数. 题解:这题其实直接桶排一下就行了,但是最后一个点会TLE. ​ 后来了解到这题可以用位运算来解决: ​ ^(异或)运算符:用于比较两个二进制数 ...