国产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)