国产ARM/RISCV与OpenHarmony物联网项目(四)网关通信服务
本文介绍国产物联网项目中的通信服务程序设计,其中重点是通信协议的设计、解析、封装处理功能实现。服务程序通过网络与节点上服务器进行通信,接收和发送消息,并使用共享内存与其他进程进行数据交互。
项目参考源码及可执行文件:https://gitee.com/www_91arm/phytiumpi_iot_ia1_sf1
一、通信协议设计
1、协议消息格式
消息采用固定长度的帧结构,具体如下:
|
字段名称 |
长度(字节) |
描述 |
|---|---|---|
|
帧头 |
2 |
固定值 |
|
消息类型 |
1 |
区分不同类型的消息,如数据上传、控制命令、参数设置等。 |
|
节点编号 |
1 |
标识消息的来源或目标节点, |
|
数据长度 |
1 |
指示消息体中数据的字节数。 |
|
消息体 |
可变 |
根据消息类型和节点编号包含不同的数据内容。 |
|
校验和 |
1 |
对帧头之后的所有字节进行异或运算得到的结果,用于数据校验。 |
2、协议消息类型定义
|
消息类型值 |
描述 |
|---|---|
|
|
节点向网关上传数据(节点 1:温度、湿度、光照度;节点 2:气体浓度) |
|
|
网关向节点发送控制命令(节点 1:灯光开关;节点 2:蜂鸣器开关) |
|
|
网关向节点设置参数(节点 1:灯光自动开关上限值;节点 2:气体自动开关上限值) |
3、协议命令说明
节点1数据查询命令:(网关-->节点)网关要求节点1返回传感器数据
|
帧头 |
消息类型 |
节点编号 |
数据长度 |
消息内容 |
校验和 |
|---|---|---|---|---|---|
|
0xAA 0x55 |
0x01 |
0x01 |
0x01 |
0xFF |
0x01 |
节点2数据查询命令:(网关-->节点)网关要求节点1返回传感器数据
|
帧头 |
消息类型 |
节点编号 |
数据长度 |
消息内容 |
校验和 |
|---|---|---|---|---|---|
|
0xAA 0x55 |
0x01 |
0x02 |
0x01 |
0xFF |
0x02 |
节点1设备控制命令:(网关-->节点)网关要求节点1打开控制设备
|
帧头 |
消息类型 |
节点编号 |
数据长度 |
消息内容 |
校验和 |
|---|---|---|---|---|---|
|
0xAA 0x55 |
0x02 |
0x01 |
0x01 |
0x01 |
0xFC |
节点1设备控制命令:(网关-->节点)网关要求节点1关闭控制设备
|
帧头 |
消息类型 |
节点编号 |
数据长度 |
消息内容 |
校验和 |
|---|---|---|---|---|---|
|
0xAA 0x55 |
0x02 |
0x01 |
0x01 |
0x00 |
0xFD |
节点1设备设置命令:(网关-->节点)网关设置节点1设备控制上限值
|
帧头 |
消息类型 |
节点编号 |
数据长度 |
消息内容 |
校验和 |
|---|---|---|---|---|---|
|
0xAA 0x55 |
0x03 |
0x01 |
0x02 |
0x00 0x41 |
0xBE |
节点1数据上传命令:(网关-->节点)网关设置节点1设备控制上限值
|
帧头 |
消息类型 |
节点编号 |
数据长度 |
消息内容 |
校验和 |
|---|---|---|---|---|---|
|
0xAA 0x55 |
0x01 |
0x01 |
0x04 |
0x20 0x3F 0x02 0x26 |
0xC0 |
节点2网络通信服务程序协议处理调试信息:
[send]
type:1, node_id:1
0xAA 0x55 0x01 0x01 0x01 0xFF 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x01 end...
[send]
type:2, node_id:1
0xAA 0x55 0x02 0x01 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xFC end...
[send]
type:2, node_id:1
0xAA 0x55 0x02 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xFD end...
[send]
type:3, node_id:1
0xAA 0x55 0x03 0x01 0x02 0x00 0x41 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xBE end...
[recv]
0xAA 0x55 0x01 0x01 0x04 0x20 0x3F 0x02 0x26 0x00 0x00 0x00 0x00 0x00 0x00 0xC0 end...
节点2网络通信服务程序协议处理调试信息:
[send]
type:1, node_id:2
0xAA 0x55 0x01 0x02 0x01 0xFF 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 end...
[send]
type:2, node_id:2
0xAA 0x55 0x02 0x02 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xFF end...
[send]
type:2, node_id:2
0xAA 0x55 0x02 0x02 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xFE end...
[send]
type:3, node_id:2
0xAA 0x55 0x03 0x02 0x02 0x00 0x80 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x7C end...
[recv]
0xAA 0x55 0x01 0x02 0x02 0x00 0x02 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xFC end...
二、程序分析与设计
1、程序流程分析与设计
程序通过网络与服务器进行通信,接收和发送消息,并使用共享内存与其他进程进行数据交互。它使用多线程来提高程序的并发性能,同时对网络连接和消息接收进行了错误处理,确保程序的稳定性。
程序启动:检查命令行参数,初始化随机数种子。
初始化共享内存:创建或获取共享内存段,并将其附加到当前进程的地址空间。
初始化全局数据:设置全局数据结构体
g_data的初始值,并将其写入共享内存。连接服务器:创建套接字,初始化服务器地址,尝试连接服务器,若失败则重试。
创建接收线程:启动一个线程用于接收节点消息。
主循环:
检查共享内存中的更新标志 ,若大于 0 则处理网页命令。
接收节点消息,若接收成功且消息解析通过,则将消息写入共享内存。
程序结束:关闭套接字。

2、程序数据结构设计
-
宏定义
#define PORT 8888
#define BUFFER_SIZE 16
#define MAX_CLIENTS 10
#define MAX_RETRIES 3
#define CMD_DATA 0x01 //数据命令
#define CMD_CTL 0x02 //控制命令
#define CMD_SET 0x03 //设置命令
#define E53_IA1 0x01 //节点1编号
#define E53_SF1 0x02 //节点2编号
定义了端口号、缓冲区大小、最大重试次数、命令类型和节点 ID 等常量。
-
结构体定义
// 共享内存数据结构
struct st_sys {
unsigned char temp_val; // 温度值
unsigned char humi_val; // 湿度值
unsigned char light_sw; // 灯光开关状态
unsigned char buzz_sw; // 蜂鸣器开关状态
unsigned int ill_val; // 光照强度值
unsigned int gas_val; // 气体浓度值
unsigned int ill_max; // 光照强度最大值
unsigned int gas_max; // 气体浓度最大值
unsigned char msg_type; //请求类型
unsigned char node_id; //节点ID
unsigned char data_flag;//数据更新标记
unsigned char control_flag;//控制更新标记
unsigned char set_flag;//设置更新标记
};
// 消息结构体
typedef struct {
unsigned char frame_header[2];
unsigned char msg_type;
unsigned char node_id;
unsigned char data_len;
unsigned char data[10];
unsigned char checksum;
} Message;
st_sys 结构体用于存储共享内存中的系统数据,Message 结构体用于封装和解析网络消息。
-
全局变量
//连接的socket文件
int g_net_fd;
//共享内存地址指针
struct st_sys* shm_dev;
struct st_sys g_data;
//接网络上传消息
Message g_response;
int recv_err_count = 0;
这些全局变量用于存储网络连接的文件描述符、共享内存地址、接收到的消息和接收错误计数。
3、 函数功能设计
|
序号 |
函数名称 |
函数功能 |
函数说明 |
|---|---|---|---|
|
1 |
|
设置共享内存 |
该函数用于创建或获取共享内存段,并将其附加到当前进程的地址空间。 |
|
2 |
|
信号处理函数 |
当接收到 |
|
3 |
|
初始化共享内存 |
该函数初始化随机数种子,调用 |
|
4 |
|
初始化全局数据 |
函数初始化全局数据结构体 |
|
5 |
|
网络数据写入共享内存 |
该函数根据接收到的消息更新全局数据结构体 |
|
6 |
|
带重试的连接函数 |
该函数尝试连接到服务器,如果连接失败,会进行最多 |
|
7 |
|
计算校验和 |
该函数计算消息的校验和,用于验证消息的完整性。 |
|
8 |
|
封装消息 |
该函数根据给定的消息类型、节点 ID、数据长度和数据内容,封装一个消息结构体。 |
|
9 |
|
调试输出消息 |
该函数用于打印消息的详细信息,方便调试。 |
|
10 |
|
解析消息 |
函数检查消息的帧头和校验和,如果都正确,则返回 0,表示解析成功。 |
|
11 |
|
发送命令消息 |
该函数封装一个命令消息,并将其发送到服务器。 |
|
12 |
|
连接服务器 |
该函数创建一个套接字,初始化服务器地址,并调用 |
|
13 |
|
接收错误处理 |
当接收消息出现错误或服务器断开连接时,该函数会初始化全局数据并将其写入共享内存。 |
|
14 |
|
接收节点消息 |
该函数从服务器接收消息,并将其存储到全局变量 |
|
15 |
|
网页命令处理 |
该函数根据共享内存中的消息类型,封装并发送不同类型的命令消息到服务器。 |
|
16 |
|
接收线程函数 |
该函数是一个线程函数,在一个无限循环中,每隔 1 秒尝试接收消息,解析消息并将其写入共享内存。 |
4、主函数流程设计
int main(int argc,char* argv[]) {
// ...
}
主函数的主要流程如下:
检查命令行参数,确保用户提供了服务器的 IP 地址。
初始化随机数种子。
初始化共享内存和全局数据,并将全局数据写入共享内存。
连接到服务器。
创建一个接收线程,用于在后台接收和处理消息。
在一个无限循环中,检查共享内存中的更新标记,如果标记为真,则调用
web_cmd_proc函数处理网页命令,并更新标记。每隔 2 秒尝试接收消息,解析消息并将其写入共享内存。
三、通信协议调用关系分析
用户浏览器打开数据显示界面
data_show.html,其中ajax函数会定时调用node_data.cgi程序。
node_data.cgi程序会修改共享内存的数据。
node_ser.c程序中的web_cmd_proc函数会根据共享内存的命令类型、更新标记,发送协议命令给指定节点。
data_show.html
$.ajax({
cache: false,
async: true,
dataType: 'json',
type: 'get',
url: "cgi-bin/node_data.cgi",
success: function (data) {
temp_val = data.temp;
humi_val = data.humi;
light_val = data.light;
gas_val = data.gas;
node_data.cgi程序会向共享内存更新协议命令类型、节点类型及更新标记数据。
node_data.cgi
// 从共享内存中读取数据
memcpy(g_dev, shm_dev, sizeof(struct st_sys));
shm_dev->msg_type = CMD_DATA;//命令类型
shm_dev->node_id = E53_IA1;//节点类型
shm_dev->update_flag++;//更新次数加1
node_ser.c程序中的web_cmd_proc函数会根据共享内存的命令类型、更新。
node_ser.c协议处理关键代码:
void web_cmd_proc(void)
{
int data_len = 0;
char web_data[8]={0};
DBG_PRINT("[Send]@@@msg_type:%d\n",shm_dev->msg_type);
switch (shm_dev->msg_type) {
case CMD_DATA: // 节点上传数据
data_len = 1;
web_data[0] = 0xff;
send_cmd_msg(g_net_fd,CMD_DATA,E53_IA1,web_data,data_len);//节点1上传数据
send_cmd_msg(g_net_fd,CMD_DATA,E53_SF1,web_data,data_len);//节点2上传数据
break;
case CMD_CTL: // 控制命令类型
data_len = 1;
if(shm_dev->node_id == E53_IA1){
web_data[0] = shm_dev->light_sw;
send_cmd_msg(g_net_fd,CMD_CTL,E53_IA1,web_data,data_len);
}
if(g_data.node_id == E53_SF1){
web_data[0] = shm_dev->buzz_sw;
send_cmd_msg(g_net_fd,CMD_CTL,E53_SF1,web_data,data_len);
}
break;
case CMD_SET: // 参数设置类型
更多推荐
所有评论(0)