C+URL:Libcurl的简单应用

_虹_

2019-04-11 20:32:36

Personal

## 本文将介绍怎样使用C++进行简单的HTTP(S)通信 # libcurl简介: Libcurl是一个开源的客户端URL传输库,支持DICT,FILE,FTP,FTPS,Gopher,HTTP,HTTPS,IMAP,IMAPS,LDAP,LDAPS,POP3,POP3S,RTMP,RTSP,SCP,SFTP,SMB,SMBS,SMTP,SMTPS,Telnet和TFTP,支持SSL证书,HTTP POST,HTTP PUT,FTP上传,基于HTTP表单的上传,代理服务器,HTTP / 2,cookies,用户名+密码验证(Basic,Plain,Digest,CRAM-MD5,NTLM,Negotiate and Kerberos)传输简历,代理隧道等等。 ~~然而这篇文章只会讲编译,HTTP GET,POST ,cookies,HTTPS和代理。~~ Curl的官网:https://curl.haxx.se/ Libcurl:https://curl.haxx.se/libcurl/ Libcurl官方教程:https://curl.haxx.se/libcurl/c/libcurl-tutorial.html 有啥用呢,~~(对于算法竞赛这东西显然没用)~~,但是我们可以用它实现一些具有一定实用意义的小工具。 # PART 1 libcurl的编译(windows sspi+libcurl7.64.0) **环境:VS2015+libcurl** 在官网可以下载libcurl的最新版本。 解压文件,在projects\Windows下是libcurl对使用vs编译提供的个版本工程,可根据自己vs对应的vc版本进行选择。 打开对应版本文件夹下的curl-all.sln,选择解决方案配置和平台。(图中编译了64位debug版本动态库。) ![](https://cdn.luogu.com.cn/upload/pic/56372.png) 点击生成,选择生成解决方案进行编译。 生成的文件在build目录下,如图的配置,生成文件位于build\Win64\VC14\DLL Debug - DLL Windows SSPI,分别是libcurld.lib,libcurld.dll。 **支持https的**libcurl的编译就完成了。~~交叉编译openssl从来没成功过。~~ # PART 2 libcurl的配置 使用vs2015建立个命令行应用的项目(不会请移步baidu)。 复制ibcurl中的include\curl目录和编译出的lib,dll到建立的工程目录下。 在vs中点击项目,选择属性,vc++目录,将curl文件夹添加至包含目录,lib,dll所在目录添加至库目录。 选择连接器,输入,添加libcurld.lib(或对应版本),保存设置。 然后再代码中include curl.h就可以开始使用libcurl了。 # PART 3 libcurl的基础使用 本文只介绍libcurl的easy接口,multi和shared接口请自学。 先介绍几个注意事项: 1. libcurl对句柄的设置都是sticky的,即对一个句柄进行的设置会一直生效。 2. easy接口是阻塞的。 纯竞赛程序员听不懂?那忽略上面也没啥关系。~~有纯竞赛的同学会看到这里吗?~~ 先上个最简单的示例程序:**获取baidu首页并在屏幕上输出**。 ```cpp #include <iostream> #include "curl/curl.h" int main() { CURL* curl_handle; curl_global_init(CURL_GLOBAL_ALL); curl_handle = curl_easy_init(); if(!curl_handle)return -1; curl_easy_setopt(curl_handle,CURLOPT_URL,"http://www.baidu.com"); CURLcode res = curl_easy_perform(curl_handle); curl_easy_cleanup(curl_handle); curl_global_cleanup(); return 0; } ``` 看不懂?我们来一句一句解释: ```cpp CURL* curl_handle; ``` 保存一个curl的句柄,所有收发数据都要围绕这个句柄进行。 ------------ ```cpp curl_global_init(CURL_GLOBAL_ALL); ``` 对libcurl的全局进行加载,**这个函数不是线程安全的**。 ------------ ```cpp curl_handle = curl_easy_init(); ``` 获取一个curl句柄(easy handle)。 **当没有调用过curl_global_init()时,curl_easy_init();会自动对前者进行调用。但前者不是线程安全的,所以最好手动调用global init** ```cpp if(!curl_handle)return -1; ``` 如果返回NULL,说明创建新句柄失败。 ------------ ```cpp curl_easy_setopt(curl_handle,CURLOPT_URL,"http://www.baidu.com"); ``` 设置访问的url,这个函数后面再进行详细解释。其实基本上啥功能的实现都和它有关。 ------------ ```cpp CURLcode res = curl_easy_perform(curl_handle); ``` 发送curl_handle的请求并获取**请求返回的状态**(注意不是获取到的数据)。 ------------ ```cpp curl_easy_cleanup(curl_handle); ``` 释放curl句柄。 ------------ ```cpp curl_global_cleanup(); ``` 关闭libcurl库。 ------------ **貌似这玩意看起来干不了啥。** 高端操作后面当然会讲了。 # PART 4 Libcurl的简单应用 还是先上示例代码:下载并**保存**页面及超时重试。 ```cpp size_t download(char* ptr, size_t size, size_t num, string* stream) { if (stream == NULL) return 0; stream->append(ptr, size*num); return size*num; } CURLcode DownloadPage(CURL* curl_handle, string url, string& data) { curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl_handle, CURLOPT_HTTPGET, true);//设置请求方式为http get curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, download); curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &data); curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, true);//设置libcurl自动进行重定向 curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, false);//不验证ssl证书。 curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, false);//设置libcurl不验证host。忽略host验证必须同时忽略ssl证书验证。 curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, CURL_DOWNLOAD_TIMEOUT); return curl_easy_perform(curl_handle); } bool AutoRestartDownload(CURL* curl_handle, string url, string& data) { CURLcode res = (CURLcode)0; int cnt = 0; do { data.clear(); res = DownloadPage(curl_handle, url, data); ++cnt; } while (res == 28&&cnt<=MAX_DOWNLOAD_RETRY); return (res == 0); } ``` 总代码有点太长,上一下主体部分吧。 part3的代码是把获取的页面输出到了屏幕,但是其实获取到的数据我们是可以选择处理方式的。如果我们需要处理获取的数据,那我们需要向libcurl注册一个回调函数。 ```cpp curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, function_name); ``` 这个属性用于注册回调函数。 回调函数的形式是: ```cpp size_t function_name(char* ptr, size_t size, size_t num, data_type* stream) ``` 这个函数的返回值表示成功读取了多少数据,因此一般应返回size*num。 如果我们想让回调函数获得更多的信息,比如获取的数据应该保存在哪,这就要用到另一个属性: **CURLOPT_WRITEDATA**。 ```cpp curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, buffer); ``` 这个函数会设置用户数据的**指针**,也就是上面回调函数获取到的第四个参数。 如果不设置回调函数,可以把 **CURLOPT_WRITEDATA** 设置为一个C文件句柄,此时libcurl会把获取的数据输出到句柄打开的文件中。(所以这个属性默认大概是stdout)。 **要注意的是,libcurl在获取到的内容较大时会将数据分段,多次调用回调函数。编写回调函数时要考虑到这一点。** 下面是超时机制: 设置libcurl超时需要下面的函数 ```cpp curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT,超时时间); ``` 超时时间以秒为单位,如果请求超时,curl_easy_perform返回28. 设置超时还可以设置 **CURLOPT_TIMEOUT_MS** 属性。单位毫秒。毫秒超时优先于秒超时。 (据说毫秒超时这玩意**可能**不太好用,而且**超时功能并不完全线程安全**) # PART 5 libcurl使用代理 以使用windows下的shadowsocks客户端为例。 设置方式: ```cpp curl_easy_setopt(curl_handle, CURLOPT_PROXY, "127.0.0.1:1080"); curl_easy_setopt(curl_handle, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME); ``` 第一个表示代理服务器的地址和端口号,第二个表示代理所使用的协议。 shadowsocks默认本机端口为1080,可以在其目录下的user-wininet.json中的“ProxyServer”项和gui-config.json中的“localPort”项查看。 和不使用代理相比,个人暂时未发现其它代码上的修改。 由于vpn的工作方式足够底层,不需要专门对代理进行设置。(vpn都被封差不多了吧) # PART 6 libcurl Post&Cookies ## Post: ```cpp curl_easy_setopt(curl, CURLOPT_POST, 1);//设置请求方式为post curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);//要post的数据,参数类型为char*。 ``` 没了。。。。 ## Cookies: ```cpp curl_easy_setopt(curl,CURLOPT_COOKIE,cookies);//参数类型char* ``` cookies格式应为 _NAME=CONTENTS_ ,如果需要多个cookie,应用分号隔开,如:“ _NAME1=CONTENTS1;NAME2=CONTENTS2;_ ” 还有一个属性是 **CURLOPT_COOKIELIST** ,功能更多,可自行查阅官网。(我没用过QAQ) # PART 7 libcurl HTTPS 在编译时如果编译了Windows SSPI,openssl或其他支持的ssl库,进行一般的https访问基本不需要对代码做什么修改。 通常没必要对ssl的安全性进行检查,所以可以用以下代码忽略检查: ```cpp curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, false);//不验证ssl证书。 curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, false);//设置libcurl不验证host。忽略host验证必须同时忽略ssl证书验证。 ``` # PART 8 libcurl的一些注意事项 libcurl超时机制不保证多线程安全,设置 **CURLOPT_NOSIGNAL** 为true可以一定程度上避免,但会导致dns解析失去超时功能。 官方建议是使用c-ares进行异步解析。~~我没写过,不做介绍。~~ ------------ openssl也不支持并发,要自己加锁。 # PART 9 有啥用 可以摸鱼下载个小说,批量扒个图片。 或者写爬虫,论坛管理器等网络相关小工具。 ~~比如不知道有没有下次的luogu冬日绘版的外挂。~~ **就算不摸鱼,学些新东西也终归是好的嘛。** ~~有没有夏日绘板啊【逃~~ # PART 10 例程 libcurl下载百度贴吧帖子(只看楼主模式)中的图片: [vs2015project 链接](https://pan.baidu.com/s/1G-w1in9SYNrUTitUMIHdrg ) 提取码:d6n8 **请自觉尊重版权,不要下载了大大的图还未经许可四处乱发。** ------------ libcurl封禁贴吧账号一天(**在有管理权限的贴吧**): [vs2015project 链接](https://pan.baidu.com/s/1HY_eDPcJ4Ebt5uhlR3P_NA) 提取码:srov 为保证安全,程序默认使用的用户cookie(即 _BASIC_BDUSS_ )被从代码里删除了。