• 背景
  • 简介
  • 使用方法
    • 1、easy interface使用方法
    • 2、multi interface使用方法

背景

最近想做一个简单的HLS拉流程序,HTTP的下载部分觉得采用libcurl来进行比较合适及方便,所以先介绍libcurl的基本用法,然后使用libcurl完成一个简单的下载小程序。

简介

libcurl是一个跨平台的开源网络协议库,支持http, https, rtsp等多种协议 。libcurl同样支持HTTPS证书授权,HTTP POST, HTTP PUT, FTP 上传, HTTP基本表单上传,代理,cookies,和用户认证。
所以,使用libcurl,可以很简单的完成HTTP的下载工作,为HLS模块的拉流部分提供简单有效的HTTP请求方法。
想要知道更多关于libcurl的介绍,可以到官网 上去了解,在这里不再详述。(官网地址 http://curl.haxx.se/)

libcurl主要提供了两种发送HTTP请求的方式,分别是easy interface方式和multi interface方式,前者是采用阻塞的方式发送单条请求,后者采用组合的方式可以一次性发送多条请求数据,且多个下载请求是异步进行的。

使用方法

1、easy interface使用方法

easy interface主要方法如下:

1)初始化,这个函数必须是调用的第一个函数,并且它返回一个easy interface
的handle,使用该handle作为easy接口中其他函数的输入。

 CURL *curl_easy_init( );

2)当操作完成时,此调用必须有相应的调用curl_easy_cleanup() 来释放handle。

void curl_easy_cleanup(CURL * handle );

3)设置此次传输的一些基本参数,如url地址、http头、cookie信息、发送超时时间等,其中,CURLOPT_URL是必设的选项。
该函数是整个模块的核心,使用该函数,我们可以设置很多相关操作,正是由于该函数的存在,才另libcurl变的简单且具备多种可操作性。
curl_easy_setopt一些经常使用的方式,会在后面补充

 CURLcode curl_easy_setopt(CURL *handle, CURLoption option, parametr);

4)在上述准备工作已经完成后,可以调用curl_easy_perform函数,则会开始HTTP的请求工作。该接口是一个阻塞的接口。

 CURLcode curl_easy_perform(CURL * easy_handle );

5)请求过程中,可以使用下面函数,获取HTTP该次请求的相关信息,包括response
code,下载的URL,下载速度等。该函数对于一次请求不是必须的。

  CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ... );

第 2 个参数有众多选项,每个选项都有其相应的含义,第3个参数根据参数2,会有不同类型。常用的info如下:

1、CURLINFO_RESPONSE_CODE 取得response code,要求第 3 个参数是个 long
型的指针。如果TCP就连接不上,值为0。 CURLINFO_EFFECTIVE_URL
取得本最终生效的URL,也即是如果有多次重定向,获取的值为最后一次URL,要求第 3 个参数是个 char 型的指针。
2、CURLINFO_SIZE_DOWNLOAD 获取下载字节数,要求第 3 个参数是个 double
型的指针。注意,这个字节数只能反映最近一次的下载。
3、CURLINFO_SPEED_DOWNLOAD 获取平均下载数据,该选项要求传递一个
double 型参数指针,这个速度不是即时速度,而是下载完成后的速度,单位是 字节/秒
4、CURLINFO_TOTAL_TIME
获取传输总耗时,要求传递一个 double 指针到函数中,这个总的时间里包括了域名解析,以及 TCP 连接过程中所需要的时间。
5、CURLINFO_CONTENT_TYPE 该选项获得 HTTP 中从服务器端收到的头部中的 Content-Type 信息。
6、CURLINFO_CONTENT_LENGTH_DOWNLOAD 获取头部content-length,要求第 3 个参数是个 double
型的指针。如果文件大小无法获取,那么函数返回值为 -1 。

使用上面的几个函数,我们就可以完成一个简单的HTTP下载程序:

CURL *curl = curl_easy_init();
if(curl) {CURLcode res;curl_easy_setopt(curl, CURLOPT_URL, "http://example.com");res = curl_easy_perform(curl);curl_easy_cleanup(curl);
}

curl_easy_setop简介:
函数原型:

CURLcode curl_easy_setopt(CURL *handle, CURLoption option, parameter);

描述:
这个函数几乎所有的curl 程序都要频繁的使用它,它告诉curl库程序将有如何的行为。 (这个函数有些像ioctl函数)
参数:
1 CURL类型的指针
2 各种CURLoption类型的选项.。这个参数的取值很多,具体的可以查看man手册。
3 parameter 这个参数 既可以是个函数的指针,也可以是某个对象的指针,也可以是个long型的变量。它用什么这取决于第二个参数。

下面介绍几个常用的参数及使用方法:

  1. CURLOPT_URL 这个选项必须要有,设置请求的URL ,如果URL参数不写上协议头(如 “http://” 或者 "ftp:// 等等),那么函数会自己进行猜解所给的主机上用的是哪一种服务协议。
    假如给的这个地址是一个不被支持的协议,那么在其后执行curl_easy_perform() 函数或 curl_multi_perform() 函数时,libcurl将返回错误(CURLE_UNSUPPORTED_PROTOCOL)。 这个选项是唯一一个在 curl_easy_perform()调用之前就一定要设置的选项。
  1. CURLOPT_WRITEFUNCTION,CURLOPT_WRITEDATA
    1)CURLOPT_WRITEFUNCTION选项用于设置接收数据回调函数,回调函数原型为: size_t function(void *ptr, size_t size, size_t nmemb, void *stream); 函数将在libcurl接收到数据后被调用,因此函数多做数据保存的功能,如处理下载文件。
    2) CURLOPT_WRITEDATA选项用于指定CURLOPT_WRITEFUNCTION函数中的stream指针的来源。
    3)如果没有通过CURLOPT_WRITEFUNCTION属性给easy handle设置回调函数,libcurl会提供一个默认的回调函数,它只是简单的将接收到的数据打印到标准输出。也可以通过CURLOPT_WRITEDATA属性给默认回调函数传递一个已经打开的文件指针,用于将数据输出到文件里。
  1. CURLOPT_HEADERFUNCTION,CURLOPT_HEADERDATA
    1)CURLOPT_HEADERFUNCTION设置接收到http头的回调函数,原型为: size_t function(void *ptr,size_t size,size_t nmemb, void *stream); libcurl一旦接收到http 头部数据后将调用该函数。
    2)CURLOPT_HEADERDATA传递指针给libcurl,该指针表明CURLOPT_HEADERFUNCTION函数的stream指针的来源。

和上面两组类似的,这样对应的回调选项还有很多,使用方法也类似,如: CURLOPT_READFUNCTION/ CURLOPT_READDATA;

  1. CURLOPT_HTTPHEADER
    libcurl有自己默认的请求头,如果不符合我们的要求,可以使用该选项自定义请求头。可以使用curl_slist_append进行自定义,重设,如果设置请求参数为空,则相当于删除该请求头。
  1. CURLOPT_USERAGENT
    该选项要求传递一个以 ‘\0’ 结尾的字符串指针,这个字符串用来在向服务器请求时发送 HTTP 头部中的 User-Agent 信息,有些服务器是需要检测这个信息的,如果没有设置User-Agent,那么服务器拒绝请求。设置后,可以骗过服务器对此的检查。
  1. CURLOPT_VERBOSE
    在使用该选项且第 3 个参数为 1 时,curl 库会显示详细的操作信息。这对程序的调试具有极大的帮助。
  1. CURLOPT_NOPROGRESS,CURLOPT_PROGRESSFUNCTION,CURLOPT_PROGRESSDATA
    这三个选项和跟数据传输进度相关。
    1)CURLOPT_PROGRESSFUNCTION设置回调函数,函数原型: int progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow); progress_callback正常情况下每秒被libcurl调用一次。
    2)CURLOPT_NOPROGRESS必须被设置为false才会启用该功能,
    3)CURLOPT_PROGRESSDATA指定的参数将作为CURLOPT_PROGRESSFUNCTION指定函数的第一个参数。
  1. CURLOPT_TIMEOUT,CURLOPT_CONNECTIONTIMEOUT
    超时相关设置,时间单位为s
    1)CURLOPT_TIMEOUT设置整个libcurl传输超时时间。
    2)CURLOPT_CONNECTIONTIMEOUT
    设置连接等待时间。设置为0,则无限等待。
  1. CURLOPT_FOLLOWLOCATION,CURLOPT_MAXREDIRS
    重定向相关设置
    1)CURLOPT_FOLLOWLOCATION 设置为非0,响应头信息Location,即curl会自己处理302等重定向
    2)CURLOPT_MAXREDIRS指定HTTP重定向的最大次数
  1. CURLOPT_RANGE ,CURLOPT_RESUME_FROM/ CURLOPT_RESUME_FROM_LARGE 断点续传相关设置。
    1)CURLOPT_RANGE 指定char *参数传递给libcurl,用于指明http请求的range
    2)CURLOPT_RESUME_FROM传递一个long参数作为偏移量给libcurl,指定开始进行传输的位置。CURLOPT_RESUME_FROM大小限制为2G,超过可以使用CURLOPT_RESUME_FROM_LARGE
  1. CURLOPT_POSTFIELDS,CURLOPT_POSTFIELDSIZE
    1)CURLOPT_POSTFIELDS 传递一个作为HTTP “POST”操作的所有数据的字符串。
    2)CURLOPT_POSTFIELDSIZE 设置POST 字节大小。
  1. CURLOPT_NOBODY
    设置该属性即可告诉libcurl我想发起一个HEAD请求 有时候你想查询服务器某种资源的状态,比如某个文件的属性:修改时间,大小等等,但是并不需要具体得到该文件,这时我们仅需要HEAD请求。
  1. CURLOPT_ACCEPT_ENCODING
    设置libcurl对特定压缩方式自动解码,支持的方式有: “br, gzip, deflate”. 第3个参数为指定的压缩方式,如果设置为 " ",则表明三种都支持。
  1. CURLOPT_MAX_RECV_SPEED_LARGE,CURLOPT_MAX_SEND_SPEED_LARGE
    限速相关设置
    1)CURLOPT_MAX_RECV_SPEED_LARGE,指定下载过程中最大速度,单位bytes/s。
    2)CURLOPT_MAX_SEND_SPEED_LARG,指定上传过程中最大速度,单位bytes/s。
  1. CURLOPT_FORBID_REUSE ,CURLOPT_FRESH_CONNEC
    如果不使用长连接,需要设置这两个属性
    1)CURLOPT_FORBID_REUSE 设置为1,在完成交互以后强迫断开连接,不重用。
    2)CURLOPT_FRESH_CONNECT设置为1,强制获取一个新的连接,替代缓存中的连接。
  1. CURLOPT_NOSIGNAL
    当多个线程都使用超时处理的时候,同时主线程中有sleep或是wait等操作。如果不设置这个选项,libcurl将会发信号打断这个wait从而可能导致程序crash。 在多线程处理场景下使用超时选项时,会忽略signals对应的处理函数。
  1. CURLOPT_BUFFERSIZE
    指定libcurl中接收缓冲区的首选大小(以字节为单位),但是不保证接收数据时每次数据量都能达到这个值。此缓冲区大小默认为CURL_MAX_WRITE_SIZE(16kB)。允许设置的最大缓冲区大小为CURL_MAX_READ_SIZE(512kB)。 允许设置的最小缓冲区大小为1024。

DNS相关选项:

  1. CURLOPT_IPRESOLVE
    指定libcurl 域名解析模式。支持的选项有:
    1)CURL_IPRESOLVE_WHATEVER:默认值,相当于PF_UNSPEC,支持IPv4/v6,具体以哪个优先需要看libc底层实现,Android中默认以IPv6优先,当IPv6栈无法使用时,libcurl会用IPv4。
    2)CURL_IPRESOLVE_V4:.仅请求A记录,即只解析为IPv4地址。
    3)CURL_IPRESOLVE_V6:.仅请求AAAA记录,即只解析为IPv6地址。
    注意:该功能生效的前提是libcurl支持IPv6,需要在curl/lib/curl_config.h配置#define ENABLE_IPV6 1
  1. CURLOPT_DNS_CACHE_TIMEOUT
    设置libcurl DNS缓存超时时间,默认为60秒,即每60秒清一次libcurl自身保存的DNS缓存。
    如果设置为0,则不适用DNS缓存,设置为-1,则永远不清缓存。
  1. CURLOPT_DNS_USE_GLOBAL_CACHE
    让libcurl使用系统DNS缓存,默认情况下,libcurl使用本身的DNS缓存。

这几个是较常用的选项,当然,libcurl远远不止这几个选项,更详细的使用方法可以参考官网。

最后使用easy interface完成下载的简单代码:

#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
#include <unistd.h>//#define CURL_DEBUG 1
#define CURL_WAIT_TIMEOUT_MSECS 60000 //60s
#define CURL_MULIT_MAX_NUM 5static size_t recive_data_fun( void *ptr, size_t size, size_t nmemb, void *stream){return fwrite(ptr,size,nmemb,(FILE*)stream);
}static size_t read_head_fun( void *ptr, size_t size, size_t nmemb, void *stream){char head[2048] = {0};memcpy(head,ptr,size*nmemb+1);printf(" %s \n",head);return size*nmemb;
}int main(int argc, char **argv)
{if(argc < 3){printf("arg1 is url; arg2 is out file\n");return -1;}    char* url = strdup( argv[1]);char* filename= strdup(argv[2]);CURL* curl_handle;CURLcode res;//intFILE* save_file = fopen(filename,"w");if(save_file == NULL){printf("open save file fail!\n");return -1;}curl_handle = curl_easy_init();if(curl_handle){curl_easy_setopt(curl_handle, CURLOPT_URL, url);//set down load urlcurl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, save_file);//set download filecurl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, recive_data_fun);//set call back funcurl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, read_head_fun);//set call back fun
#ifdef CURL_DEBUGcurl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1);
#endif //start down loadres = curl_easy_perform(curl_handle);printf("curl fetch code %d\n",res);}//releaseif(save_file){fclose(save_file);save_file = NULL;}if(curl_handle){curl_easy_cleanup(curl_handle);}if(url){free(url);}return 0;
}

2、multi interface使用方法

multi interface的使用是在easy interface的基础之上,将多个easy handler加入到一个stack中,同时发送请求。与easy interface不同,它是一种异步,非阻塞的传输方式。

在掌握easy interface的基础上,multi interface的使用也很简单:
1)curl_multi _init初始化一个multi handler对象,
2)初始化多个easy handler对象,使用curl_easy_setopt进行相关设置,
3)调用curl_multi _add_handle把easy handler添加到multi curl对象中
4)添加完毕后执行curl_multi_perform方法进行并发的访问,
5)访问结束后curl_multi_remove_handle移除相关easy curl对象,先用curl_easy_cleanup清除easy handler对象,最后curl_multi_cleanup清除multi handler对象。

multi interface一些函数说明:
1)和easy interface类似,multi 的初始化和清除函数如下:

 CURLM *curl_multi_init( );CURLMcode curl_multi_cleanup( CURLM*multi_handle );

2)

CURLMcode curl_multi_add_handle(CURLM *multi_handle, CURL *easy_handle);
CURLMcode curl_multi_remove_handle(CURLM *multi_handle, CURL *easy_handle);

当设置好easy模式并准备传输的时候,可以使用curl_multi_add_handle替代curl_easy_perform,这样easy handler则会加入multi栈中。我们能在任何时候增加一个esay handler给multi模式,即使easy已经在执行传输操作了。
也可以在任何时候使用curl_multi_remove_handle将esay handler从multi栈中移出,一旦移出可以再次使用curl_easy_perform来进行传输任务。

3)

CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles);

添加easy handler到multi并不马上开始执行,由curl_multi_perform启动执行。
启动后将执行所有multi stack中的收发事件。如果栈上是空的直接返回。函数参数running_handles会返回当前还未结束的easy handler个数。

4)等待及超时

CURLMcode curl_multi_fdset(CURLM *multi_handle,fd_set *read_fd_set,fd_set *write_fd_set,fd_set *exc_fd_set,int *max_fd);CURLMcode curl_multi_wait(CURLM *multi_handle,struct curl_waitfd extra_fds[],unsigned int extra_nfds,int timeout_ms,int *numfds);

libcurl中,旧的API使用curl_multi_fdset设置 select或者poll模型触发。
我们看下官网给的example:

#ifdef _WIN32
#define SHORT_SLEEP Sleep(100)
#else
#define SHORT_SLEEP usleep(100000)
#endiffd_set fdread;
fd_set fdwrite;
fd_set fdexcep;
int maxfd = -1;long curl_timeo;curl_multi_timeout(multi_handle, &curl_timeo);
if(curl_timeo < 0)curl_timeo = 1000;timeout.tv_sec = curl_timeo / 1000;
timeout.tv_usec = (curl_timeo % 1000) * 1000;FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep);/* get file descriptors from the transfers */
mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);if(maxfd == -1) {SHORT_SLEEP;rc = 0;
}
elserc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);switch(rc) {
case -1:/* select error */break;
case 0:
default:/* timeout or readable/writable sockets */curl_multi_perform(multi_handle, &still_running);break;
}/* if there are still transfers, loop! */

新的API中,官网更推荐使用curl_multi_wait的方式来实现,实现方便,同时也可以避免select中file descriptors不能大于1024的问题。curl_multi_wait会轮询multi上的所有easy句柄,一直阻塞直到至少有一个被触发或者超时。
如果multi句柄正因为网络延时而挂起,会有一个更短更精确的时间来代替我们自己设置的超时时间timeout_ms。
curl_waitfd参数添加需要监听的socket。
wait返回后,numfds返回被触发的事件数量,若为0表示超时或者没有事件等待。numfds的值包括multi栈上的和extra_fds新加的之和。

看下官网的example:

CURL *easy_handle;
CURLM *multi_handle;/* add the individual easy handle */
curl_multi_add_handle(multi_handle, easy_handle);do {CURLMcode mc;int numfds;mc = curl_multi_perform(multi_handle, &still_running);if(mc == CURLM_OK ) {/* wait for activity, timeout or "nothing" */mc = curl_multi_wait(multi_handle, NULL, 0, 1000, &numfds);}if(mc != CURLM_OK) {fprintf(stderr, "curl_multi failed, code %d.n", mc);break;}/* 'numfds' being zero means either a timeout or no file descriptors towait for. Try timeout on first occurrence, then assume no filedescriptors and no file descriptors to wait for means wait for 100milliseconds. */if(!numfds) {repeats++; /* count number of repeated zero numfds */if(repeats > 1) {WAITMS(100); /* sleep 100 milliseconds */}}elserepeats = 0;} while(still_running);curl_multi_remove_handle(multi_handle, easy_handle);

对于select和curl_multi_wait两种方式,都可以使用curl_multi_timeout获取一个合适的超时时间,当然,超时时间也可以我们自己设置。

5)

CURLMsg *curl_multi_info_read( CURLM *multi_handle,   int *msgs_in_queue);

我们可以调用curl_multi_info_read获取每一个执行完成的操作信息。在完成后即将其移除multi stack。

/* call curl_multi_perform or curl_multi_socket_action first, then loopthrough and check if there are any transfers that have completed */
struct CURLMsg *m;
do {int msgq = 0;m = curl_multi_info_read(multi_handle, &msgq);if(m && (m->msg == CURLMSG_DONE)) {CURL *e = m->easy_handle;transfers--;curl_multi_remove_handle(multi_handle, e);curl_easy_cleanup(e);}
} while(m);

最后使用multi interface完成并发下载的简单代码:

#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
#include <unistd.h> //#define CURL_DEBUG 1
#define CURL_WAIT_TIMEOUT_MSECS 1000 //1s
#define CURL_MULIT_MAX_NUM 5 typedef struct curl_obj{ CURL* curl_handle; FILE* save_file; char* fetch_url; size_t (*recive_data_fun)( void *ptr, size_t size, size_t nmemb, void *stream); size_t (*read_head_fun)( void *ptr, size_t size, size_t nmemb, void *stream);
}curl_obj; static size_t recive_data_fun( void *ptr, size_t size, size_t nmemb, void *stream){ return fwrite(ptr,size,nmemb,(FILE*)stream);
} static size_t read_head_fun( void *ptr, size_t size, size_t nmemb, void *stream){ char head[2048] = {0}; memcpy(head,ptr,size*nmemb+1); printf(" %s \n",head); return size*nmemb;
} int main(int argc, char **argv)
{ if(argc < 3){ printf("ERROR----arg1 is url; arg2 is out file\n"); return -1; } char* outfile_name[CURL_MULIT_MAX_NUM] = {0}; curl_obj obj[CURL_MULIT_MAX_NUM]; int mulit_h_num = ((argc -1) < CURL_MULIT_MAX_NUM)? (argc -1):CURL_MULIT_MAX_NUM; CURLM *multi_handle = curl_multi_init();     for(int i=0;i<mulit_h_num;i++){ obj[i].fetch_url = strdup( argv[i+1]);//need free char out_filename[1024] ; sprintf(out_filename,"/storage/external_storage/sda4/%s",strrchr( argv[i+1], '/')); printf("----save_file[%d] [%s]\n",i,out_filename); obj[i].save_file = fopen(out_filename,"w"); if(!obj[i].save_file){ printf("ERROR----fail!!!\n"); goto release; } obj[i].curl_handle = curl_easy_init(); obj[i].recive_data_fun = recive_data_fun; obj[i].read_head_fun = read_head_fun; if(obj[i].curl_handle){ curl_easy_setopt(obj[i].curl_handle, CURLOPT_NOSIGNAL, 1L);curl_easy_setopt(obj[i].curl_handle, CURLOPT_URL, obj[i].fetch_url);//set down load url curl_easy_setopt(obj[i].curl_handle, CURLOPT_WRITEDATA, obj[i].save_file);//set download file curl_easy_setopt(obj[i].curl_handle, CURLOPT_WRITEFUNCTION, obj[i].recive_data_fun);//set call back fun
#ifdef CURL_DEBUG curl_easy_setopt(obj[i].curl_handle, CURLOPT_VERBOSE, 1);
#else curl_easy_setopt(obj[i].curl_handle, CURLOPT_HEADERFUNCTION, obj[i].read_head_fun);//set call back fun
#endif  if(multi_handle) curl_multi_add_handle(multi_handle, obj[i].curl_handle);//add task }else{ printf("fetch [%s]----ERROR!!!\n",obj[i].fetch_url ); //goto release; } } int still_running,repeats;curl_multi_perform(multi_handle, &still_running); do { int numfds = 0; long timeout_ms = CURL_WAIT_TIMEOUT_MSECS; curl_multi_timeout(multi_handle, &timeout_ms);//get timeout ms instead CURLMcode retcode = curl_multi_wait(multi_handle, NULL, 0, timeout_ms, &numfds); if (retcode != CURLM_OK) { printf("ERROR----curl_multi_wait  errorcode[%d]\n",retcode);           break; } /* 'numfds' being zero means either a timeout or no file descriptors towait for. Try timeout on first occurrence, then assume no filedescriptors and no file descriptors to wait for means wait for 10milliseconds. */if(!numfds) {if(repeats++ > 60){printf("ERROR----timeout break!!! \n");           break;}else{usleep(10*1000);  /* sleep 10 milliseconds */continue;}}else{repeats = 0;}retcode = curl_multi_perform(multi_handle, &still_running); if (retcode != CURLM_OK) { printf("ERROR----curl_multi_perform  errorcode[%d]\n",retcode); break; } //printf("still_running[%d]\tnumfds[%d]\n",still_running,numfds );int msgs_left = 0; CURLMsg *msg = NULL; while ((msg = curl_multi_info_read(multi_handle, &msgs_left)) != NULL){ if (msg->msg == CURLMSG_DONE) { long http_response_code = -1; char* http_url = NULL; curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &http_response_code); curl_easy_getinfo(msg->easy_handle, CURLINFO_EFFECTIVE_URL, &http_url);printf("[%s]fetch done, response[%d]\n",http_url,http_response_code ); } } } while (still_running); release:  printf("release\n"); for(int i=0;i<mulit_h_num;i++){ if(obj[i].curl_handle){ curl_multi_remove_handle(multi_handle, obj[i].curl_handle); curl_easy_cleanup(obj[i].curl_handle); } if(obj[i].save_file){ fclose(obj[i].save_file); obj[i].save_file = NULL; } if(obj[i].fetch_url){ free(obj[i].fetch_url); obj[i].fetch_url = NULL; } } if(multi_handle !=NULL){curl_multi_cleanup(multi_handle); }return 0;
}

libcurl 使用方法简介相关推荐

  1. 【机器学习入门到精通系列】蒙特卡罗方法简介和代码演示

    文章目录 1 蒙特卡罗方法简介 2 求圆周率pi的近似值 3 求定积分的近似值 1 蒙特卡罗方法简介 蒙特卡罗(Monte Carlo)方法:简单来说,蒙特卡洛的基本原理简单描述是先大量模拟,然后计算 ...

  2. 【Groovy】编译时元编程 ( ASTTransformation#visit 方法简介 | org.codehaus.groovy.ast.ModuleNode 脚本节点 )

    文章目录 一.ASTTransformation#visit 方法简介 二.org.codehaus.groovy.ast.ModuleNode 脚本节点 一.ASTTransformation#vi ...

  3. 【Android 异步操作】AsyncTask 异步任务 ( 参数简介 | 方法简介 | 使用方法 | AsyncTask 源码分析 )

    文章目录 一.AsyncTask 参数简介 二.AsyncTask 方法简介 三.AsyncTask 基本用法 四.AsyncTask 构造函数源码解析 五.AsyncTask 构造函数相关源码注释 ...

  4. UA PHYS515A 电磁理论III 静磁学问题2 标量势方法与向量势方法简介

    UA PHYS515A 电磁理论III 静磁学问题2 标量势方法与向量势方法简介 标量势方法 向量势方法 Hard Ferromagnets 标量势方法 当空间中不存在电流密度时(J⃗=0\vec J ...

  5. 微信门店小程序怎样创建 门店小程序创建方法简介

    微信门店小程序怎样创建 门店小程序创建方法简介 微信门店小程序是什么东西?门店小程序要怎样创建?还不清楚微信门店小程序详情的小伙伴们抓紧时间跟上小编一起来看一下吧!     门店小程序是什么? 微信发 ...

  6. ML之相似度计算:图像数据、字符串数据等计算相似度常用的十种方法简介、代码实现

    ML之相似度计算:图像数据.字符串数据等计算相似度常用的十种方法简介.代码实现 目录 相似度 1.余弦相似性-夹角余弦(Cosine_Distance)距离 2.代码实现-余弦距离.余弦相似度 2.皮 ...

  7. Redis Cluster搭建方法简介22211111

    Redis Cluster搭建方法简介 (2013-05-29 17:08:57) 转载▼ Redis Cluster即Redis的分布式版本,将是Redis继支持Lua脚本之后的又一重磅功能,官方声 ...

  8. html5 filereader读取文件,H5的FileReader分布读取文件应该如何使用以及其方法简介...

    这次给大家带来H5的FileReader分布读取文件应该如何使用以及其方法简介,H5的FileReader分布读取文件的使用以及其方法简介的注意事项有哪些,下面就是实战案例,一起来看一下. 先介绍一下 ...

  9. TabBarController创建及使用方法简介

    TabBarController创建及使用方法简介 大致讲解一下TabBarController的创建过程: 首先,我们需要一些视图,如创建UIControllerView类型的view1,view2 ...

  10. left join 和join区别_sleep、yield、join方法简介与用法 sleep与wait区别 多线程中篇

    Object中的wait.notify.notifyAll,可以用于线程间的通信,核心原理为借助于监视器的入口集与等待集逻辑 通过这三个方法完成线程在指定锁(监视器)上的等待与唤醒,这三个方法是以锁( ...

最新文章

  1. SAP事务码MM17物料主数据批量维护
  2. 左滑右滑,在VS Code里滑个妹纸给你写喜欢的代码?
  3. C语言中,全局变量滥用的后果竟如此严重?
  4. JMeter4.0以上 分布式测试报错 server failed start Listen failed on port
  5. STM32串口开发之环形缓冲区
  6. 建议检察院服务器服务器配置 显示器,切换器 键鼠
  7. linux 路径 冒号_软件测试必须掌握的linux命令大全
  8. 数据库工作笔记012---mysql触发器trigger 实例详解_保证数据库完整性还是不错的
  9. 杭电 -- 2553 N皇后问题
  10. 麦子学院-第一阶段-测试基础
  11. python基础--字典
  12. java 线程 condition_JAVA多线程按指定顺序执行线程 Condition应用
  13. 朱兰的质量观(转载)
  14. 排名前5的iOS测试自动化框架
  15. 良田摄像头 linux,良田万能摄像头高拍仪驱动下载|良田万能摄像头驱动 官方版 - 软件下载 - 绿茶软件园|33LC.com...
  16. CAN报文解析—案例
  17. vue中组件根元素添加样式无效
  18. operator开发流程
  19. 《广西经济社会发展报告(2019)》正式发布 聚焦发展热点
  20. 国产银河麒麟系统V10忘记密码重置操作

热门文章

  1. 当“程序猿”遇到“母亲节”,看他们是如何送上特殊的祝福
  2. 微信如何直接下载apk,实现微信点击超链接自动下载APP
  3. JAVA项目答辩题之参考_Java项目答辩
  4. 《激荡三十年》十八、青春飞扬——互联网的崛起
  5. fir高通滤波器matlab程序,FIR数字滤波器的Matlab实现[高等教育]
  6. 2022年6月 青少年软件编程(Python) 等级考试试卷(二级)
  7. java获取iso周_java实现iso的周数计算
  8. ios审核新坑解决,因为需要访问相机权限被拒绝We noticed that your app requests the user’s consent to access their camera
  9. 凤凰系统安装到移动硬盘教程
  10. 分析完百年飞机空难数据,我发现了这几条“保命”小秘诀