项目参考源码及可执行文件:https://gitee.com/www_91arm/phytiumpi_iot_ia1_sf1

一、通信协议设计

1、协议消息格式

消息采用固定长度的帧结构,具体如下:

字段名称

长度(字节)

描述

帧头

2

固定值 0xAA 0x55,用于标识消息的起始。

消息类型

1

区分不同类型的消息,如数据上传、控制命令、参数设置等。

节点编号

1

标识消息的来源或目标节点,0x01 表示节点 1,0x02 表示节点 2。

数据长度

1

指示消息体中数据的字节数。

消息体

可变

根据消息类型和节点编号包含不同的数据内容。

校验和

1

对帧头之后的所有字节进行异或运算得到的结果,用于数据校验。

2、协议消息类型定义

消息类型值

描述

0x01

节点向网关上传数据(节点 1:温度、湿度、光照度;节点 2:气体浓度)

0x02

网关向节点发送控制命令(节点 1:灯光开关;节点 2:蜂鸣器开关)

0x03

网关向节点设置参数(节点 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、程序流程分析与设计

程序通过网络与服务器进行通信,接收和发送消息,并使用共享内存与其他进程进行数据交互。它使用多线程来提高程序的并发性能,同时对网络连接和消息接收进行了错误处理,确保程序的稳定性。

  1. 程序启动:检查命令行参数,初始化随机数种子。

  2. 初始化共享内存:创建或获取共享内存段,并将其附加到当前进程的地址空间。

  3. 初始化全局数据:设置全局数据结构体 g_data 的初始值,并将其写入共享内存。

  4. 连接服务器:创建套接字,初始化服务器地址,尝试连接服务器,若失败则重试。

  5. 创建接收线程:启动一个线程用于接收节点消息。

  6. 主循环

    • 检查共享内存中的更新标志 ,若大于 0 则处理网页命令。

    • 接收节点消息,若接收成功且消息解析通过,则将消息写入共享内存。

  7. 程序结束:关闭套接字。

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

set_web_shm

设置共享内存

该函数用于创建或获取共享内存段,并将其附加到当前进程的地址空间。

2

signal_handler

信号处理函数

当接收到 SIGINT 信号(通常是用户按下 Ctrl+C)时,该函数会分离共享内存并退出程序。

3

init_shm

初始化共享内存

该函数初始化随机数种子,调用 set_web_shm 函数设置共享内存,并注册信号处理函数。

4

init_g_data

初始化全局数据

函数初始化全局数据结构体 g_data 的各个成员。

5

msg_to_shm

网络数据写入共享内存

该函数根据接收到的消息更新全局数据结构体 g_data,并将更新后的数据写入共享内存。

6

connect_with_retry

带重试的连接函数

该函数尝试连接到服务器,如果连接失败,会进行最多 MAX_RETRIES 次重试。

7

calculate_checksum

计算校验和

该函数计算消息的校验和,用于验证消息的完整性。

8

pack_message

封装消息

该函数根据给定的消息类型、节点 ID、数据长度和数据内容,封装一个消息结构体。

9

print_message

调试输出消息

该函数用于打印消息的详细信息,方便调试。

10

parse_message

解析消息

函数检查消息的帧头和校验和,如果都正确,则返回 0,表示解析成功。

11

send_cmd_msg

发送命令消息

该函数封装一个命令消息,并将其发送到服务器。

12

connt_server

连接服务器

该函数创建一个套接字,初始化服务器地址,并调用 connect_with_retry 函数连接到服务器。

13

recv_err_proc

接收错误处理

当接收消息出现错误或服务器断开连接时,该函数会初始化全局数据并将其写入共享内存。

14

recv_node_msg

接收节点消息

该函数从服务器接收消息,并将其存储到全局变量 g_response 中。如果接收失败,会增加错误计数,当错误计数达到 5 次时,调用 recv_err_proc 函数进行错误处理。

15

web_cmd_proc

网页命令处理

该函数根据共享内存中的消息类型,封装并发送不同类型的命令消息到服务器。

16

receive_msg

接收线程函数

该函数是一个线程函数,在一个无限循环中,每隔 1 秒尝试接收消息,解析消息并将其写入共享内存。

4、主函数流程设计

int main(int argc,char* argv[]) {
    // ...
}

主函数的主要流程如下:

  1. 检查命令行参数,确保用户提供了服务器的 IP 地址。

  2. 初始化随机数种子。

  3. 初始化共享内存和全局数据,并将全局数据写入共享内存。

  4. 连接到服务器。

  5. 创建一个接收线程,用于在后台接收和处理消息。

  6. 在一个无限循环中,检查共享内存中的更新标记,如果标记为真,则调用 web_cmd_proc 函数处理网页命令,并更新标记。

  7. 每隔 2 秒尝试接收消息,解析消息并将其写入共享内存。

三、通信协议调用关系分析

  1. 用户浏览器打开数据显示界面data_show.html,其中ajax函数会定时调用node_data.cgi程序。

  2. node_data.cgi程序会修改共享内存的数据。

  3. 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: // 参数设置类型

 

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐