近期应项目要求,需要在嵌入式设备上实现将数据上传至服务器,查找了许多资料,现在将整个过程所需要的知识整理下来,以备后续查找。
硬件环境
下位机:基于Linux的嵌入式开发硬件
上位机:web服务器(PC端)
HTTP协议简介
HTTP即Hyper Text Transfer Protocol (超文本传输协议),是一种基于TCP/IP通信协议来传递数据 (HTML 文件,图片文件,查询结果等)。
HTTP协议的几种请求方法
在HTTP1.1中,总共有8种请求方法(也叫动作),用来表明对Request-URL指定的资源的不同操作方式。HTTP协议的几种请求方法如下表所示:
HTTP服务器至少应该实现GET和HEAD/POST方法,其他方法都是可选的,此外除上述方法,特定的HTTP服务器支持扩展自定义的方法。大部分时候,需要使用的只有GET和POST。针对项目设计的C语言代码主要实现的功能即为上述方法的POST功能。
四种常见的 POST 提交数据方式
在使用post,put,delete,options等方法的时候,他们都有方法体body,用来存储数据。而body的类型可以有:form-data、x-www-form-urlencoded、raw、binary
form-data
就是http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。当上传的字段是文件时,会有Content-Type来表名文件类型;content-disposition,用来说明字段的一些信息;由于有boundary隔离,所以multipart/form-data既可以上传文件,也可以上传键值对,它采用了键值对的方式,所以可以上传多个文件。
x-www-form-urlencoded
这是最常见的POST提交数据的方式了。原生的form表单,如果不设置enctype属性,最终就是以application/x-www-form-urlencoded方式提交数据。会将表单内的数据转换为键值对,比如,name=java&age = 23。
raw
可以上传任意格式的文本,可以上传text、json、xml、html等。
binary
相当于Content-Type:application/octet-stream,从字面意思得知,只可以上传二进制数据,通常用来上传文件,由于没有键值,所以,一次只能上传一个文件。
而本次由于项目需要,使用的是json方式传递数据。
实现思路
由于HTTP协议是基于TCP通信的,故可以有如下多种方式实现HTTP传输协议:
1)使用DTU
2)使用AT命令
3)第三方开源库
4)使用socket通信
本次使用第三方库curl来实现,curl是一个功能强大的网络工具,它能够通过http、ftp等方式下载文件,也能够上传文件。这类库语句简单,能够省去许多开发的时间,提高开发效率。
实现过程
安装wireshark抓包软件
wireshark用于调试http协议和分析http原始数据。
wireshark软件的下载链接如下:
http://soft.wuseng.net/11/3438.html?tab=5752682
确定抓包过程中,上位机或下位机的IP地址
下位机地址:
按win+R打开系统运行界面,输入cmd打开系统终端,在终端中输入指令ipconfig查看本机IP地址:
上位机地址:
由于本次项目使用已知的服务器,所以此处的IP地址是已知的。若需要向一个网站上传数据,也可以通过终端ping+网站域名的方式,得到网站的IP地址。如CSDN的IP地址为101.200.35.175。
设置过滤条件
上一步中已经知道了上位机和下位机的IP地址,即可在wireshark的filter一栏中输入以下过滤条件:
http and ip.addr == 192.168.1.110 and tcp.port == 80
http:指定网络协议
ip.addr == 192.168.1.110:指定服务器ip地址,请根据实际情况替换。
tcp.port == 8080,指定端口号,请根据实际情况替换。
安装JSON包和CURL工具
json安装包链接:https://pan.baidu.com/s/1BDc2JldxTEmmcxwdqesvTw
提取码:54jg
sudo tar -xvf tar xvf json-c-0.9.tar.gz
cd xvf json-c-0.9.tar.gz
./configure
make
sudo make install
安装curl
sudo apt install curl
sudo apt install libcurl4-openssl-dev
编写HTTP传输代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#include "/usr/local/include/json/json.h"
#define POSTURL "xxx.xxx.xxx.xxx" //需要接收消息的IP地址
int main(int argc,char *argv[])
{
//1、创建一个json数组对象,就可以理解为外面那个大容器[]
struct json_object *arr=json_object_new_array();
//2、创建两个字符串对象,可以以理解为[]中的两个小容器{}
struct json_object *str1=json_object_new_object();
//struct json_object *str2=json_object_new_object();
//3、把要存放的数据转为对象
struct json_object *value1=json_object_new_int(1);
struct json_object *value2=json_object_new_int(1);
struct json_object *value3=json_object_new_int(1);
struct json_object *value4=json_object_new_int(1);
struct json_object *value5=json_object_new_double(118.846597);
struct json_object *value6=json_object_new_double(32.027024);
struct json_object *value7=json_object_new_double(12.18);
//4、把数值对象添加到字符串对象中
json_object_object_add(str1,"carId", value1);
json_object_object_add(str1,"carType", value2);
json_object_object_add(str1,"carStatus", value3);
json_object_object_add(str1,"driveStatus", value4);
json_object_object_add(str1,"longitude", value5);
json_object_object_add(str1,"latitude", value6);
json_object_object_add(str1,"carSpeed", value7);
//5、把数组对象转为字符流进行发送
const char *temp=json_object_to_json_string(str1);
CURL *curl;
CURLcode res;
struct curl_slist *http_header = NULL;
curl = curl_easy_init();
if (!curl)
{
fprintf(stderr,"curl init failed\n");
return -1;
}
curl_easy_setopt(curl,CURLOPT_URL,POSTURL); //url地址
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); //不检查ssl,可访问https
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); //不检查ssl,可访问https
curl_easy_setopt(curl,CURLOPT_POSTFIELDS,temp); //post参数
curl_easy_setopt(curl,CURLOPT_POST,1); //设置问非0表示本次操作为post
curl_easy_setopt(curl,CURLOPT_VERBOSE,1); //打印调试信息
http_header = curl_slist_append(NULL, "Content-Type:application/json;charset=UTF-8");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, http_header);
curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,1); //设置为非0,响应头信息location
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10);//接收数据时超时设置,如果10秒内数据未
res = curl_easy_perform(curl);
if (res != CURLE_OK)
{
switch(res)
{
case CURLE_UNSUPPORTED_PROTOCOL:
fprintf(stderr,"不支持的协议,由URL的头部指定\n");
case CURLE_COULDNT_CONNECT:
fprintf(stderr,"不能连接到remote主机或者代理\n");
case CURLE_HTTP_RETURNED_ERROR:
fprintf(stderr,"http返回错误\n");
case CURLE_READ_ERROR:
fprintf(stderr,"读本地文件错误\n");
default:
fprintf(stderr,"返回值:%d\n",res);
}
return -1;
}
curl_easy_cleanup(curl);
printf("\n");
return 0;
}
编译:
gcc http.c -o http -ljson -lcurl
接收消息测试与分析
输入过滤条件,点击apply,并运行以上代码,可以抓取到两个数据包:
分别是下位机发送的HTTP请求和上位机发回的HTTP响应。Source一栏表示发送方的IP地址,Destination一栏表示接收方也就是服务器的IP地址,Protocol表示遵循的协议,Length表示数据长度,Info表示数据内容。
在任意数据包上右键点击“追踪流”、“HTTP流”,可以看到发送与接收到的消息。
至此,HTTP发送测试完成。
参考博客
https://blog.csdn.net/songfeihu0810232/article/details/54892149
https://blog.csdn.net/weixin_42399752/article/details/94744785
https://blog.csdn.net/xukai871105/article/details/31008635