HTTP协议 学习:2-基于libcurl的开发
HTTP协议 学习:2-基于libcurl的开发
背景
上一讲我们介绍了HTTP报文的一些内容,这一讲我们基于http有关的开源库,进行HTTP通信。最后再完成一个简单的下载小程序。
curl 简介
curl是一个跨平台的开源网络协议库,支持http, https, rtsp等多种协议 。libcurl同样支持HTTPS证书授权,HTTP POST, HTTP PUT, FTP 上传, HTTP基本表单上传,代理,cookies和用户认证。
libcurl主要提供了两种发送HTTP请求的方式,分别是easy interface方式和multi interface方式。更多内容可以参考:《The libcurl API》
- easy interface:采用阻塞的方式发送单条请求
- multi interface:采用组合的方式可以一次性发送多条请求数据,支持多个下载请求是异步进行的。
基于easy interface 的 使用
初始化与释放有关的句柄(handle)
CURL *curl_easy_init( );
描述:初始化,并且它返回一个easy interface 的 handle, 使用该handle作为easy接口中其他函数的输入。
void curl_easy_cleanup(CURL * handle );
描述:当操作完成时,释放handle。
设置HTTP传输参数
CURLcode curl_easy_setopt(CURL *handle, CURLoption option, parametr);
描述:设置此次传输的一些基本参数,如url地址、http头、cookie信息、发送超时时间等,其中,CURLOPT_URL是必设的选项。
该函数是整个模块的核心,使用该函数,我们可以设置很多相关操作,正是由于该函数的存在,才使得libcurl变的简单且具备多种可操作性。curl_easy_setopt一些经常使用的方式,会在后面补充
开始HTTP请求
CURLcode curl_easy_perform(CURL * easy_handle);
描述:在上述准备工作(curl_easy_setopt
)已经完成后,可以调用curl_easy_perform函数,则会开始HTTP的请求工作。该接口是一个阻塞的接口。
(可选)获取相关信息
CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ... );
描述:请求过程中,可以使用下面函数,获取HTTP该次请求的相关信息,包括response
code,下载的URL,下载速度等。该函数对于一次请求不是必须的。
第 2 个参数有众多选项,每个选项都有其相应的含义,第3个参数根据参数2,会有不同类型。常用的info如下:
- CURLINFO_RESPONSE_CODE: 取得response code,要求第 3 个参数是个 long
型的指针。如果TCP就连接不上,值为0。 CURLINFO_EFFECTIVE_URL
取得本最终生效的URL,也即是如果有多次重定向,获取的值为最后一次URL,要求第 3 个参数是个 char 型的指针。 - CURLINFO_SIZE_DOWNLOAD :获取下载字节数,要求第 3 个参数是个 double
型的指针。注意,这个字节数只能反映最近一次的下载。 - CURLINFO_SPEED_DOWNLOAD :获取平均下载数据,该选项要求传递一个
double 型参数指针,这个速度不是即时速度,而是下载完成后的速度,单位是 字节/秒 - CURLINFO_TOTAL_TIME :获取传输总耗时,要求传递一个 double 指针到函数中,这个总的时间里包括了域名解析,以及 TCP 连接过程中所需要的时间。
- CURLINFO_CONTENT_TYPE :获得 HTTP 中从服务器端收到的头部中的 Content-Type 信息。
- 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库程序将有如何的行为。 (这个函数有些像ioctl函数)
参数解析:
handle: CURL类型的指针
option:各种CURLoption类型的选项.。
parameter: 参数,根据option
的不同可以取不同的值。
下面介绍几个常用的参数及使用方法:
libcurl远远不止这几个选项,更详细的使用方法可以参考官方文档。
CURLOPT_URL
这个选项必须要有,设置请求的URL ,如果URL参数不写上协议头(如 “http://” 或者 "ftp:// 等等),那么函数会自己进行猜解所给的主机上用的是哪一种服务协议。
假如给的这个地址是一个不被支持的协议,那么在其后执行curl_easy_perform() 函数或 curl_multi_perform() 函数时,libcurl将返回错误(CURLE_UNSUPPORTED_PROTOCOL)。 这个选项是唯一一个在 curl_easy_perform()调用之前就一定要设置的选项。
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属性给默认回调函数传递一个已经打开的文件指针,用于将数据输出到文件里。
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;
CURLOPT_HTTPHEADER
libcurl有自己默认的请求头,如果不符合我们的要求,可以使用该选项自定义请求头。可以使用curl_slist_append进行自定义,重设,如果设置请求参数为空,则相当于删除该请求头。
CURLOPT_USERAGENT
该选项要求传递一个以 ‘\0’ 结尾的字符串指针,这个字符串用来在向服务器请求时发送 HTTP 头部中的 User-Agent 信息,有些服务器是需要检测这个信息的,如果没有设置User-Agent,那么服务器拒绝请求。设置后,可以骗过服务器对此的检查。
CURLOPT_VERBOSE
在使用该选项且第 3 个参数为 1 时,curl 库会显示详细的操作信息。这对程序的调试具有极大的帮助。
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指定函数的第一个参数。
CURLOPT_TIMEOUT,CURLOPT_CONNECTIONTIMEOUT
超时相关设置,时间单位为s
1)CURLOPT_TIMEOUT设置整个libcurl传输超时时间。
2)CURLOPT_CONNECTIONTIMEOUT 设置连接等待时间。设置为0,则无限等待。
CURLOPT_FOLLOWLOCATION,CURLOPT_MAXREDIRS
重定向相关设置
1)CURLOPT_FOLLOWLOCATION 设置为非0,响应头信息Location,即curl会自己处理302等重定向
2)CURLOPT_MAXREDIRS指定HTTP重定向的最大次数
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
CURLOPT_POSTFIELDS,CURLOPT_POSTFIELDSIZE
1)CURLOPT_POSTFIELDS 传递一个作为HTTP “POST”操作的所有数据的字符串。
2)CURLOPT_POSTFIELDSIZE 设置POST 字节大小。
CURLOPT_NOBODY
设置该属性即可告诉libcurl我想发起一个HEAD请求 有时候你想查询服务器某种资源的状态,比如某个文件的属性:修改时间,大小等等,但是并不需要具体得到该文件,这时我们仅需要HEAD请求。
CURLOPT_ACCEPT_ENCODING
设置libcurl对特定压缩方式自动解码,支持的方式有: “br, gzip, deflate”. 第3个参数为指定的压缩方式,如果设置为 " ",则表明三种都支持。
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。
CURLOPT_FORBID_REUSE ,CURLOPT_FRESH_CONNEC
如果不使用长连接,需要设置这两个属性
1)CURLOPT_FORBID_REUSE 设置为1,在完成交互以后强迫断开连接,不重用。
2)CURLOPT_FRESH_CONNECT设置为1,强制获取一个新的连接,替代缓存中的连接。
CURLOPT_NOSIGNAL
当多个线程都使用超时处理的时候,同时主线程中有sleep或是wait等操作。如果不设置这个选项,libcurl将会发信号打断这个wait从而可能导致程序crash。 在多线程处理场景下使用超时选项时,会忽略signals对应的处理函数。
CURLOPT_BUFFERSIZE
指定libcurl中接收缓冲区的首选大小(以字节为单位),但是不保证接收数据时每次数据量都能达到这个值。此缓冲区大小默认为CURL_MAX_WRITE_SIZE(16kB)。允许设置的最大缓冲区大小为CURL_MAX_READ_SIZE(512kB)。 允许设置的最小缓冲区大小为1024。
DNS相关选项
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
CURLOPT_DNS_CACHE_TIMEOUT
设置libcurl DNS缓存超时时间,默认为60秒,即每60秒清一次libcurl自身保存的DNS缓存。
如果设置为0,则不适用DNS缓存,设置为-1,则永远不清缓存。
CURLOPT_DNS_USE_GLOBAL_CACHE
让libcurl使用系统DNS缓存,默认情况下,libcurl使用本身的DNS缓存。
例程:使用easy interface完成下载
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <curl/curl.h>
//#define CURL_DEBUG 1
#define CURL_WAIT_TIMEOUT_MSECS 60000 //60s
#define CURL_MULIT_MAX_NUM 5
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("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;
//int
FILE* 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 url
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, save_file);//set download file
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, recive_data_fun);//set call back fun
curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, read_head_fun);//set call back fun
#ifdef CURL_DEBUG
curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1);
#endif
//start down load
res = curl_easy_perform(curl_handle);
printf("curl fetch code %d\n",res);
}
//release
if(save_file){
fclose(save_file);
save_file = NULL;
}
if(curl_handle){
curl_easy_cleanup(curl_handle);
}
if(url){
free(url);
}
return 0;
}
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
对象。
初始化 与 释放
和easy interface类似,multi 的初始化和清除函数如下:
CURLM *curl_multi_init( );
CURLMcode curl_multi_cleanup( CURLM* multi_handle);
添加 与 移除
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来进行传输任务。
开始HTTP并发请求
CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles);
添加easy handler到multi并不马上开始执行,由curl_multi_perform启动执行。
启动后将执行所有multi stack中的收发事件。如果栈上是空的直接返回。函数参数running_handles会返回当前还未结束的easy handler个数。
等待及超时
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)
#endif
fd_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;
}
else
rc = 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 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;
} while(still_running);
curl_multi_remove_handle(multi_handle, easy_handle);
对于select和curl_multi_wait两种方式,都可以使用curl_multi_timeout获取一个合适的超时时间,当然,超时时间也可以我们自己设置。
(可选)获取操作信息
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 loop
through 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 to
wait for. Try timeout on first occurrence, then assume no file
descriptors and no file descriptors to wait for means wait for 10
milliseconds. */
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;
}
HTTP协议 学习:2-基于libcurl的开发的更多相关文章
- TCP/IP协议学习(五) 基于C# Socket的C/S模型
TCP/IP协议作为现代网络通讯的基石,内容包罗万象,直接去理解理论是比较困难的:然而通过实践先理解网络通讯的理解,在反过来理解学习TCP/IP协议栈就相对简单很多.C#通过提供的Socket API ...
- TCP/IP协议学习(七) 基于C# Socket的Web服务器---动态通讯实现
目录 (1).基于Ajax的前端实现 (2).Web服务器后端处理 一个完整的web服务器,不仅需要满足用户端对于图片.文档等资源的需求:还能够对于用户端的动态请求,返回指定程序生成的数据.支持动态请 ...
- TCP/IP协议学习(四) 基于C# Socket的Web服务器---静态资源处理
目录 1. C# Socket通讯 2. HTTP 解析引擎 3. 资源读取和返回 4. 服务器测试和代码下载 Web服务器是Web资源的宿主,它需要处理用户端浏览器的请求,并指定对应的Web资源返回 ...
- 学习参考《Flask Web开发:基于Python的Web应用开发实战(第2版)》中文PDF+源代码
在学习python Web开发时,我们会选择使用Django.flask等框架. 在学习flask时,推荐学习看看<Flask Web开发:基于Python的Web应用开发实战(第2版)> ...
- Spring框架学习09——基于AspectJ的AOP开发
1.基于注解开发AspectJ (1)AspectJ注解 基于注解开发AspectJ要比基于XML配置开发AspectJ便捷许多,所以在实际开发中推荐使用注解方式.关于注解的相关内容如下: @Aspe ...
- HTTP协议学习
面试过程中又一个常见的问题,http协议,因为做服务器开发如果用http协议的话,现在各种开源软件都封装好了,python中只需要简单的继承定义好的类,重写get或者post等方法,几行代码就可以搭建 ...
- HTTP协议学习【转】
面试过程中又一个常见的问题,http协议,因为做服务器开发如果用http协议的话,现在各种开源软件都封装好了,python中只需要简单的继承定义好的类,重写get或者post等方法,几行代码就可以搭建 ...
- 基于.NET网页开发的工作,需要掌握的知识点
学习计划对于程序员来说尤为重要,我最近根据自己的职业规划和招聘网站上对于基于.NET网页开发工作所需要的技能做出了一个总结,这个总结的内容也将是自己最近一年的知识补充和学习的方向,各位园友也可以把它作 ...
- Java基础学习总结(70)——开发Java项目常用的工具汇总
要想全面了解java开发工具,我们首先需要先了解一下java程序的开发过程,通过这个过程我们能够了解到java开发都需要用到那些工具. 首先我们先了解完整项目开发过程,如图所示: 从上图中我们能看到一 ...
- 网络协议学习笔记(四)传输层的UDP和TCP
概述 传输层里比较重要的两个协议,一个是 TCP,一个是 UDP.对于不从事底层开发的人员来讲,或者对于开发应用的人来讲,最常用的就是这两个协议.由于面试的时候,这两个协议经常会被放在一起问,因而我在 ...
随机推荐
- C#的基于.net framework的Dll模块编程(四) - 编程手把手系列文章
这次继续这个系列的介绍: 一.命名空间的起名: 对于C#来说,一般命名空间的建议是:公司名(或个人名称).产品名.分类名,比如我这边是用的这个:Lzhdim.LPF.Helper,意思是个人名Lzhd ...
- Spring学习一(依赖注入/Bean/注解等)
1.Spring依赖注入的方式. 2.依赖注入的类型 3.Bean的作用域 4.自动注入 5.使用注解的方式 6.在spring配置文件中引入属性文件 1.Spring依赖注入的方式 平常的java开 ...
- Solution Set - 组合计数
CF40E Number Table Link&Submission. 显然 \(n,m\) 奇偶性不同时无解.奇偶性相同时,假设有一行全为空,剩下每行至少一个有空,则除这些位置外没有限制的位 ...
- ES_CCS/R(二):跨集群搜索 Cross-cluster search (CCS)
跨集群搜索(cross-cluster search)使你可以针对一个或多个远程集群运行单个搜索请求. 例如,你可以使用跨集群搜索来筛选和分析存储在不同数据中心的集群中的日志数据. 示例 :在一个集群 ...
- 9、iptables 防火墙
1.iptables 基础规则 1.1.Linux 包过滤防火墙 netfilter 位于 Linux 内核中的包过滤功能体系 称为 Linux 防火墙的 "内核态" iptabl ...
- homebrew的安装和使用
目录 背景 安装xcode 安装homebrew 有关报错解决 卸载脚本 homebrew软件搜索 brew 常用命令 brew redis安装 PhpWebStudy安装 安装php 背景 最近用b ...
- 🔥httpsok-v1.11.0支持CDN证书自动部署
httpsok-v1.11.0支持CDN证书自动部署 介绍 httpsok 是一个便捷的 HTTPS 证书自动续签工具,专为 Nginx .OpenResty 服务器设计.已服务众多中小企业,稳定.安 ...
- postgresql 创建索引
--查询索引 select * from pg_indexes where tablename='tab1'; --创建索引(查询用到哪几列,就对哪几个字段创建索引) CREATE INDEX ind ...
- C 语言编程 — 高级数据类型 — 字符串
目录 文章目录 目录 前文列表 字符串 前文列表 <程序编译流程与 GCC 编译器> <C 语言编程 - 基本语法> <C 语言编程 - 基本数据类型> <C ...
- AIRIOT答疑第3期|如何使用物联网平台的可视化组态引擎?
丰富组件,满足千人千面! AIRIOT物联网低代码平台的可视化组态引擎,具备丰富的可视化看板及组件,满足各类工艺流程图.数据可视化需求.支持三维编辑.图形绘制.图表设计等设计方式,PPT模式设计软件界 ...