第三部分 网络编程 (8 Linux下实现C语言的HTTP请求实现)

2019-07-13 07:12发布

Linux下的http请求有许多种方式,其中curl库是C语言封装的一个强大的库,使用curl比封装socket更加方便。

8.1 Curl使用

Curl是一款著名的字符界面下的下载工具,支持HTTP、HTTPS、FTP、FTPS、DICT、TELNET、LDAP、FILE,和GOPHER。此外还具有cookies支持、断点续传、FTP上传、密码支持、SSL支持和代理支持等特性。curl同时还提供了一套libcurl的库,开发者可以基于这个库开发其他下载工具。

8.1.1 Curl安装

8.1.1.1 Ubuntu 上安装

Curl 的官网下载地址:http://curl.haxx.se/download/
截止2018.4月更新至7.59.0版本。
1.下载并解压
下载到的压缩包为curl-7.59.0.tar.gz使用命令: $wget http://curl.haxx.se/download/curl-7.59.0.tar.gz 解压: $tar -zxvf curl-7.59.0.tar.gz 2.进入解压出的目录curl-7.59.0. $cd curl-7.59.0 3.配置参数 sudo ./configure 4.编译 sudo make sudo make install 5.检查安装
使用 curl –version 检查是否更新成功,出现如下情况安装基本成功. $curl --version 这里写图片描述
出现上图所示信息表示安装成功。

8.1.1.2 ARM板上安装

1.下载并解压
下载到的压缩包为curl-7.59.0.tar.gz使用命令: $wget http://curl.haxx.se/download/curl-7.59.0.tar.gz 解压: $tar -zxvf curl-7.59.0.tar.gz 2.进入解压出的目录curl-7.59.0. $cd curl-7.59.0 3.建立编译夹 $mkdir install 4.配置参数 ./configure --host=arm-fsl-linux-gnueabi ­­prefix=`pwd`/install CC=arm-fsl-linux-gnueabi-gcc CXX=arm-fsl-linux-gnueabi-g++ 【注意】
–­­host=arm-fsl-linux-gnueabi表示该软件编译完成后在arm平台上运行
–­­prefix后面为软件安装目录
5.编译 $make $make install 6.查看 $ls install/ 这里写图片描述
libcurl头文件在install/include/curl目录 # ls install/include/curl 这里写图片描述
交叉编译后的动态库文件在lib目录 $ls install/lib 这里写图片描述
7.移植
将lib文件下的移植到开发板的/usr/lib。 8.测试
Test.c #include #include #include const char data[]="this is what we post to the silly web server"; struct WriteThis { const char *readptr; long sizeleft; }; static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp) { struct WriteThis *pooh = (struct WriteThis *)userp; if(size*nmemb < 1) return 0; if(pooh->sizeleft) { *(char *)ptr = pooh->readptr[0]; /* copy one single byte */ pooh->readptr++; /* advance pointer */ pooh->sizeleft--; /* less data left */ return 1; /* we return 1 byte at a time! */ } return 0; /* no more data left to deliver */ } int main(void) { CURL *curl; CURLcode res; struct WriteThis pooh; pooh.readptr = data; pooh.sizeleft = (long)strlen(data); /* In windows, this will init the winsock stuff */ res = curl_global_init(CURL_GLOBAL_DEFAULT); /* Check for errors */ if(res != CURLE_OK) { fprintf(stderr, "curl_global_init() failed: %s ", curl_easy_strerror(res)); return 1; } /* get a curl handle */ curl = curl_easy_init(); if(curl) { /* First set the URL that is about to receive our POST. */ curl_easy_setopt(curl, CURLOPT_URL, "http://47.106.72.113/"); /* Now specify we want to POST data */ curl_easy_setopt(curl, CURLOPT_POST, 1L); /* we want to use our own read function */ curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback); /* pointer to pass to our read function */ curl_easy_setopt(curl, CURLOPT_READDATA, &pooh); /* get verbose debug output please */ curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* If you use POST to a HTTP 1.1 server, you can send data without knowing the size before starting the POST if you use chunked encoding. You enable this by adding a header like "Transfer-Encoding: chunked" with CURLOPT_HTTPHEADER. With HTTP 1.0 or without chunked transfer, you must specify the size in the request. */ #ifdef USE_CHUNKED { struct curl_slist *chunk = NULL; chunk = curl_slist_append(chunk, "Transfer-Encoding: chunked"); res = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); /* use curl_slist_free_all() after the *perform() call to free this list again */ } #else /* Set the expected POST size. If you want to POST large amounts of data, consider CURLOPT_POSTFIELDSIZE_LARGE */ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, pooh.sizeleft); #endif #ifdef DISABLE_EXPECT /* Using POST with HTTP 1.1 implies the use of a "Expect: 100-continue" header. You can disable this header with CURLOPT_HTTPHEADER as usual. NOTE: if you want chunked transfer too, you need to combine these two since you can only set one list of headers with CURLOPT_HTTPHEADER. */ /* A less good option would be to enforce HTTP 1.0, but that might also have other implications. */ { struct curl_slist *chunk = NULL; chunk = curl_slist_append(chunk, "Expect:"); res = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); /* use curl_slist_free_all() after the *perform() call to free this list again */ } #endif /* Perform the request, res will get the return code */ res = curl_easy_perform(curl); /* Check for errors */ if(res != CURLE_OK) fprintf(stderr, "curl_easy_perform() failed: %s ", curl_easy_strerror(res)); /* always cleanup */ curl_easy_cleanup(curl); } curl_global_cleanup(); return 0; } 执行命令: $arm-fsl-linux-gnueabi-gcc -I/home/farsight/curl-7.59.0/install/include -L/home/farsight/curl-7.59.0/install/lib -o test test.c -lcurl 将可执行文件test拷贝到开发板,执行程序。可以见到如下信息。 * Trying 47.106.72.113... * TCP_NODELAY set * Connected to 47.106.72.113 (47.106.72.113) port 80 (#0) > POST / HTTP/1.1 Host: 47.106.72.113 Accept: */* Content-Length: 44 Content-Type: application/x-www-form-urlencoded * We are completely uploaded and fine < HTTP/1.1 405 Not Allowed < Server: nginx/1.8.1 < Date: Mon, 25 Jun 2018 05:45:38 GMT < Content-Type: text/html < Content-Length: 172 < Connection: keep-alive < <html> <head><title>405 Not Allowedtitle>head> <body bgcolor="white"> <center><h1>405 Not Allowedh1>center> <hr><center>nginx/1.8.1center> body> html> * Connection #0 to host 47.106.72.113 left intact libcurl测试实例集:https://curl.haxx.se/libcurl/c/example.html
关于libcurl实例参看以上网址。

8.1.2 Curl使用步骤

1.全局初始化 CURLcode curl_global_init(long flags); flags: CURL_GLOBAL_ALL //初始化所有内部的模块 CURL_GLOBAL_SSL //初始化支持安全套接字 CURL_GLOBAL_WIN32 //初始化win32套接字库 CURL_GLOBAL_NOTHING //不初始化任何额外模块 CURL_GLOBAL_DEFAULT //与CURL_GLOBAL_ALL相同 该函数必须再所有其他libcurl函数调用前被调用,从而构建整个libcurl函数运行所需的环境,多次调用是幂等的。flags参数能够使用”或”操作符进行多个选项的拼接。一般情况使用CURL_GLOBAL_ALL是最好的选择。
该函数不是线程安全的,不能在程序的其他线程中调用,只能应用程序开始时,进行全局初始化调用。虽然不调用这个函数,使用curl_easy_init也会自行调用该函数,但在多线程处理时,可能出现多次调用的情况,应避免。
2.创建当次请求句柄
每次请求都需要创建一个句柄,所有操作围绕以此句柄进行: CURL *curl_handler = curl_easy_init(); 3.设置属性
libcurl针对所有协议,统一使用一个简单的函数curl_easy_setopt进行设置,这大大简化了使用,不过需要参考不同协议设置不同的属性:
https://curl.haxx.se/libcurl/c/curl_easy_setopt.html
这里以http发送GET请求为例设置如下: //设置请求的url curl_easy_setopt(curl_handler, CURLOPT_URL, url); //设置是否返回请求头 curl_easy_setopt(curl_handler, CURLOPT_HEADER, 1L); //设置屏蔽其他信号 curl_easy_setopt(curl_handler, CURLOPT_NOSIGNAL, 1L); //设置下载数据回调函数 curl_easy_setopt(curl_handler, CURLOPT_WRITEFUNCTION, write_func); curl_easy_setopt(curl_handler, CURLOPT_WRITEDATA, write_data); 回调函数原型:
size_t function( void *ptr, size_t size, size_t nmemb, void *userp);
函数将在libcurl接收到数据后被调用。
void *ptr是下载回来的数据.
void *userp是用户指针, 用户通过这个指针传输自己的数据.
CURLOPT_WRITEDATA:设置回调函数中的void *userp指针的来源。 //设置是否使用下载进度控制函数 curl_easy_setopt(curl_handler, CURLOPT_NOPROGRESS, 0L); curl_easy_setopt(curl_handler, CURLOPT_PROGRESSFUNCTION, prog_func); curl_easy_setopt(curl_handler, CURLOPT_PROGRESSDATA, pdata); 下载进度回调函数与下载数据的回调函数原型相同,data也相同。 //设置请求头 struct curl_list *header_list = NULL; for (int i = 0; i < headers.size(); ++i) { header_list = curl_slist_append(header_list, headers[i].c_str()); } curl_easy_setopt(curl_handler, CURLOPT_HTTPHEADER, header_list); curl_slist_free_all(header_list); //其他选项
CURLOPT_HEADERFUNCTION
CURLOPT_HEADERDATA
只取HTTP头部数据, 处理与下载数据回调的处理相同.
CURLOPT_TIMEOUT
超时时间.
CURLOPT_CONNECTIONTIMEOUT
连接等待时间.
CURLOPT_FOLLOWLOCATION
   设置支持302重定向
   CURLOPT_RANGE
  断点续传, 指定传输分片, 格式:”0-200”
其中,针对http协议,不同请求有特定的属性需要设置,下面依次列举:
- HEAD:需要设置CURLOPT_NOBODY为true
- DELETE:需要设置CURLOPT_CUSTOMREQUEST为”DELETE”
- PUT:需要设置CURLOPT_UPLOAD为true,同时设置CURLOPT_READFUNCTION、CURLOPT_READDATA、CURLOPT_INFILESIZE_LARGE
- POST:需要设置CURLOPT_POST为true,设置CURLOPT_POSTFIELDS、CURLOPT_POSTFIELDSIZE 4.执行 CURLcode curl_easy_perform(CURL *handler); 该函数执行当次请求创建的句柄,返回值有非常详细的定义libcurl库返回状态码解释与速查。
5.销毁当次请求句柄 void curl_easy_cleanup(CURL *handler); 该函数销毁当次请求创建的句柄。
6.全局析构 void curl_global_cleanup(); 该函数销毁全局执行环境。
 其他接口
libcurl除了上述常用接口外,提供了其他接口可以进行更为方便和精确的控制。 CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ... ); //支持的CURLINFO类型见此:https://curl.haxx.se/libcurl/c/curl_easy_getinfo.html curl_version() returns a pointer to the libcurl version string curl_getdate() converts a date string to time_t curl_formadd() build multipart form-data posts curl_formfree() free a previously built form POST curl_slist_append() builds a linked list curl_slist_free_all() frees a whole curl_slist as made with curl_slist_append() curl_easy_escape() URL encodes a string curl_easy_unescape() URL decodes a string libcurl官方教程:https://curl.haxx.se/libcurl/c/libcurl-tutorial.html