微信公众号:二进制人生
专注于嵌入式linux开发。问题或建议,请发邮件至hjhvictory@163.com。
更新日期:2020/1/15,转载请注明出处。
最近比较忙,好多天没有更新了,多了好几个关注的用户。
持续收集轮子或者造轮子,意在写一个嵌入式linux通用库,包含常用的各种数据结构和接口。已完成如下:
循环队列
环形缓冲区
位图
跳跃表
哈希表
二值信号量
通用动态数组
http请求集成
短期计划:
内存分配器实现
多用户共享内存封装
单一读写用户共享内存封装
让网络编程更加简单--套接字封装
内核链表
FTP协议集成
ARP协议集成
ffmpeg库常用方法封装
内容目录
前言用libghttp实现http get请求用libghttp实现http post请求异步模式示例缺陷分析
前言
在嵌入式Linux里头,http是应用得比较多的网络协议之一。常常用于和平台软件对接,比如物联网设备的传感器信息上报,报警图片上传,接收的对象通常是云服务器或者是局域网内自己架设的服务器后台。
http客户端请求的实现,我自然不会傻傻的自己去写,因为肯定会有前人造过的轮子。于是上网搜索了下C语言实现的http开源库。
发现实现http请求的开源库很多,但用C实现的比较少。其中有一个叫libcurl,算是一个比较著名且好用的库。libcurl当前支持http, https, ftp, gopher, telnet, dict, file, 和ldap 协议。
对于只想实现简单的http get或者post请求的场景有点大才小用。我找到了一个简单易用、短小精悍、代码容易理解、C实现的http请求库,libghttp。阅读了它的代码后欣喜万分,如获至宝。
代码量少且容易理解的好处在于,出了bug我们可以自己fix,而且有不同需求时也使得我们有能力对其进行改造。
闲话少说,直接看官网:

1.0.9是它的最后一个版本,估计有十几年没有更新了。
编译和安装很简单:
./configure --prefix=xxx
make
make install
xxx表示库和头文件的安装路径,必须是绝对路径。
用libghttp实现http get请求
来写一个简单的get请求示例:
/*
* http get test
*/
#include <stdio.h>
#include <string.h>
#include "ghttp.h"
int main(int argc, char **argv)
{
char *uri = "http://192.168.1.100";//可以用windows自行搭建一个http服务器,填写百度的话可能会不通,因为不知道百度有没有一些反爬虫机制
ghttp_request *request = NULL;
ghttp_status status;
FILE * pFile;
char *buf;
int bytes_read;
int size;
pFile = fopen ( "index.html" , "wb" );
request = ghttp_request_new();//新建一个http请求
if(ghttp_set_uri(request, uri) == -1)//设置请求的url
return -1;
if(ghttp_set_type(request, ghttp_type_get) == -1)//设置请求类型为get
return -1;
ghttp_prepare(request);//请求前的一些准备工作,比如设置请求头
status = ghttp_process(request);//发起请求
if(status == ghttp_error)
return -1;
printf("Status code -> %d\n", ghttp_status_code(request));//打印http状态码
buf = ghttp_get_body(request);//获取http body
bytes_read = ghttp_get_body_len(request);
size = strlen(buf);//size == bytes_read
fwrite (buf , 1 ,size , pFile );
fclose(pFile);
return 0;
}
编译方法:gcc test_ghttp_get.c ./install/lib/libghttp.a -I./install/include -o httpget
./install是你安装libghttp库时指定的安装路径。
执行程序./httpget
用libghttp实现http post请求
再来写一个post请求的测试程序。
/*
* http post test
*/
#include <stdio.h>
#include <string.h>
#include "ghttp.h"
int main() {
char body[] = "helloword=me";
char *uri = "http://192.168.1.100";
ghttp_request *request = NULL;
ghttp_status status;
char *buf;
char retbuf[128];
int len;
request = ghttp_request_new();
if (ghttp_set_uri(request, uri) == -1)
return -1;
if (ghttp_set_type(request, ghttp_type_post) == -1) //post
return -1;
ghttp_set_header(request, http_hdr_Content_Type, "application/x-www-form-urlencoded");
//ghttp_set_sync(request, ghttp_sync); //set sync
len = strlen(body);
ghttp_set_body(request, body, len);//填充body
ghttp_prepare(request);
status = ghttp_process(request);
if (status == ghttp_error)
return -1;
buf = ghttp_get_body(request); //test
printf("buf=%s\n",buf);
ghttp_clean(request);
return 0;
}
异步模式示例
libghttp支持两种访问模式,分别是同步模式和异步模式。
同步和异步是指调用ghttp_process函数发起http请求时,是一次完成(会有while循环),还是需要多次调用来完成。不管是同步模式还是异步模式,libghttp都是分多步来完成一次http请求,分别是发送http请求行,发送http头,发送http body,接着是解析http响应头,解析http响应body。
libghttp通过维护一个状态机来实现http的分步访问,而且这是一个多层嵌套的状态机。
最外层的3种状态如下:
typedef enum ghttp_proc_tag {
ghttp_proc_none = 0,
ghttp_proc_request,
ghttp_proc_response_hdrs,
ghttp_proc_response
} ghttp_proc;
分别表示:发送请求-->解析响应头-->解析响应body。
每种大状态又细分成n步。
比如发送请求拆分成3步:
typedef enum http_req_state_tag {
http_req_state_start = 0,
http_req_state_sending_request,
http_req_state_sending_headers,
http_req_state_sending_body
} http_req_state;
通过这样步步拆解的思想使得代码清晰易懂,而异步访问的实现也是基于此。
温习下http协议的格式:


异步模式的情况下:
需要用户循环调用ghttp_process函数,直至其返回值为ghttp_done才表示一次http访问最终完成。
异步请求使用示例
#include <stdio.h>
#include <unistd.h>
#include "ghttp.h"
void bail(char *s)
{
fputs(s, stderr); fputc('\n', stderr);
exit(1);
}
void status(ghttp_request *r, char *desc)
{
ghttp_current_status st;
st = ghttp_get_status(r);
fprintf(stderr, "%s: %s [%d/%d]\n",
desc,
st.proc == ghttp_proc_request ? "request" :
st.proc == ghttp_proc_response_hdrs ? "response-headers" :
st.proc == ghttp_proc_response ? "response" : "none",
st.bytes_read, st.bytes_total);
}
int main(int argc, char **argv)
{
int bytes = 0;
ghttp_request *req;
ghttp_status req_status;
if (argc < 2)
bail("usage: simple-get URI");
req = ghttp_request_new();
if (ghttp_set_uri(req,argv[1]) < 0)
bail("ghttp_set_uri");
if (ghttp_prepare(req) < 0)
bail("ghttp_prepare");
if (ghttp_set_sync(req, ghttp_async) < 0)
bail("ghttp_set_sync");
do {
status(req, "conn0");
req_status = ghttp_process(req);
if (req_status == ghttp_error) {
fprintf(stderr, "ghttp err: %s\n",
ghttp_get_error(req));
return 2;
}
if (req_status != ghttp_error && ghttp_get_body_len(req) > 0) {
bytes += ghttp_get_body_len(req);
ghttp_flush_response_buffer(req);
}
} while (req_status == ghttp_not_done);
fprintf(stderr, "conn0 received %d bytes\n", bytes);
ghttp_clean(req);
return 0;
}
异步的好处除了调用ghttp_process之后可以快速返回之外,在返回的http body比较大时,可以分多次接收,不必一次分配巨大的内存。
缺陷分析
在实际做项目开发时,一个库我们不是随随便便就可以拿来用的,除了能实现功能之外,我们还要分析其是否稳定,是否高效,潜在的风险,当前的缺陷等等。
通过分析代码,总结了libghttp的几点不足:
1、libghttp的最大不足在于它不支持https,虽然可以自行改造,但自行改造的话我们还得投入时间去编码和测试。
2、调用ghttp_set_body
设置body数据时,body是由我们在外面定义的数据缓冲区,在一次http请求完成前,我们不能修改body的内容,否则可能会更改最后发送的数据。我们可以看下ghttp_set_body
的实现:
int
ghttp_set_body(ghttp_request *a_request, char *a_body, int a_len)
{
......
a_request->req->body = a_body;
a_request->req->body_len = a_len;
return 0;
}
可以看到并没有为a_request->req->body
分配内存空间和拷贝数据,仅仅是将外部缓冲区的地址赋值给a_request->req->body
。
3、ghttp_set_body
一定要在ghttp_prepare
之前调用,否则会导致body数据发送出错,甚至奔溃,原因是因为ghttp_prepare
会更新http头里的Content-Length
字段的值,如果调用完ghttp_prepare
再调用ghttp_set_body
会导致http发送的Content-Length
字段值和实际不符。
基于此对ghttp_set_body
函数进行了修改,在里面更新下Content-Length
字段的值。
4、发起一个http请求,至少需要5K的内存分配,5K里不包括那些基本的数据结构的内存占用。
5、libghttp为了保存从套接字读取的数据或者向套接字写数据而分配了1K的缓冲区,发起一次http请求缓冲区存在频繁的分配和释放。
网络上关于libghttp介绍少之又少,如果有时间,我会推出关于libghttp源码分析的文章或者视频。
愿你有所收获…
图 二进制人生公众号





