1、Curl简介
2、Easy interface
3、Multi interface
libcurl作为是一个多协议的便于客户端使用的URL传输库,基于C语言,提供C语言的API接口,支持DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, Telnet and TFTP这些协议,同时支持使用SSL证书的安全文件传输:HTTP POST, HTTP PUT, FTP 上传, 基于HTTP形式的上传、代理、Cookies、用户加密码的认证等多种应用场景。另外,libcurl是一个高移植性的库,能在绝大多数系统上运行,包括Solaris, NetBSD, FreeBSD, OpenBSD, Darwin, HPUX, IRIX, AIX, Tru64, Linux, UnixWare, HURD, Windows, Amiga, OS/2, BeOs, Mac OS X, Ultrix, QNX, OpenVMS, RISC OS, Novell NetWare, DOS等。
libcurl提供了两种接口,分别是easy interface和multi interface。easy interface以同步的方式进行数据传输,执行curl函数时会一直阻塞到数据传输完毕后返回,且一次操作只能发送一次请求,如果要同时发送多个请求,必须使用多线程。 而multi interface以一种简单的、非阻塞、异步的方式进行传输,它允许在一个线程中,同时提交多个相同类型的请求。 在使用multi interface之前,你应该掌握easy interface的基本使用。因为multi interface是建立在easy interface基础之上的,它只是简单的将多个easy handler添加到一个multi stack,而后同时传输而已。
在基于libcurl的程序里,主要采用callback function (回调函数)的形式完成传输任务,用户在启动传输前设置好各类参数和回调函数,当满足条件时libcurl将调用用户的回调函数实现特定功能。下面是利用libcurl完成传输任务的流程:
在整过过程中设置 curl_easy_setopt() 参数是最关键的,几乎所有的libcurl程序都要使用它。
描述:这个函数只能用一次。(其实在调用curl_global_cleanup() 函数后仍然可再用),如果这个函数在curl_easy_init函数调用时还没调用,它讲由libcurl库自动完成。
参数:flags
描述:在结束libcurl使用的时候,用来对curl_global_init做的工作清理。类似于close的函数。
描述: 打印当前libcurl库的版本。
描述:curl_easy_init用来初始化一个CURL的指针(有些像返回FILE类型的指针一样). 相应的在调用结束时要用curl_easy_cleanup 函数清理。一般curl_easy_init意味着一个会话的开始. 它的返回值一般都用在easy系列的函数中.
描述:这个调用用来结束一个会话.与curl_easy_init配合着用.
参数: CURL类型的指针.
描述:这个函数在初始化CURL类型的指针 以及curl_easy_setopt完成后调用. 就像字面的意思所说perform就像是个舞台.让我们设置的option 运作起来。
参数: CURL类型的指针.。
返回值:返回0意味一切ok,非0代表错误发生。主要错误码说明:
描述:发出http请求后,服务器会返回应答头信息和应答数据,如果仅仅是打印应答头的所有内容,则直接可以通过curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, function) 的方式来完成,这里需要获取的是应答头中特定的信息,比如应答码、cookies列表等需要通过这个函数。注意,这个函数必须在执行 curl_easy_perform() 后调用。
参数:
info参数就是我们需要获取的内容,下面是一些参数值:
第三个参数必须是指向 long的指针、指向char *的指针、指向struct curl_slist *的指针或指向double的指针,函数调用返回CURL_OK时,指向的数据将被填充。
描述: 这个函数最重要了,几乎所有的curl 程序都要频繁的使用它.它告诉curl库.程序将有如何的行为.。比如要查看一个网页的html代码等.(这个函数有些像ioctl函数)
参数:
CURL类型的指针;
各种CURLoption类型的选项.(都在curl.h库里有定义),下面我们对这个选项进行详细解读;
parameter 这个参数既可以是个函数的指针,也可以是某个对象的指针,也可以是个long型的变量.它用什么这取决于第二个参数。
CURLoption的各种参数介绍:
struct curl_slist *headers=NULL; /* init to NULL is important */
headers = curl_slist_append(headers, "Hey-server-hey: how are you?");
headers = curl_slist_append(headers, "X-silly-content: yes");
curl_easy_setopt(easyhandle, CURLOPT_HTTPHEADER, headers); /* pass our list of custom made headers */
curl_easy_perform(easyhandle); /* transfer http */
curl_slist_free_all(headers); /* free the header list */
对于已经存在的消息头,可以重新设置它的值:
headers = curl_slist_append(headers, "Accept: Agent-007");
headers = curl_slist_append(headers, "Host: munged.host.line");
对于一个已经存在的消息头,设置它的内容为空,libcurl在发送请求时就不会同时提交该消息头:
headers = curl_slist_append(headers, "Accept:");
写回调函数原型为:
size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
函数将在libcurl接收到数据后被调用,因此函数多做数据保存的功能,如处理下载文件。对于大多数传输,此回调被多次调用,每次调用都会传递另一块数据。ptr 指向传输的数据,该数据的大小为nmemb ,size参数总是1。CURLOPT_WRITEDATA 用于表明CURLOPT_WRITEFUNCTION函数中的userdata指针的来源。如果你没有通过CURLOPT_WRITEFUNCTION属性给easy handle设置回调函数,libcurl会提供一个默认的回调函数,它只是简单的将接收到的数据打印到标准输出。你也可以通过 CURLOPT_WRITEDATA属性给默认回调函数传递一个已经打开的文件指针,用于将数据输出到文件里。
读回调函数原型是:
size_t read_callback(char *buffer, size_t size, size_t nitems, void *userdata)
当libcurl需要读取数据传递给远程主机时将调用该函数,在这个函数中,buffer指针指向的数据块需要填充最多 size * nitems 个字节。CURLOPT_READDATA 表明CURLOPT_READFUNCTION函数原型中的userdata指针来源。
写header回调函数原型为:
size_t header_callback(char *buffer, size_t size, size_t nitems, void *userdata);
libcurl一旦接收到http 头部数据后将调用该函数,只取报文header。CURLOPT_WRITEDATA 传递指针给libcurl,该指针表明CURLOPT_HEADERFUNCTION 函数的userdata指针的来源。
进度相关回调函数原型为:
int progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow);
这个函数跟数据传输进度相关。CURLOPT_PROGRESSFUNCTION 指定的函数正常情况下每秒被libcurl调用一次,为了使CURLOPT_PROGRESSFUNCTION被调 用,CURLOPT_NOPROGRESS必须被设置为false,CURLOPT_PROGRESSDATA指定的参数将作为 CURLOPT_PROGRESSFUNCTION指定函数的第一个参数。
实例:
#include
#include
bool getUrl(char *filename)
{
CURL *curl;
CURLcode res;
FILE *fp;
if ((fp = fopen(filename, "w")) == NULL) // 返回结果用文件存储
return false;
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Accept: Agent-007");
curl = curl_easy_init(); // 初始化
if (curl)
{
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); // 改协议头
curl_easy_setopt(curl, CURLOPT_URL,"http://www.baidu.com");
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); //将返回的http头输出到fp指向的文件
curl_easy_setopt(curl, CURLOPT_HEADERDATA, fp); //将返回的html主体数据输出到fp指向的文件
res = curl_easy_perform(curl); // 执行
if (res != 0) {
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
fclose(fp);
return true;
}
}
bool postUrl(char *filename)
{
CURL *curl;
CURLcode res;
FILE *fp;
if ((fp = fopen(filename, "w")) == NULL)
return false;
curl = curl_easy_init();
if (curl)
{
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "/tmp/cookie.txt"); // 指定cookie文件
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "&logintype=uid&u=xieyan&psw=xxx86"); // 指定post内容
curl_easy_setopt(curl, CURLOPT_URL, " http://mail.sina.com.cn/cgi-bin/login.cgi "); // 指定url
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
fclose(fp);
return true;
}
int main(void)
{
getUrl("/tmp/get.html");
postUrl("/tmp/post.html");
}
libcurl的easy接口是阻塞的,也就是说必须等到上一个curl请求执行完后,下一个curl请求才能继续执行,在一般的应用场合,这种阻塞的访问方式是没有问题的,但是当程序需要进行多次curl并发请求的时候,easy接口就无能为力了,这个时候curl提供的multi接口就派上用场了。相比而言,multi接口的使用会比easy 接口稍微复杂点,毕竟multi接口是依赖easy接口的,首先粗略的讲下其使用流程:
描述:此函数返回一个CURLM句柄,用作所有其它multi-function的输入,有时在文档的某些位置称为multi handle。当操作完成时,此init调用必须对 curl_multi_cleanup() 进行调用。
描述:此函数在给定套接字和应用程序的私有指针之间的多句柄中创建关联。这是为curl_socket_multi_action使用而设计的。设置后,sockptr指针将传递给特定sockfd套接字的所有未来套接字回调。如果libcurl尚未使用给定的sockfd,则此函数将返回错误。
描述:curl_multi_setopt用于告诉libcurl multi handle 如何表现。通过使用curl_multi_setopt的相应选项,可以在使用该多句柄时更改libcurl的行为。使用选项后跟参数param设置所有选项。该参数可以是long,函数指针,对象指针或curl_off_t类型,具体取决于特定选项所期望的内容。
CURLMoption的参数介绍:
回调函数原型:
int socket_callback(CURL * easy, / * easy handle * /
curl_socket_t s, / * socket * /
int what, / *描述套接字* /
void * userp, / *私有回调指针* /
void * socketp); / *私有套接字指针* /
参数:
easy:表示与此次更新相关的特定easy handle。因为一个socket和一个easy handle关联在一起;
s :这个函数调用所关注的特定套接字。如果what参数不被设置为CURL_POLL_REMOVE,那么what参数它包含应用程序应监视此套接字上的哪些活动的有关信息。对此次回调的后续调用可能会更新已监视的套接字的bits位;
userp:通过CURLMOPT_SOCKETDATA设置;
socketp:通过curl_multi_assign设置,或者可以为NULL.
what:socket的状态,它可以时一下几个值之一: CURL_POLL_IN、CURL_POLL_OUT、CURL_POLL_INOUT、CURL_POLL_REMOVE。
当curl_multi_socket_action被调用时,它通过0次、1次或者多次调用socket_callback来通知应用程序socket状态的更新(也就是把socket的状态更新传给socket_callback,然后由socket_callback来处理这种更新),socket_callback函数获取状态更新,其中包含自上次调用socket_callback以来的更改。如果socket_callback函数指针为空,则不会调用回调函数。
回调函数原型:
int timer_callback(CURLM *multi, /* multi handle */
long timeout_ms, /* timeout in number of ms */
void *userp); /* private callback pointer */
timeout_ms:一个timeout_ms传递给此回调的值,-1意味着你应该删除这个定时器。所有其他值都是有效的到期时间,以毫秒为单位。
该timer_callback当超时到期时间改变才会被调用。
userp:该指针由CURLMOPT_TIMERDATA参数设置。
回调函数timer_callback安装一个间隔为timeout_ms的非重复计时器。当计时器触发时,调用curl_multi_socket_action或curl_multi_perform,具体取决于使用是哪个接口。
描述:为多会话添加一个简单的句柄,调用这个函数时会自动回调 timer_callback() 函数。
描述:从多会话中删除一个简单的句柄。
描述:从每个简单句柄读取/写入可用数据,它底层使用的是select作为事件监听器。其中running_handles用于保存正在运行的easy_handle数量。
描述:该函数为应用程序提供了一种方法,不仅可以避免被强制使用select(),还可以提供更高性能的API,这将为使用大量同时连接的应用程序带来显着差异。该函数时用来代替curl_multi_perform的。当应用程序检测到由libcurl处理的套接字上的操作时(比如通过poll、select函数),应调用curl_multi_socket_action并将sockfd参数设置为带有该操作的套接字。ev_bitmask设置一般设置为0,libcurl在内部测试描述符。可以将CURL_SOCKET_TIMEOUT传递给sockfd参数,以启动整个过程或发生超时。返回时,running_handles指向多句柄中正在运行的easy handle的数量。当此数字达到0时,所有传输都完成。当您在特定套接字上调用curl_multi_socket_action并且计数器减1时,它并不一定意味着这个确切的套接字/传输是完成的。使用curl_multi_info_read找出完成的简易句柄。该函数的用法总结如下:
这个实例中使用两个while循环,第一个循环用于创建socket,第二个循环用于发送数据到socket 或者 从socket中读取数据。
#include
#include
#include
#include
#include
#include
using namespace std;
size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
size = size * nmemb;
std::cout << "write callback, data size:" << size << endl;
return size;
}
size_t header_callback(char *buffer, size_t size, size_t nitems, void *userdata)
{
size = size * nitems;
std::cout << "header callback, data sieze:" << size << endl;
return size;
}
int socket_callback(CURL* easy, curl_socket_t s, int action, void* userp, void* socketp)
{
std::cout << endl << "socket callback" << endl;
std::cout << "socketp : " << socketp << endl;
std::cout << "socket fd:" << s << endl;
struct pollfd* fd = reinterpret_cast(userp);
fd->fd = s;
fd->events = 0;
fd->revents = 0;
if (action == CURL_POLL_REMOVE)
{
std::cout << "action: CURL_POLL_REMOVE" << endl;
fd->fd = -1;
}
else if (action == CURL_POLL_IN || action == CURL_POLL_INOUT)
{
std::cout << "action: CURL_POLL_IN | CURL_POLL_INOUT" << endl;
fd->events |= POLLIN;
}
else if (action == CURL_POLL_OUT || action == CURL_POLL_INOUT)
{
std::cout << "action: CURL_POLL_OUT | CURL_POLL_INOUT" << endl;
fd->events |= POLLOUT;
}
std::cout << endl;
return 0;
}
int timer_callback(CURLM *multi, long timeout_ms, void *userp)
{
std::cout << endl << "timer callback" << endl;
std::cout << "timeout_ms:" << timeout_ms << endl << endl;
return 0;
}
int main()
{
CURL* mHandle = curl_easy_init();
curl_easy_setopt(mHandle, CURLOPT_URL, "www.baidu.com");
curl_easy_setopt(mHandle, CURLOPT_VERBOSE, 0L);
curl_easy_setopt(mHandle, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(mHandle, CURLOPT_TCP_KEEPALIVE, 1L);
curl_easy_setopt(mHandle, CURLOPT_TCP_KEEPIDLE, 120L);
curl_easy_setopt(mHandle, CURLOPT_TCP_KEEPINTVL, 60L);
curl_easy_setopt(mHandle, CURLOPT_TCP_NODELAY, 1L);
curl_easy_setopt(mHandle, CURLOPT_HTTPGET, 1);
FILE *header_fp;
if ((header_fp = fopen("./header_file", "w")) == NULL)
{
return false;
}
curl_easy_setopt(mHandle, CURLOPT_HEADERDATA, header_fp);
curl_easy_setopt(mHandle, CURLOPT_HEADERFUNCTION, header_callback);
FILE *body_fp;
if ((body_fp = fopen("./body_file", "w")) == NULL)
{
return false;
}
curl_easy_setopt(mHandle, CURLOPT_WRITEDATA, body_fp);
curl_easy_setopt(mHandle, CURLOPT_WRITEFUNCTION, write_callback);
CURLM* mMultiHandle = curl_multi_init();
struct pollfd mPollFd;
int mStillRunning = 0;
curl_multi_setopt(mMultiHandle, CURLMOPT_TIMERDATA, mPollFd);
curl_multi_setopt(mMultiHandle, CURLMOPT_TIMERFUNCTION, timer_callback);
curl_multi_setopt(mMultiHandle, CURLMOPT_SOCKETDATA, &mPollFd);
curl_multi_setopt(mMultiHandle, CURLMOPT_SOCKETFUNCTION, socket_callback);
curl_multi_setopt(mMultiHandle, CURLMOPT_PIPELINING , 1);
curl_multi_setopt(mMultiHandle, CURLMOPT_MAX_HOST_CONNECTIONS, 10);
curl_multi_setopt(mMultiHandle, CURLMOPT_MAX_PIPELINE_LENGTH, 5);
curl_multi_add_handle(mMultiHandle, mHandle); //这个函数调用后会自动调用timer_callback()函数
std::cout << "begin perform curl" << endl;
do
{
curl_multi_socket_action(mMultiHandle, CURL_SOCKET_TIMEOUT, 0, &mStillRunning);
usleep(1000); // 延迟1ms
} while (mPollFd.fd == 0 && mStillRunning); //这个循环中创建需要用到的socket
std::cout << "create socket successfule!" << endl;
do {
curl_multi_socket_action(mMultiHandle, mPollFd.fd, 0, &mStillRunning); //通知回调函数
} while (mPollFd.fd != 0 && mStillRunning); //这个循环中不断读或者写数据,直到socket被关闭
fclose(header_fp);
fclose(body_fp);
return 0;
}
参考:https://curl.haxx.se/libcurl/c/