socket是tcp、udp连接基石。TSL是加密通信,使用openssl库加密通信。
有了socket连接,可以自己组应用层协议,实现http、mqtt等通信。可以通过移植libcurl、mqtt库等来更方便实现应用层网络传输。
1 简介
参考:https://blog.csdn.net/huohongpeng/category_10856701.html
1.1 支持协议
libcurl库支持这些应用层协议:DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, POP3, POP3S, RTMP, RTMPS, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET and TFTP。curl 支持 SSL 认证, libcurl同样支持HTTPS证书授权,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是免费的,线程安全的,IPv6兼容,特性丰富,快速并且已经被很多成功知名的大公司应用。
1.2 参考网址
1.2.1 libcurl网站的入口
https://curl.se/libcurl/
1.2.2 《Everything curl 》书网页版,其中libcurl basics和HTTP with libcurl章节对编程有很大的参考意义
https://everything.curl.dev/
1.2.3 libcurl的编程示例,常用的功能都有例程源码
https://curl.se/libcurl/c/example.html
1.2.4 curl_easy_setopt()函数的选项说明
https://curl.se/libcurl/c/curl_easy_setopt.html
2 Easy和Multi
Easy接口是同步的,高效的,可以快速应用,大量的应用是基于这些接口的。Multi接口功能上和Easy没有区别,但是Multi是异步的,Easy同步的,也就是说只能一次完成一个传输,要想开始新的传输,就得等之前的传输完成,有点类似Linux中的阻塞执行。
相反Multi是异步的有点类似非阻塞执行,就像Linux下使用select或者poll在单线程下,实现多路复用的功能。我自己觉得Easy更常用,如果需要多次执行可以使用Easy结合多线程编程,libcurl是线程安全的。我的文章也是重点介绍Easy相关的接口,不介绍Multi接口。
3 Easy接口的使用
当然在使用上面这些函数之前需要对整个库进行初始化,用到的函数就是curl_global_init(),整个库都不再使用了或者程序要结束了调用curl_global_cleanup()去释放所有资源。
4 其他常用接口
除了常用的传输相关的函数还有一些常用的辅助函数:
curl_version() //返回libcurl版本相关信息的指针
curl_getdate() //转换一个日期 string 到 time_t
curl_mime_init() //一些mime系列函数对于HTTP POST表单的操作
curl_formadd() //也是HTTP POST表单的操作,但是这个接口比较老了
curl_slist_append() //建立一个清单的链表,最常用的就是HTTP头的添加和删除。
curl_slist_free_all() //释放通过curl_slist_append()创建的curl_slist
curl_easy_escape() //URL编码
curl_easy_unescape() //URL解码
5 总结
这篇文章只是简单的对libcurl的一个整体介绍,后面针对具体的函数和例程进行讲解
名称:curl_global_init
原型
#include
CURLcode curl_global_init(long flags);
描述
这个函数设置libcurl需要的程序运行环境,可以把它看作一个的加载器扩展
在进程环境(所有代码共享内存地址空间的程序)下,libcurl其他所有函数被调用之前这函数至少需要被调用一次。在进程的生命周期内,这个环境设置是没有变化的,所以在同一个程序中,多次调用和只调用一次没有区别。
这个flags选项精确的告诉libcurl那些特性需要被初始化,这些特性可以按位异或,一般情况下都是使用 CURL_GLOBAL_ALL这个标志。除非你非常了解这个哭的内部原理,否则不建议使用其他参数。
这个函数不是线程安全的。千万不要在进程中的其他线程里面去调用。这并不是说使用libcurl不能使用多线程。curl_global_init()不是线程安全的,主要是因为它调用了其他函数库中的函数,而这些函数可能不是线程安全的,一般情况都是在进程刚开始的时候,没有创建其他线程之前调用curl_global_init()。
FLAGS
CURL_GLOBAL_ALL
CURL_GLOBAL_SSL
CURL_GLOBAL_WIN32
CURL_GLOBAL_NOTHING
CURL_GLOBAL_DEFAULT
CURL_GLOBAL_ACK_EINTR
返回值
当发生错误时会返回非0值,如果返回非0值,代表初始化失败了,不要使libcurl的其他功能
名称: curl_global_cleanup
原型
#include
void curl_global_cleanup(void);
描述
这个函数释放 curl_global_init()已经获得的所有资源。同样这个函数也不是线程安全的。
警告
curl_global_cleanup不阻塞的等待任何被libcurl创建的线程终止(例如name resolving线程)。如果一个包含libcurl被动态的卸载,但是libcurl创建的线程仍然运行,这可能引起程序的崩溃。一般在整个程序(进程)结束之前调用这个函数释放资源,就不会有问题。
名称:curl_easy_init
原型
#include
CURL *curl_easy_init( );
描述
这个函数必须首先被调用,他返回CURL easy会话的一个句柄,这个句柄作为其他easy相关接口函数的输入参数。当所有操作完成后,必须对应的调用 curl_easy_cleanup函数释放资源,也就是说
curl_easy_init和curl_easy_cleanup成对出现,就像malloc和free。
如果在curl_easy_init之前没有调用curl_global_init,那么curl_easy_init会自动的调用curl_easy_init。但是在多线程情况下这是致命的错误,因为curl_easy_init不是线程安全的。也能会会引起资源泄露,因为没有对应的curl_global_cleanup释放资源。
强烈的建议你适当的显示调用curl_global_init,不要允许自动的行为。一般都是在进程开始的时候显示的调用curl_global_init,在进程退出之前显示的调用curl_global_cleanup。
返回值
如果这个函数返回NULL,说明发生了错误,不在在使用其他的curl函数
示例
CURL *curl = curl_easy_init();
if(curl)
{
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
名称: curl_easy_cleanup
原型
#include
void curl_easy_cleanup(CURL *handle);
描述
这个函数必须在整个easy会话的最后被调用,它正好和curl_easy_init相反。这个函数可能关闭和这个句柄相关的所有连接。如果你打算传输多个文件,请不要每次都调用这个函数,这个句柄打开以后,重复使用这个句柄传输所有文件,都传输完成以后,在调用这个函数,释放这个句柄。重复使用句柄是libcurl高性能使用的关键。
这个函数也可以在progress callback 或者header callback中调用,这样可以终止连接。
(句柄)handle被这个函数清理以后,在继续使用都是非法的。curl_easy_cleanup已经清理掉了这个句柄和这个句柄相关的所有内存。
如果这个函数输入的句柄是一个空指针,这个函数会立即返回。
示例
CURL *curl = curl_easy_init();
if(curl)
{
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
名称: curl_easy_setopt
原型
#include
CURLcode curl_easy_setopt(CURL *handle, CURLoption option, parameter);
描述
curl_easy_setopt 用于告诉libcurl需要做什么,或者是告诉libcurl需要完成哪些任务。通过设置合适的选项(options),应用可以改变libcurl的行为。所有被设置的选项后面都会跟随一个参数(parameter)。参数可以是一个long型变量,函数指针,一个对象(结构体)指针或是一个curl_off_t变量,跟随的这个参数是什么取决于选项(option)。需要仔细的阅读这个手册,因为一个不合理的输入值可能引起libcurl的行为异常。你可以在每次函数调用只设置一个选项。典型的应用程会在设置阶段多次调用curl_easy_setopt。通过这个函数设置handle的选项,对于后面使用这个handle来执行的传输都是有效的。在每一次传输完成后这些选项不会被复位,所以如果你想在后面的传输使用不同的选项,你可以在本次传输完成后,下一次传输执行前,通过调用curl_easy_setopt 来改变选项。也可以使用curl_easy_reset复位所有选型到默认值。
字符串作为’char *'类型传给libcurl,libcurl会对字符串进行拷贝;因此在curl_easy_setopt返回后,参数指针指向的字符串存储空间可以被复写或者释放。只有CURLOPT_POSTFILEDS这种情况除外。但是可以使用CURLOPT_COPYPOSTFILEDS作为替代。curl_easy_setopt不接受大小超过8M长度的字符串。
对于选项设置的顺序是没有要求的。
在7.17.0以前的版本,字符串是不被开拷贝的。所以强制用户保持字符串是有效的指导这些字符串不再需要。
这个handle是从curl_easy_init或者curl_easy_duphandle的调用返回的。
curl_easy_setopt有很多很多的选项,我在后面结合实际使用在做具体的介绍。
CURLOPT_VERBOSE
CURLOPT_WRITEFUNCTION
CURLOPT_WRITEDATA
CURLOPT_READFUNCTION
CURLOPT_READDATA
...
...
返回值
CURL_OK(0)代表使用了恰当的设置,如果是非0值意味着有错误发生。
如果你设置的选项libcurl不知道是什么,有以下可能的原因:
如果返回CURLE_UNKNOWN_OPTION,可能的原因是这个选项是老版本的库使用的,在新版本库用被移除了。
如果返回CURL_NOT_BUILD_IN,是因为在编译的时候禁止了相关的功能。
示例
CURL *curl = curl_easy_init();
if(curl)
{
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
名称:curl_easy_perform
原型
#include
CURLcode curl_easy_perform(CURL *easy_handle);
描述
这个函数在curl_easy_init和所有的curl_easy_setopt参数设置完成后调用。调用这个函数libcurl将按照设置的选项执行动作。这个函数需要使用curl_easy_init函数返回的相同的easy_handle作为输入被调用。
curl_easy_perform通过阻塞的方式执行全部的请求,当执行完以后或者失败了才返回。对于非阻塞的方式,可以参考curl_multi_perform.
你可以使用相同的easy_handle多次调用curl_easy_perform.如果你打算传输一个或更多个文件,更推荐你使用这种方式。对于之后的传输libcurl将尝试重用相同的链接,因此会更快,更少的使用cpu和网络资源。需要注意的是,使用curl_easy_setopt在两次curl_easy_perform去设置选项(比如每个不同的传输肯定需要不同的CURLOPT_URL)。
相同easy_handle,绝不能在两个不同的地方同时调用curl_easy_perform。在下一调用的时候,必须让这个函数先返回,也换句话说就是按顺序阻塞的执行。如果想并行传输,必须使用多个easy_handle。
当easy_handle添加到multi handle中,他将不能在使用curl_easy_perform。
返回值
CURL_OK(0)意味着一切都是正常的,非0意味着发生了一个错误,错误定义在
示例
CURL *curl = curl_easy_init();
if(curl)
{
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
名称 curl_easy_getinfo
原型
#include
CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ... );
描述
使用这个函数从curl会话请求内部信息。第三个参数必须是一个long变量,一个指向char *的指针,一个指向struct curl_slist *指针或者一个指向double的指针(就像本文档下面将要描述的那样)。当这个函数返回CURL_OK时,这个被指向的数据会被相应的填充。
不用对这个函数返回的内存进行释放(free),除非在下面被特殊的强调。
有效的INFO
下面的信息可以被截取(有很多,可以去英文网站去看,有对应的示例代码):
CURLINFO_HTTP_VERSION 获取在连接时候使用的http版本。
CURL *curl = curl_easy_init();
if(curl)
{
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
res = curl_easy_perform(curl);
if(res == CURLE_OK)
{
long http_version;
curl_easy_getinfo(curl, CURLINFO_HTTP_VERSION, &http_version);
}
curl_easy_cleanup(curl);
}
CURLINFO_TOTAL_TIME 获取前一个传输的执行总时间
curl = curl_easy_init();
if(curl)
{
double total;
curl_easy_setopt(curl, CURLOPT_URL, url);
res = curl_easy_perform(curl);
if(CURLE_OK == res)
{
res = curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &total);
if(CURLE_OK == res)
{
printf("Time: %.1f", total);
}
}
/* always cleanup */
curl_easy_cleanup(curl);
}
CURLINFO_CONNECT_TIME 获取connet从开始到完成的时间
curl = curl_easy_init();
if(curl)
{
double connect;
curl_easy_setopt(curl, CURLOPT_URL, url);
res = curl_easy_perform(curl);
if(CURLE_OK == res)
{
res = curl_easy_getinfo(curl, CURLINFO_CONNECT_TIME, &connect);
if(CURLE_OK == res)
{
printf("Time: %.1f", connect);
}
}
/* always cleanup */
curl_easy_cleanup(curl);
}
CURLINFO_SPEED_UPLOAD_T 获取上传速度,单位bytes/second
CURL *curl = curl_easy_init();
if(curl)
{
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
/* Perform the request */
res = curl_easy_perform(curl);
if(!res)
{
curl_off_t speed;
res = curl_easy_getinfo(curl, CURLINFO_SPEED_UPLOAD_T, &speed);
if(!res)
{
printf("Upload speed %" CURL_FORMAT_CURL_OFF_T " bytes/sec\n", speed);
}
}
}
关于时间
可以从curl_easy_getinfo()获取下面看到的6个时间值
curl_easy_perform()
|
|--NAMELOOKUP
|--|--CONNECT
|--|--|--APPCONNECT
|--|--|--|--PRETRANSFER
|--|--|--|--|--STARTTRANSFER
|--|--|--|--|--|--TOTAL
|--|--|--|--|--|--REDIRECT
返回值
如果操作成功,返回CURLE_OK
1 摘要
1.1 平台
运行平台:君正x2000
libcurl版本:7.68.0,通过buildroot编译生成。
编译器:mips-linux-gnu-gcc (gcc version 7.2.0 (Ingenic r4.0.0-gcc720 2018.02-28))
编译主机:ubuntu 18.04
1.2 功能
http通信(1)
1.1最少的代码实现http访问百度,这个示例将通过get方法访问百度,并将百度返回的结果存入data.html文件中。
2 选项名:CURLOPT_URL
提供请求的URL
2.1 原型
#include
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_URL, char *URL);
2.2 描述
使用这个选项需要传入一个指向URL的指针。这个参数应该是一个char*有一个终止符(\0)的字符串,这些字符串必须使用下面这些格式的URL编码:
scheme://host:port/path
关于格式更详细的解释可以参考RFC 3986
libcurl执行传输(curl_easy_perform)之前不会检查URL的语法,也不会使用这个变量。所以无论你将URL设置成什么值,curl_easy_setopt都会正常返回CURL_OK.
如果URL中没有给出scheme名称(例如http://or ftp://等等),libcurl会根据host进行猜测。如果最外层sub-domain名称和 DICT, FTP, IMAP, LDAP, POP3 or SMTP匹配,那么这些协议将会被使用。其他情况会使用HTTP协议。在7.45.0版本如以后猜测功能将被会被设置默认协议禁止,对于细节可以查看CURLOPT_DEFAULT_PROTOCOL.
如果scheme指定的协议或者libcurl推理出来的协议如果libcurl不支持,那么在调用curl_easy_perform的时候会返回CURLE_UNSUPPORTED_PROTOCOL 错误。可以通过curl_version_info 查看具体的协议。
还可以通过CURLOPT_PROTOCOLS限制libcurl传输中使用的协议。对于从外部接收的URl进行限制,这是非常有用的。
如果设置了CURLOPT_CURLU,那么CURLOPT_URL将被忽略。
在开始传输之前必须要设置CURLOPT_CURLU和CURLOPT_URL。
在curl_easy_setopt设置之后,URL字符串可以被释放,不用保留。
2.3 示例
CURL *curl = curl_easy_init();
if(curl)
{
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
curl_easy_perform(curl);
}
3 选项名:CURLOPT_WRITEFUNCTION
设置被接收数据的写回调函数。(用于保存接收的数据)
3.1 原型
#include
size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_WRITEFUNCTION, write_callback
3.2 描述
传入回调函数的指针,回调函数是上面描述的格式。
当被接收的数据需要保存就会调用这个回调函数。对于大部分传输,这个回调函数会被调用多次,并且每次一执行都会提供一部分数据块。ptr指向一个被接收的数据,nmemb是数据的大小,size总是1.
回调函数会在每次调用中传递尽可能多的数据,但是你不能预测。可能是1个字节,也可能是几千个字节。回调函数单次传递的body data(http的数据部分)的最大数据量定义在curl.h文件中的CURL_MAX_WRITE_SIZE(默认大小是16K)。如果CURLOPT_HEADER被使能,head部份的数据也会通过这个回调函数进行传递。传递的头的最大数据量定义在CURL_MAX_HTTP_HEADER 中。通常情况下是100K。
如果传输的文件是空的,这个函数也可能传递0个字节的数据。
被传递的到这个函数的数据,没有null终止符(\0).
可以通过CURLOPT_WRITEDATA选项设置userdata参数。
回调函数应该返回实际处理的字节数。如果传递的值和返回的值不同,那么将会向libcurl发送一个错误信号。这将会引起传输终止,并且执行函数将会返回一个CURLE_WRITE_ERROR错误。
如果回调函数返回CURL_WRITEFUNC_PAUSE 将会引起回调函数挂起。可以在curl_easy_pause查看具体的细节。
如果回调函数指针为空,libcurl将使用库内部的回调函数。默认的回调函数将写数据到CURLOPT_WRITEDATA指定的FILE*。
libcurl将使用’fwrite’作为内部默认的回调函数。
3.3 示例
struct memory
{
char *response;
size_t size;
};
static size_t cb(void *data, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
struct memory *mem = (struct memory *)userp;
char *ptr = realloc(mem->response, mem->size + realsize + 1);
if(ptr == NULL)
return 0; /* out of memory! */
mem->response = ptr;
memcpy(&(mem->response[mem->size]), data, realsize);
mem->size += realsize;
mem->response[mem->size] = 0;
return realsize;
}
struct memory chunk;
/* send all data to this function */
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, cb);
/* we pass our 'chunk' struct to the callback function */
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
4 代码实现
Makefile
LIBS=-L../../../buildroot/output/target/usr/lib -lpthread -lz -lcurl -lcrypto -lssl
CC_FLAG=-Wall -I../../../buildroot/output/staging/usr/include
SRC=curl_test.c
CC=mips-linux-gnu-gcc
test : $(SRC)
$(CC) $< $(LIBS) $(CC_FLAG) -O2 -o $@
cp -v $@ ~/nfs
.PHONY : clean
clean :
rm -rf test
curl_test.c
#include
#include
#include
#include
#include
static size_t baidu_get_test_cb(void *buffer, size_t size, size_t nmemb, void *user_p);
static void baidu_get_test(void);
int main()
{
CURLcode code;
code = curl_global_init(CURL_GLOBAL_ALL);
if(CURLE_OK != code)
{
printf("curl init Err\n");
return -1;
}
/*
* get 方法访问百度并获取数据
*/
baidu_get_test();
curl_global_cleanup();
return 0;
}
static size_t baidu_get_test_cb(void *buffer, size_t size, size_t nmemb, void *user_p)
{
FILE *fp = (FILE *)user_p;
size_t return_size = fwrite(buffer, size, nmemb, fp);
return return_size;
}
static void baidu_get_test(void)
{
/*
* 获取一个easy句柄,后面通过这个句柄进行通信
*/
CURL *easy_handle = curl_easy_init();
if(!easy_handle)
{
printf("curl_easy_init Err \n");
curl_global_cleanup();
return ;
}
/*
* 打开一个data.html文件,用于存储百度返回的数据
*/
FILE *fp = fopen("data.html", "w+");
/*
* 设置libcurl的选项,没有指定http访问方法,libcurl默认使用get方法
*/
curl_easy_setopt(easy_handle, CURLOPT_URL, "http://www.baidu.com");
curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, baidu_get_test_cb);
curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, fp);
/*
* 执行访问
*/
CURLcode code = curl_easy_perform(easy_handle);
if(CURLE_OK != code)
{
printf("curl_easy_perform %d\n", code);
}
fclose(fp);
/*
* 最后记得释放句柄
*/
curl_easy_cleanup(easy_handle);
}
执行结果
# cat data.html
<!DOCTYPE html><!--STATUS OK-->
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<link rel="dns-prefetch" href="//s1.bdstatic.com"/>
<link rel="dns-prefetch" href="//t1.baidu.com"/>
<link rel="dns-prefetch" href="//t2.baidu.com"/>
<link rel="dns-prefetch" href="//t3.baidu.com"/>
<link rel="dns-prefetch" href="//t10.baidu.com"/>
<link rel="dns-prefetch" href="//t11.baidu.com"/>
<link rel="dns-prefetch" href="//t12.baidu.com"/>
<link rel="dns-prefetch" href="//b1.bdstatic.com"/>
<title>百度一下,你就知道</title>
<link href="http://s1.bdstatic.com/r/www/cache/static/home/css/index.css" rel="stylesheet" type="text/css" />
<!--[if lte IE 8]><style index="index" >#content{height:480px\9}#m{top:260px\9}
<!--[if IE 8]><style index="index" >#u1 a.mnav,#u1 a.mnav:visited{font-family:simsun}
<script>var hashMatch = document.location.href.match(/#+(.*wd=[^&].+)/);if (hashMatch && hashMatch[0] && hashMatch[1]) {document.location.replace("http://"+location.host+"/s?"+hashMatch[1]);}var ns_c = function(){};
<script>function h(obj){obj.style.behavior='url(#default#homepage)';var a = obj.setHomePage('//www.baidu.com/');}</script>
<noscript><meta http-equiv="refresh" content="0; url=/baidu.html?from=noscript"/></noscript>
<script>window._ASYNC_START=new Date().getTime();</script>
</head>
<body link="#0000cc"><div id="wrapper" style="display:none;"><div id="u"><a href="//www.baidu.com/gaoji/preferences.html" onmousedown="return user_c({'fm':'set','tab':'setting','login':'0'})">搜索设置</a>|<a id="btop" href="/" onmousedown="return user_c({'fm':'set','tab':'index','login':'0'})">百度首页</a>|<a id="lb" href="https://passport.baidu.com/v2/?login&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F" onclick="return false;" onmousedown="return user_c({'fm':'set','tab':'login'})">登录</a><a href="https://passport.baidu.com/v2/?reg®Type=1&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F" onmousedown="return user_c({'fm':'set','tab':'reg'})" target="_blank" class="reg">注册</a></div><div id="head"><div class="s_nav"><a href="/" class="s_logo" onmousedown="return c({'fm':'tab','tab':'logo'})"><img src="//www.baidu.com/img/baidu_jgylogo3.gif" width="117" height="38" border="0" alt="到百度首页" title="到百度首页"></a><div class="s_tab" id="s_tab"><a href="http://news.baidu.com/ns?cl=2&rn=20&tn=news&word=" wdfield="word" onmousedown="return c({'fm':'tab','tab':'news'})">新闻</a> 网页 贴吧 知道 音乐 图片 视频 地图 文库 更多»
©2018 Baidu 使用百度前必读 京ICP证030173号
1 摘要
1.1 平台
运行平台:君正x2000
libcurl版本:7.68.0,通过buildroot编译生成。
编译器:mips-linux-gnu-gcc (gcc version 7.2.0 (Ingenic r4.0.0-gcc720 2018.02-28))
编译主机:ubuntu 18.04
1.2 功能
http通信(1)
1.1最少的代码实现http访问百度,这个示例将通过get方法访问百度,并将百度返回的结果存入data.html文件中。
http通信(2)
2.1 增加获取libcurl库信息
2.2 增加打印打印http响应head数据
2.3 增加使能版本库调试功能
2 打印版本库信息
static void print_version_info(void)
{
curl_version_info_data *version_info = curl_version_info(CURLVERSION_NOW);
printf("version:%s\n", version_info->version);
printf("host:%s\n", version_info->host);
printf("ssl_version:%s\n", version_info->ssl_version);
}
int main()
{
........
........
/*
* 打印本本库信息
*/
print_version_info();
....
....
return 0;
}
2.1 执行结果
# ./test
version:7.68.0
host:mipsel-buildroot-linux-gnu
ssl_version:OpenSSL/1.1.1g
3 增加调试功能
CURLOPT_VERBOSE选项非常有用,当我们在调试嵌入式系统的时候没有wireshark等抓包工具,打开CURLOPT_VERBOSE选项可以看到libcurl的执行过程
/*
* 设置libcurl的选项,没有指定http访问方法,libcurl默认使用get方法
*/
curl_easy_setopt(easy_handle, CURLOPT_URL, "http://www.baidu.com");
curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, baidu_get_test_cb);
curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, fp);
/*
* 增加调试功能,可以查看libcurl的执行过程
*/
curl_easy_setopt(easy_handle, CURLOPT_VERBOSE, 1);
3.1 执行结果
# ./test
version:7.68.0
host:mipsel-buildroot-linux-gnu
ssl_version:OpenSSL/1.1.1g
* Trying 110.242.68.3:80...
* TCP_NODELAY set
* Connected to www.baidu.com (110.242.68.3) port 80 (#0)
> GET / HTTP/1.1
Host: www.baidu.com
Accept: */*
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Cache-Control: no-cache
< Connection: keep-alive
< Content-Length: 14615
< Content-Type: text/html
< Date: Sat, 20 Mar 2021 03:02:49 GMT
< P3p: CP=" OTI DSP COR IVA OUR IND COM "
< P3p: CP=" OTI DSP COR IVA OUR IND COM "
< Pragma: no-cache
< Server: BWS/1.1
< Set-Cookie: BAIDUID=948BD633A8A66E61E08D2D51C124E8F2:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
< Set-Cookie: BIDUPSID=948BD633A8A66E61E08D2D51C124E8F2; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
< Set-Cookie: PSTM=1616209369; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
< Set-Cookie: BAIDUID=948BD633A8A66E611382AE5374C312B9:FG=1; max-age=31536000; expires=Sun, 20-Mar-22 03:02:49 GMT; domain=.baidu.com; path=/; version=1; comment=bd
< Traceid: 161620936935453114985383216040678206036
< Vary: Accept-Encoding
< X-Ua-Compatible: IE=Edge,chrome=1
<
* Connection #0 to host www.baidu.com left intact
4 选项名:CURLOPT_HEADERFUNCTION
CURLOPT_WRITEFUNCTION 用于接收http get返回的数据部分信息,如果想接收http get返回的头部信息,可以使用CURLOPT_HEADERFUNCTION选项
4.1 原型
#include
size_t header_callback(char *buffer, size_t size, size_t nitems, void *userdata);
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_HEADERFUNCTION, header_callback);
4.2 描述
传递一个上面原型中类型的回调函数指针。
当libcurl接收头数据的时候这个函数被调用。每一个头部信息,都会调用一次这个函数并且只有完整的头才会产生回调。使用这个函数去解析头部是非常简单的。buffer指针指向被接被接收的数据。nitems是数据的大小,size总是1。不要认为一行头信息是以’\0’结束的。
userdata指针是你通过CURLOPT_HEADERDATA选项设置的。
回调函数必须返回实际被处理的字节个数,如果回调函数返回的和传进来的字节数不同,libcurl将会产生一个错误信号。这将会引起传输终止,并且执行函数将会返回一个CURLE_WRITE_ERROR错误。
传入到回到函数的完整的http可能多大CURL_MAX_HTTP_HEADER (100K)字节数,这其中也包括最后一行含有的终止符。
重要的是要注意,在发起请求后,将为收到的所有响应的头调用回调函数,而不仅仅是最终响应。这包括在身份验证协商期间发生的所有响应。如果你只需要对最终响应的头进行操作,那么您将需要自己在回调中收集头信息,并使用HTTP状态行来划分响应边界。
对于HTTP传输,状态行和响应主体前面的空行都包含在报头中,并传递给此函数。
5 代码实实现
Makefile
LIBS=-L../../../buildroot/output/target/usr/lib -lpthread -lz -lcurl -lcrypto -lssl -lcjson
CC_FLAG=-Wall -I../../../buildroot/output/staging/usr/include
SRC=curl_test.c
CC=mips-linux-gnu-gcc
test : $(SRC)
$(CC) $< $(LIBS) $(CC_FLAG) -O2 -o $@
cp -v $@ ~/nfs
.PHONY : clean
clean :
rm -rf test
curl_test.c
#include
#include
#include
#include
#include
static void baidu_get_test(void);
static void print_version_info(void);
int main()
{
CURLcode code;
code = curl_global_init(CURL_GLOBAL_ALL);
if(CURLE_OK != code)
{
printf("curl init Err\n");
return -1;
}
/*
* 打印本本库信息
*/
print_version_info();
/*
* get 方法访问百度并获取数据
*/
baidu_get_test();
curl_global_cleanup();
return 0;
}
static size_t baidu_get_test_cb(void *buffer, size_t size, size_t nmemb, void *user_p)
{
FILE *fp = (FILE *)user_p;
size_t return_size = fwrite(buffer, size, nmemb, fp);
return return_size;
}
static size_t head_process_data(char *buffer, size_t size, size_t nitems, void *userdata)
{
char *head_line = userdata;
printf("nitems = %d\n", nitems);
strncpy(head_line, (const char *)buffer, nitems);
*(head_line + nitems) = 0;
printf("head info : %s", head_line);
return nitems;
}
static void baidu_get_test(void)
{
/*
* 获取一个easy句柄,后面通过这个句柄进行通信
*/
CURL *easy_handle = curl_easy_init();
if(!easy_handle)
{
printf("curl_easy_init Err \n");
curl_global_cleanup();
return ;
}
/*
* 打开一个data.html文件,用于存储百度返回的数据
*/
FILE *fp = fopen("data.html", "w+");
char *head_line = (char *)malloc(100*1024);
/*
* 设置libcurl的选项,没有指定http访问方法,libcurl默认使用get方法
*/
curl_easy_setopt(easy_handle, CURLOPT_URL, "http://www.baidu.com");
curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, baidu_get_test_cb);
curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, fp);
/*
* 增加调试功能,可以查看libcurl的执行过程
*/
curl_easy_setopt(easy_handle, CURLOPT_VERBOSE, 1);
/*
* 接收http头信息
*/
curl_easy_setopt(easy_handle, CURLOPT_HEADERFUNCTION, head_process_data);
curl_easy_setopt(easy_handle, CURLOPT_HEADERDATA, head_line);
/*
* 执行访问
*/
CURLcode code = curl_easy_perform(easy_handle);
if(CURLE_OK != code)
{
printf("curl_easy_perform %d\n", code);
}
free(head_line);
fclose(fp);
/*
* 最后记得释放句柄
*/
curl_easy_cleanup(easy_handle);
}
static void print_version_info(void)
{
curl_version_info_data *version_info = curl_version_info(CURLVERSION_NOW);
printf("version:%s\n", version_info->version);
printf("host:%s\n", version_info->host);
printf("ssl_version:%s\n", version_info->ssl_version);
}
1 摘要
1.1 平台
运行平台:君正x2000
libcurl版本:7.68.0,通过buildroot编译生成。
编译器:mips-linux-gnu-gcc (gcc version 7.2.0 (Ingenic r4.0.0-gcc720 2018.02-28))
编译主机:ubuntu 18.04
1.2 功能
http通信(1)
1.1最少的代码实现http访问百度,这个示例将通过get方法访问百度,并将百度返回的结果存入data.html文件中。
http通信(2)
2.1 增加获取libcurl库信息
2.2 增加打印打印http响应head数据
2.3 增加使能版本库调试功能
http通信(3)
3.1 在http请求中增加头部的字段
2 选项名:CURLOPT_HTTPHEADER
设置自定义的HTTP头信息
2.1 原型
#include
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_HTTPHEADER, struct curl_slist *headers);
2.2 描述
传入一个指向链表的指针,这个链表包含了http通信的头部字段。
链表应该是一个填充了 struct curl_slist结构的全部有效的链表。使用curl_slist_append创建一个链表,使用释放是一个链表curl_slist_free_all 。如果链表中添加了一个头部字段,而这个头部字段在libcurl内部使用了,那么头部字段的内容默认会使用你设置的内容。如果你添加了一个没有内容的头部,比如’Accept:'(冒号右边没有数据),那么内部使用的这个头部字段将被禁止。这个选项CURLOPT_HTTPHEADER可以用于添加新的头部字段或者替代一个内部头部字段或者删除一个内部头部字段。如果要添加一个没有内容的字段(冒号的右边没有值),那么使用分号结尾的头部字段(muhead;) 。
链表内的每一个头部必须不要添加CRLF(“\r\n”) ,因为libcurl会自动的为每一个头部字段添加CRLF 。如果不遵守这个规则,将会导致奇怪的bug,因为服务器很可能会忽略你指定的一部分头部字段。
请求的第一行(包含方法,通常是GET或POST)不是头部字段,不能使用此选项替换。只有请求行后面的行才是头部字段。如果想改变方法行(第一行)可以使用CURLOPT_CUSTOMREQUEST选项。
传给curl_easy_setopt的这个链表,libcurl并不会拷贝,所以在请求执行完成之前必须保持这个链表不被释放,请求执行完以后,不再使用这个链表了,可以通过curl_slist_free_all释放。
将NULL传递给该选项,可以将其重置为无自定义头。
2.3 示例代码
CURL *curl = curl_easy_init();
struct curl_slist *list = NULL;
if(curl)
{
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
list = curl_slist_append(list, "Shoesize: 10");
list = curl_slist_append(list, "Accept:");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_perform(curl);
curl_slist_free_all(list); /* free the list again */
}
3 功能代码
默认
GET / HTTP/1.1
Host: www.baidu.com
Accept: */*
修改后
GET / HTTP/1.1
name: huohongpeng
Content-Type:
Accept: application/json
Makefile
LIBS=-L../../../buildroot/output/target/usr/lib -lpthread -lz -lcurl -lcrypto -lssl -lcjson
CC_FLAG=-Wall -I../../../buildroot/output/staging/usr/include
SRC=curl_test.c
CC=mips-linux-gnu-gcc
test : $(SRC)
$(CC) $< $(LIBS) $(CC_FLAG) -O2 -o $@
cp -v $@ ~/nfs
.PHONY : clean
clean :
rm -rf test
curl_test.c
#include
#include
#include
#include
#include
static void baidu_get_test(void);
static void print_version_info(void);
int main()
{
CURLcode code;
code = curl_global_init(CURL_GLOBAL_ALL);
if(CURLE_OK != code)
{
printf("curl init Err\n");
return -1;
}
/*
* 打印本本库信息
*/
print_version_info();
/*
* get 方法访问百度并获取数据
*/
baidu_get_test();
curl_global_cleanup();
return 0;
}
static size_t baidu_get_test_cb(void *buffer, size_t size, size_t nmemb, void *user_p)
{
FILE *fp = (FILE *)user_p;
size_t return_size = fwrite(buffer, size, nmemb, fp);
return return_size;
}
static size_t head_process_data(char *buffer, size_t size, size_t nitems, void *userdata)
{
char *head_line = userdata;
printf("nitems = %d\n", nitems);
strncpy(head_line, (const char *)buffer, nitems);
*(head_line + nitems) = 0;
printf("head info : %s", head_line);
return nitems;
}
static void baidu_get_test(void)
{
/*
* 获取一个easy句柄,后面通过这个句柄进行通信
*/
CURL *easy_handle = curl_easy_init();
if(!easy_handle) {
printf("curl_easy_init Err \n");
curl_global_cleanup();
return ;
}
/*
* 打开一个data.html文件,用于存储百度返回的数据
*/
FILE *fp = fopen("data.html", "w+");
char *head_line = (char *)malloc(100*1024);
/*
* 设置libcurl的选项,没有指定http访问方法,libcurl默认使用get方法
*/
curl_easy_setopt(easy_handle, CURLOPT_URL, "http://www.baidu.com");
curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, baidu_get_test_cb);
curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, fp);
/*
* 增加调试功能,可以查看libcurl的执行过程
*/
curl_easy_setopt(easy_handle, CURLOPT_VERBOSE, 1);
/*
* 接收http头信息
*/
curl_easy_setopt(easy_handle, CURLOPT_HEADERFUNCTION, head_process_data);
curl_easy_setopt(easy_handle, CURLOPT_HEADERDATA, head_line);
struct curl_slist *plist;
plist = curl_slist_append(0, "name: huohongpeng");
/*
* 删除Host字段
*/
curl_slist_append(plist, "Host:");
/*
* 添加没有内容的字段
*/
curl_slist_append(plist, "Content-Type;");
/*
* 修改Accept: application/json
*/
curl_slist_append(plist, "Accept: application/json");
curl_easy_setopt(easy_handle, CURLOPT_HTTPHEADER, plist);
/*
* 执行访问
*/
CURLcode code = curl_easy_perform(easy_handle);
if(CURLE_OK != code)
{
printf("curl_easy_perform %d\n", code);
}
free(head_line);
fclose(fp);
curl_slist_free_all(plist);
/*
* 最后记得释放句柄
*/
curl_easy_cleanup(easy_handle);
}
static void print_version_info(void)
{
curl_version_info_data *version_info = curl_version_info(CURLVERSION_NOW);
printf("version:%s\n", version_info->version);
printf("host:%s\n", version_info->host);
printf("ssl_version:%s\n", version_info->ssl_version);
}
1 摘要
1.1 平台
运行平台:君正x2000
libcurl版本:7.68.0,通过buildroot编译生成。
编译器:mips-linux-gnu-gcc (gcc version 7.2.0 (Ingenic r4.0.0-gcc720 2018.02-28))
编译主机:ubuntu 18.04
1.2 功能
http通信(1)
1.1最少的代码实现http访问百度,这个示例将通过get方法访问百度,并将百度返回的结果存入data.html文件中。
http通信(2)
2.1 增加获取libcurl库信息
2.2 增加打印打印http响应head数据
2.3 增加使能版本库调试功能
http通信(3)
3.1 在http请求中增加头部的字段
http通信(4)
4.1 获取libcurl执行过程信息
2 信息名称:CURLINFO_TOTAL_TIME
获取上一次的总传输时间
2.1 原型
#include
CURLcode curl_easy_getinfo(CURL *handle, CURLINFO_TOTAL_TIME, double *timep);
2.2 描述
传递一个指向double的指针来接收前一次传输的总时间(以秒为单位),包括名称解析、TCP连接等,double表示以秒为单位的时间。
当执行重定向时,每个请求的时间被加在一起。
2.3 示例代码
curl = curl_easy_init();
if(curl)
{
double total;
curl_easy_setopt(curl, CURLOPT_URL, url);
res = curl_easy_perform(curl);
if(CURLE_OK == res)
{
res = curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &total);
if(CURLE_OK == res)
{
printf("Time: %.1f", total);
}
}
/* always cleanup */
curl_easy_cleanup(curl);
}
3 信息名称:CURLINFO_CONNECT_TIME
获取连接过程的时间
3.1 原型
#include
CURLcode curl_easy_getinfo(CURL *handle, CURLINFO_CONNECT_TIME, double *timep);
3.2 描述
传递一个指向double的指针来接收从开始到远程主机(或代理)连接完成的总时间(以秒为单位)。
当执行重定向时,每个请求的时间被加在一起。
3.3 示例代码
curl = curl_easy_init();
if(curl)
{
double connect;
curl_easy_setopt(curl, CURLOPT_URL, url);
res = curl_easy_perform(curl);
if(CURLE_OK == res)
{
res = curl_easy_getinfo(curl, CURLINFO_CONNECT_TIME, &connect);
if(CURLE_OK == res)
{
printf("Time: %.1f", connect);
}
}
/* always cleanup */
curl_easy_cleanup(curl);
}
4 信息名称:CURLINFO_SIZE_DOWNLOAD
获取下载的字节数。
4.1 原型
#include
CURLcode curl_easy_getinfo(CURL *handle, CURLINFO_SIZE_DOWNLOAD, double *dlp);
4.2 描述
传递一个指向double的指针来接收下载的字节总数。这个数据只针对最后一次传输,每一次传输都会重新计算这个数。这是计算实际有效载荷数据,通常也被称为body。所有元数据和头部数据将不被计算在内。
4.3 示例
CURL *curl = curl_easy_init();
if(curl)
{
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
/* Perform the request */
res = curl_easy_perform(curl);
if(!res)
{
/* check the size */
double dl;
res = curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &dl);
if(!res)
{
printf("Downloaded %.0f bytes\n", cl);
}
}
5 代码实现
Makefile
LIBS=-L../../../buildroot/output/target/usr/lib -lpthread -lz -lcurl -lcrypto -lssl -lcjson
CC_FLAG=-Wall -I../../../buildroot/output/staging/usr/include
SRC=curl_test.c
CC=mips-linux-gnu-gcc
test : $(SRC)
$(CC) $< $(LIBS) $(CC_FLAG) -O2 -o $@
cp -v $@ ~/nfs
.PHONY : clean
clean :
rm -rf test
curl_test.c
#include
#include
#include
#include
#include
static void baidu_get_test(void);
static void print_version_info(void);
int main()
{
CURLcode code;
code = curl_global_init(CURL_GLOBAL_ALL);
if(CURLE_OK != code)
{
printf("curl init Err\n");
return -1;
}
/*
* 打印本本库信息
*/
print_version_info();
/*
* get 方法访问百度并获取数据
*/
baidu_get_test();
curl_global_cleanup();
return 0;
}
static size_t baidu_get_test_cb(void *buffer, size_t size, size_t nmemb, void *user_p)
{
FILE *fp = (FILE *)user_p;
size_t return_size = fwrite(buffer, size, nmemb, fp);
return return_size;
}
static size_t head_process_data(char *buffer, size_t size, size_t nitems, void *userdata)
{
char *head_line = userdata;
printf("nitems = %d\n", nitems);
strncpy(head_line, (const char *)buffer, nitems);
*(head_line + nitems) = 0;
printf("head info : %s", head_line);
return nitems;
}
static void baidu_get_test(void)
{
/*
* 获取一个easy句柄,后面通过这个句柄进行通信
*/
CURL *easy_handle = curl_easy_init();
if(!easy_handle)
{
printf("curl_easy_init Err \n");
curl_global_cleanup();
return ;
}
/*
* 打开一个data.html文件,用于存储百度返回的数据
*/
FILE *fp = fopen("data.html", "w+");
char *head_line = (char *)malloc(100*1024);
/*
* 设置libcurl的选项,没有指定http访问方法,libcurl默认使用get方法
*/
curl_easy_setopt(easy_handle, CURLOPT_URL, "http://www.baidu.com");
curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, baidu_get_test_cb);
curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, fp);
/*
* 增加调试功能,可以查看libcurl的执行过程
*/
curl_easy_setopt(easy_handle, CURLOPT_VERBOSE, 1);
/*
* 接收http头信息
*/
curl_easy_setopt(easy_handle, CURLOPT_HEADERFUNCTION, head_process_data);
curl_easy_setopt(easy_handle, CURLOPT_HEADERDATA, head_line);
struct curl_slist *plist;
plist = curl_slist_append(0, "name: huohongpeng");
/*
* 删除Host字段
*/
curl_slist_append(plist, "Host:");
/*
* 添加没有内容的字段
*/
curl_slist_append(plist, "Content-Type;");
/*
* 修改Accept: application/json
*/
curl_slist_append(plist, "Accept: application/json");
curl_easy_setopt(easy_handle, CURLOPT_HTTPHEADER, plist);
/*
* 执行访问
*/
CURLcode code = curl_easy_perform(easy_handle);
if(CURLE_OK != code)
{
printf("curl_easy_perform %d\n", code);
}
/*
* 获取信息
*/
double tmp = 0;
code = curl_easy_getinfo(easy_handle, CURLINFO_TOTAL_TIME, &tmp);
printf("CURLINFO_TOTAL_TIME = %lf\n", tmp);
curl_easy_getinfo(easy_handle, CURLINFO_CONNECT_TIME, &tmp);
printf("CURLINFO_CONNECT_TIME = %lf\n", tmp);
curl_easy_getinfo(easy_handle, CURLINFO_SIZE_DOWNLOAD, &tmp);
printf("CURLINFO_SIZE_DOWNLOAD = %d\n", (int)tmp);
free(head_line);
fclose(fp);
curl_slist_free_all(plist);
/*
* 最后记得释放句柄
*/
curl_easy_cleanup(easy_handle);
}
static void print_version_info(void)
{
curl_version_info_data *version_info = curl_version_info(CURLVERSION_NOW);
printf("version:%s\n", version_info->version);
printf("host:%s\n", version_info->host);
printf("ssl_version:%s\n", version_info->ssl_version);
}