C+URL:Libcurl的简单应用

_虹_

2019-04-11 20:32:36

Tech. & Eng.

本文将介绍怎样使用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版本动态库。)

点击生成,选择生成解决方案进行编译。

生成的文件在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首页并在屏幕上输出

#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;
}

看不懂?我们来一句一句解释:

CURL* curl_handle;

保存一个curl的句柄,所有收发数据都要围绕这个句柄进行。

curl_global_init(CURL_GLOBAL_ALL);

对libcurl的全局进行加载,这个函数不是线程安全的

curl_handle = curl_easy_init();

获取一个curl句柄(easy handle)。

当没有调用过curl_global_init()时,curl_easy_init();会自动对前者进行调用。但前者不是线程安全的,所以最好手动调用global init

if(!curl_handle)return -1; 

如果返回NULL,说明创建新句柄失败。

curl_easy_setopt(curl_handle,CURLOPT_URL,"http://www.baidu.com");

设置访问的url,这个函数后面再进行详细解释。其实基本上啥功能的实现都和它有关。

CURLcode res = curl_easy_perform(curl_handle);

发送curl_handle的请求并获取请求返回的状态(注意不是获取到的数据)。

curl_easy_cleanup(curl_handle); 

释放curl句柄。

curl_global_cleanup(); 

关闭libcurl库。

貌似这玩意看起来干不了啥。 高端操作后面当然会讲了。

PART 4 Libcurl的简单应用

还是先上示例代码:下载并保存页面及超时重试。

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注册一个回调函数。

curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, function_name);

这个属性用于注册回调函数。

回调函数的形式是:

size_t function_name(char* ptr, size_t size, size_t num, data_type* stream)

这个函数的返回值表示成功读取了多少数据,因此一般应返回size*num。

如果我们想让回调函数获得更多的信息,比如获取的数据应该保存在哪,这就要用到另一个属性: CURLOPT_WRITEDATA

curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, buffer);

这个函数会设置用户数据的指针,也就是上面回调函数获取到的第四个参数。

如果不设置回调函数,可以把 CURLOPT_WRITEDATA 设置为一个C文件句柄,此时libcurl会把获取的数据输出到句柄打开的文件中。(所以这个属性默认大概是stdout)。

要注意的是,libcurl在获取到的内容较大时会将数据分段,多次调用回调函数。编写回调函数时要考虑到这一点。

下面是超时机制:

设置libcurl超时需要下面的函数

curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT,超时时间);

超时时间以秒为单位,如果请求超时,curl_easy_perform返回28.

设置超时还可以设置 CURLOPT_TIMEOUT_MS 属性。单位毫秒。毫秒超时优先于秒超时。

(据说毫秒超时这玩意可能不太好用,而且超时功能并不完全线程安全

PART 5 libcurl使用代理

以使用windows下的shadowsocks客户端为例。

设置方式:


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:

curl_easy_setopt(curl, CURLOPT_POST, 1);//设置请求方式为post
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);//要post的数据,参数类型为char*。

没了。。。。

Cookies:

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的安全性进行检查,所以可以用以下代码忽略检查:


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 链接 提取码:d6n8

请自觉尊重版权,不要下载了大大的图还未经许可四处乱发。

libcurl封禁贴吧账号一天(在有管理权限的贴吧):

vs2015project 链接 提取码:srov

为保证安全,程序默认使用的用户cookie(即 _BASICBDUSS )被从代码里删除了。