cURL用于tranfering data with URLs。支持大部分常见通讯协议,也支持大部分的平台,而且是完全开源的 ,因此被广泛使用。我们常说的cURL是指它的命令行工具(command line tool),libcurl是指它的库(library,C实现)。
一、资料
快速查询API?将https://curl.se/libcurl/c/*.html中的*替换成相应api进行查询。
二、Two interfaces
libcurl提供了两套接口,easy和multi。
curl_easy_***(...); // 这是easy版接口
curl_multi_***(...); // 这是multi版接口
The easy interface lets you do single transfers with a synchronous and blocking function call.
The multi interface allows multiple simultaneous transfers in a single thread.
1、easy interface
使用示例如下:
// ************************************************************
// libcurl的全局初始化,在我们的程序中只需调用一次,和curl_global_cleanup首尾呼应。
// 注意,这不是thread-safe的,确保在main thread执行。
// ************************************************************
curl_global_init( CURL_GLOBAL_ALL );
// ************************************************************
// 获取当前libcurl的版本信息,一般用于我们检查是否是合适的版本。
// ************************************************************
curl_version_info_data *data = curl_version_info( CURLVERSION_NOW );
// ************************************************************
// create an easy handle which starts a libcurl easy session
// 必须和curl_easy_cleanup()首尾呼应。
// ************************************************************
CURL *easyhandle = curl_easy_init();
// ************************************************************
// 设置选项,和OpenGL的State类似,只要设置了就一直保持,除非显式改变。
// API手册:https://curl.se/libcurl/c/curl_easy_setopt.html
// 每个option的用法:https://curl.se/libcurl/c/****.html,将**替换成optionname
// 每个option的例子:https://curl.se/libcurl/c/options-in-examples.html
// ************************************************************
curl_easy_setopt( easyhandle, optionname, ...... );
// 设置error msg的输出error buffer,用于我们debug。
char curl_errbuf[CURL_ERROR_SIZE];
curl_easy_setopt( easyhandle, CURLOPT_ERRORBUFFER, curl_errbuf );
// ************************************************************
// 重置这个handle的所有选项至默认。
// ************************************************************
curl_easy_reset( easyhandle );
// ************************************************************
// 复制一个全新独立的handle,option也会复制。
// ************************************************************
CURL *copyEasyHandle = curl_easy_duphandle( easyhandle );
// ************************************************************
// perform a blocking(阻塞) file transfer,将会阻塞,直到transfer完成。
// 如果触发错误,则会输出到上面的CURLOPT_ERRORBUFFER指定的buffer
// 错误码解释:https://curl.se/libcurl/c/libcurl-errors.html
// ************************************************************
CURLcode errCode = curl_easy_perform( easyhandle );
if( errCode == CURLE_OK )
{
}
else
{
CCLOG( curl_errbuf );
}
// ************************************************************
// End a libcurl easy handle,这个handle将不再有用,和curl_easy_init正好相反。
// handle所使用到的connections都会关闭,除非attached to multi handle
// 可能会触发progress callback或者header callback,因为有些通讯协议是请求/响应协议。比如FTP、POP3、IMAP
// ************************************************************
curl_easy_cleanup( easyhandle );
// ************************************************************
// releases resources acquired by curl_global_init.
// 这不是thread-safe的,
// ************************************************************
curl_global_cleanup();
2、multi interface
The multi interface allows your program to transfer multiple files in both directions at the same time。注意,这是说,我们可以在单线程中调用multi接口,就有多线程异步效果,而不需要我们自己设计多线程。
The multi interface is simply a way to make multiple transfers at the same time by adding up multiple easy handles into a “multi stack”。
使用示例如下:
curl_global_init( CURL_GLOBAL_ALL );
// ******************************************************************************************
// create a multi handle,和curl_multi_cleanup首尾呼应。
// ******************************************************************************************
CURLM* multi_handle = curl_multi_init();
CURL *easyhandle = curl_easy_init();
curl_easy_setopt( easyhandle, optionname, ...... );
// ******************************************************************************************
// add an easy handle to a multi session
// easy handle将压栈multi stack,由multi handle管理,禁止再调用curl_easy_perform
// easy handle will use a shared connection cache owned by the multi handle
// 必须调用curl_multi_remove_handle才能从stack中去除,即使eady handle compeleted也没用。
// ******************************************************************************************
curl_multi_add_handle(multi_handle, easyhandle);
// ******************************************************************************************
// 和curl_multi_add_handle相反,记得还要调用curl_easy_cleanup才能真正release the easy hanle
// ******************************************************************************************
curl_multi_remove_handle(multi_handle, easyhandle);
curl_easy_cleanup(easyhandle);
// ******************************************************************************************
// reads/writes available data from each easy handle
// ******************************************************************************************
int runningHandles; // perform之后还在传输数据的handle数量。
CURLMcode errCode = curl_multi_perform(multi_handle, &runningHandles);
// ******************************************************************************************
// read multi stack informationals
// ******************************************************************************************
CURLMsg* msg = curl_multi_info_read(multi_handle, &msgs_in_queue);
// ************************************************************
// close down a multi session which removes a whole multi stack.
// 如果multi_handle=nulltpr,则返回ErrCode = CURLM_BAD_HANDLE
// ************************************************************
CURLMCode errCode = curl_multi_cleanup(multi_handle);
// ************************************************************
// releases resources acquired by curl_global_init.
// 这不是thread-safe的,
// ************************************************************
curl_global_cleanup();
三、使用例子
1、下载单个文件(异步)
#include <stdio.h>
#include <string.h>
/* curl stuff */
#include <curl/curl.h>
#ifdef _WIN32
#define WAITMS(x) Sleep(x)
#else
/* Portable sleep for platforms other than Windows. */
#define WAITMS(x) \
struct timeval wait = { 0, ( x ) * 1000 }; \
( void ) select( 0, NULL, NULL, NULL, &wait )
#endif
int main()
{
CURL *http_handle;
CURLM *multi_handle;
CURLMcode errCode;
int numfds; // number of file descriptors
int still_running = 0; // keep number of running handles
int repeats = 0;
curl_global_init( CURL_GLOBAL_ALL );
http_handle = curl_easy_init();
curl_easy_setopt( http_handle, CURLOPT_URL, "https://www.example.com/" );
multi_handle = curl_multi_init(); // create a multi stack
curl_multi_add_handle( multi_handle, http_handle ); // add the individual transfers
curl_multi_perform( multi_handle, &still_running ); // we start some action by calling perform right away
while( still_running )
{
errCode = curl_multi_wait( multi_handle, NULL, 0, 1000, &numfds ); // wait for activity, timeout or "nothing"
if( errCode != CURLM_OK )
{
fprintf( stderr, "curl_multi_wait() failed, code %d.\n", errCode );
break;
}
// 'numfds' being zero means either a timeout or no file descriptors to wait for.
// Try timeout on first occurrence, then assume no file descriptors and no file descriptors
// to wait for means wait for 100 milliseconds.
if( !numfds )
{
repeats++; // count number of repeated zero numfds
if( repeats > 1 )
{
WAITMS( 100 ); // sleep 100 milliseconds
}
}
else
{
repeats = 0;
}
curl_multi_perform( multi_handle, &still_running );
}
curl_multi_remove_handle( multi_handle, http_handle );
curl_easy_cleanup( http_handle );
curl_multi_cleanup( multi_handle );
curl_global_cleanup();
}
2、并行下载多个文件(异步)
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <curl/curl.h>
static const char *urls[] =
{
"https://www.microsoft.com",
"https://opensource.org",
"https://www.google.com",
"https://www.yahoo.com",
"https://www.ibm.com",
"https://www.mysql.com",
"https://www.oracle.com",
"https://www.ripe.net",
"https://www.iana.org",
"https://www.amazon.com",
"https://www.netcraft.com",
"https://www.heise.de",
"https://www.chip.de",
"https://www.ca.com",
"https://www.cnet.com",
"https://www.mozilla.org",
"https://www.cnn.com",
"https://www.wikipedia.org",
"https://www.dell.com",
"https://www.hp.com",
"https://www.cert.org",
"https://www.mit.edu",
"https://www.nist.gov",
"https://www.ebay.com",
"https://www.playstation.com",
"https://www.uefa.com",
"https://www.ieee.org",
"https://www.apple.com",
"https://www.symantec.com",
"https://www.zdnet.com",
"https://www.fujitsu.com/global/",
"https://www.supermicro.com",
"https://www.hotmail.com",
"https://www.ietf.org",
"https://www.bbc.co.uk",
"https://news.google.com",
"https://www.foxnews.com",
"https://www.msn.com",
"https://www.wired.com",
"https://www.sky.com",
"https://www.usatoday.com",
"https://www.cbs.com",
"https://www.nbc.com/",
"https://slashdot.org",
"https://www.informationweek.com",
"https://apache.org",
"https://www.un.org",
};
#define MAX_PARALLEL 10
#define NUM_URLS sizeof(urls)/sizeof(char *)
static size_t write_cb( char *data,
size_t n,
size_t l,
void *userp )
{
/* take care of the data here, ignored in this example */
( void ) data;
( void ) userp;
return n * l;
}
static void add_transfer( CURLM *multi_handle, int index )
{
CURL *easy_handle = curl_easy_init();
curl_easy_setopt( easy_handle, CURLOPT_WRITEFUNCTION, write_cb );
curl_easy_setopt( easy_handle, CURLOPT_URL, urls[index] );
curl_easy_setopt( easy_handle, CURLOPT_PRIVATE, urls[index] );
curl_multi_add_handle( multi_handle, easy_handle );
}
int main( void )
{
CURLM *multi_handle;
CURLMsg *msg;
unsigned int transfers = 0;
int msgs_left = -1;
int still_alive = 1;
curl_global_init( CURL_GLOBAL_ALL );
multi_handle = curl_multi_init();
// Limit the amount of simultaneous connections curl should allow
curl_multi_setopt( multi_handle, CURLMOPT_MAXCONNECTS, ( long ) MAX_PARALLEL );
for( transfers = 0; transfers < MAX_PARALLEL; transfers++ ) { add_transfer( multi_handle, transfers ); }
do
{
curl_multi_perform( multi_handle, &still_alive );
while( ( msg = curl_multi_info_read( multi_handle, &msgs_left ) ) )
{
if( msg->msg == CURLMSG_DONE )
{
char *url;
CURL *easy_handle = msg->easy_handle;
curl_easy_getinfo( easy_handle, CURLINFO_PRIVATE, &url );
fprintf( stderr, "R: %d - %s <%s>\n",
msg->data.result,
curl_easy_strerror( msg->data.result ),
url );
curl_multi_remove_handle( multi_handle, easy_handle );
curl_easy_cleanup( easy_handle );
}
else
{
fprintf( stderr, "E: CURLMsg (%d)\n", msg->msg );
}
if( transfers < NUM_URLS ) { add_transfer( multi_handle, transfers++ ); }
}
if( still_alive ) { curl_multi_wait( multi_handle, NULL, 0, 1000, NULL ); }
} while( still_alive || ( transfers < NUM_URLS ) );
curl_multi_cleanup( multi_handle );
curl_global_cleanup();
return EXIT_SUCCESS;
}
3、下载单个文件到本地(同步)
#include <stdio.h>
#include <stdlib.h>
#include <curl/curl.h>
static size_t write_data( void *ptr, // 指向接收到的数据的第一个object
size_t size, // object的数量
size_t nmemb, // 每个object有几个byte
void *stream ) // output stream,比如File
{
size_t written = fwrite( ptr, size, nmemb, ( FILE * ) stream );
return written;
}
int main( int argc, char *argv[] )
{
CURL *curl_handle;
FILE *pagefile;
static const char *pagefilename = "page.out";
if( argc < 2 )
{
printf( "Usage: %s <URL>\n", argv[0] );
return 1;
}
curl_global_init( CURL_GLOBAL_ALL );
/* init the curl session */
curl_handle = curl_easy_init();
/* set URL to get here */
curl_easy_setopt( curl_handle, CURLOPT_URL, argv[1] );
/* Switch on full protocol/debug output while testing */
curl_easy_setopt( curl_handle, CURLOPT_VERBOSE, 1L );
/* disable progress meter, set to 0L to enable it */
curl_easy_setopt( curl_handle, CURLOPT_NOPROGRESS, 1L );
/* send all data to this function */
curl_easy_setopt( curl_handle, CURLOPT_WRITEFUNCTION, write_data );
/* open the file */
pagefile = fopen( pagefilename, "wb" );
if( pagefile )
{
// write the page body to this file handle
// 上面write function函数回调的最后一个参数
curl_easy_setopt( curl_handle, CURLOPT_WRITEDATA, pagefile );
/* get it! */
curl_easy_perform( curl_handle );
/* close the header file */
fclose( pagefile );
}
/* cleanup curl stuff */
curl_easy_cleanup( curl_handle );
curl_global_cleanup();
return 0;
}
4、HTTP GET/POST
#include <stdio.h>
#include <curl/curl.h>
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_PROXY, "10.99.60.201:8080");// 代理
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_PROXY, "10.99.60.201:8080");
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");
}
5、断点续传
//采用CURLOPT_RESUME_FROM_LARGE 实现文件断点续传功能
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <curl/curl.h>
//这个函数为CURLOPT_HEADERFUNCTION参数构造
/* 从http头部获取文件size*/
size_t getcontentlengthfunc(void *ptr, size_t size, size_t nmemb, void *stream) {
int r;
long len = 0;
/* _snscanf() is Win32 specific */
// r = _snscanf(ptr, size * nmemb, "Content-Length: %ld\n", &len);
r = sscanf(ptr, "Content-Length: %ld\n", &len);
if (r) /* Microsoft: we don't read the specs */
*((long *) stream) = len;
return size * nmemb;
}
/* 保存下载文件 */
size_t wirtefunc(void *ptr, size_t size, size_t nmemb, void *stream)
{
return fwrite(ptr, size, nmemb, stream);
}
/*读取上传文件 */
size_t readfunc(void *ptr, size_t size, size_t nmemb, void *stream)
{
FILE *f = stream;
size_t n;
if (ferror(f))
return CURL_READFUNC_ABORT;
n = fread(ptr, size, nmemb, f) * size;
return n;
}
// 下载 或者上传文件函数
int download(CURL *curlhandle, const char * remotepath, const char * localpath,
long timeout, long tries)
{
FILE *f;
curl_off_t local_file_len = -1 ;
long filesize =0 ;
CURLcode r = CURLE_GOT_NOTHING;
int c;
struct stat file_info;
int use_resume = 0;
/* 得到本地文件大小 */
//if(access(localpath,F_OK) ==0)
if(stat(localpath, &file_info) == 0)
{
local_file_len = file_info.st_size;
use_resume = 1;
}
//采用追加方式打开文件,便于实现文件断点续传工作
f = fopen(localpath, "ab+");
if (f == NULL) {
perror(NULL);
return 0;
}
//curl_easy_setopt(curlhandle, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curlhandle, CURLOPT_URL, remotepath);
curl_easy_setopt(curlhandle, CURLOPT_CONNECTTIMEOUT, timeout); // 设置连接超时,单位秒
//设置http 头部处理函数
curl_easy_setopt(curlhandle, CURLOPT_HEADERFUNCTION, getcontentlengthfunc);
curl_easy_setopt(curlhandle, CURLOPT_HEADERDATA, &filesize);
// 设置文件续传的位置给libcurl
curl_easy_setopt(curlhandle, CURLOPT_RESUME_FROM_LARGE, use_resume?local_file_len:0);
curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, f);
curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, wirtefunc);
//curl_easy_setopt(curlhandle, CURLOPT_READFUNCTION, readfunc);
//curl_easy_setopt(curlhandle, CURLOPT_READDATA, f);
curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, 1L);
r = curl_easy_perform(curlhandle);
fclose(f);
if (r == CURLE_OK)
return 1;
else {
fprintf(stderr, "%s\n", curl_easy_strerror(r));
return 0;
}
}
int main(int c, char **argv) {
CURL *curlhandle = NULL;
curl_global_init(CURL_GLOBAL_ALL);
curlhandle = curl_easy_init();
//download(curlhandle, "ftp://user:pass@host/path/file", "C:\\file", 0, 3);
download(curlhandle , "http://software.sky-union.cn/index.asp","/work/index.asp",1,3);
curl_easy_cleanup(curlhandle);
curl_global_cleanup();
return 0;
}