简介
libcurl是一个客户端URL传输库,支持很多协议(自行查看官方简介:https://curl.haxx.se/libcurl/),简而言之,这个库能爬网页,有了这个库,就不需要自己写socket来爬网页了,方便了很多。
此处使用的语言为C++,虽然libcurl有C++的binding,但是那样就得看两份文档了,太麻烦,况且官方文档提到了在C++下使用时,唯一需要注意的是回调函数不能为非静态类成员函数。(官方文档地址https://curl.haxx.se/libcurl/c/libcurl-tutorial.html)
类定义
下面是我用libcurl写的一个类html_getter的头文件:
//html_getter.h
#ifndef HTML_GETTER_H_INCLUDED
#define HTML_GETTER_H_INCLUDED
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <atomic>
#include "log.h"
class html_getter
{
public:
html_getter();
std::string *get_string(const std::string &url);
~html_getter();
private:
static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
static std::atomic_int init; //identify whether the curl_global_init() function has been called
};
#endif // HTML_GETTER_H_INCLUDED
除了构造函数和析构函数之外,还有两个成员函数:write_callback()和get_string()、一个成员变量:init。write_callback()和init用于支持libcurl,get_string()是我们的功能接口,调用时,传入一个url,即可将该url对应的网页内容以string *返回。
类实现
类的实现代码如下:
//html_getter.cpp
#include "html_getter.h"
std::atomic_int html_getter::init(0);
html_getter::html_getter()
{
if (init == 0) {
curl_global_init(CURL_GLOBAL_ALL);
}
init++;
}
size_t html_getter::write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
std::string *str = (std::string *)userdata;
size_t realsize = size * nmemb;
char *temp = (char *)malloc(realsize * sizeof(char) + 1);
memcpy(temp, ptr, realsize);
temp[realsize] = '\0';
*str += temp;
free(temp);
return realsize;
}
std::string *html_getter::get_string(const std::string &url)
{
CURL *curl = curl_easy_init();
CURLcode res;
std::string buffer = "";
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_COOKIE, "ViewMode=contents");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buffer);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
buffer = "";
}
curl_easy_cleanup(curl);
return new std::string(buffer);
}
html_getter::~html_getter()
{
init--;
if (init == 0) {
curl_global_cleanup();
}
}
先来说说init这个静态成员,它用来标识当前有几个html_getter实例,为什么要记录这个信息呢?是因为libcurl在调用其功能函数之前需要先调用初始化函数curl_global_init(),功能函数使用结束之后需要调用其清理函数curl_global_cleanup()。在初始化一个html_getter实例时,如果发现init值为0,则需要调用初始化函数;与此相反,当释放一个html_getter实例时,如果发现init值为0,则需要调用清理函数。
初始化函数curl_global_init()可传入的参数有很多,可以用来初始化不同的子模块。官方文档提到了“CURL_GLOBAL_ALL will make it initialize all known internal sub modules, and might be a good default option. ”,因此我也采用了CURL_GLOBAL_ALL作为初始化参数。
libcurl有两类API——easy interface和multi interface,前者是同步的,后者是前者的异步实现。前者较为容易且“Numerous applications have been built using this.”,因此我在这里使用前者。
Easy interface
easy interface的基本使用流程如下:
初始化并获得句柄
函数原型为CURL *curl_easy_init( ),在这一步得到的句柄需要在后面几步中用到,因此需要将这个句柄保存下来,留待使用。
设置参数
设置参数使用的函数原型为CURLcode curl_easy_setopt(CURL *handle, CURLoption option, parameter),第一个参数为句柄,在第一步得到,第二个参数为参数类型,它有能够设置的参数类型有很多,因为类的功能为使用GET请求网页,因此此处用到了四个参数:CURLOPT_URL设置请求URL、CURLOPT_COOKIE设置COOKIE、CURLOPT_WRITEFUNCTION设置写回调函数、CURLOPT_WRITEDATA设置写缓冲区(最后会被传给回调函数)。其中写回调函数的原型为size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)它在执行了curl_easy_perform()函数后,获取到资源后被调用,ptr为获取的资源,获取的资源的大小为size * nmemb,并且资源不会以’\0’结尾。最后的userdata即为使用CURLOPT_WRITEDATA设置参数时传入的参数,一般我们会把得到的资源经过处理后保存在这里。
写回调函数的坑
每次写回调函数被调用时,ptr指向的资源是有大小限制的,一般不超过16K。因此如果资源比较大,调用了curl_easy_perform()后写回调函数会被调用多次,资源会被分批传入,所以我这里使用了std::string类型对资源进行拼接。另外一个坑是传输资源时,即使资源足够小,也可能会被切分,也就是说,即使资源只有8K,也可能会被切分成4*2K进行处理,因此写回调函数一定要被设计成能处理分批数据的。
执行
函数原型为CURLcode curl_easy_perform(CURL * easy_handle ),执行后如果没有出错(通过返回值判断是否出错),就会调用写回调函数进行处理。
清理
函数原型为void curl_easy_cleanup(CURL * handle),没什么可说的,有init就要有cleanup,有open就要有close。
以上就是libcurl简单的应用,更多内容可参见官方文档和样例。