• 应用层报文协议设计

为了让数据能够按时间序列存储以及按时间序列设计索引取出数据,并且能够在应用层对数据包进行校验,能够用过状态机编程以实现数据的检验,重传,成功发送等多种状态。

具体设计如下:

  1. 时间戳字段,字段名time_stamp, 数据格式 time_t, 由七个uint16_t构成的结构体,

分别表示年-月-日-时-分-秒-毫秒。时间戳字段一方面用于作为数据传输的校验功能,另一方面用于数据的存储与按时间回放,将时间戳作为key构建索引。

  1. 1秒几帧图像字段,字段名fps, 数据格式uint16_t,表示一秒由几帧图像,用于在     应用层将图像数据的拼接。
  2. 帧数字段,字段名frame,数据格式uint16_t, 表示当前图像数据属于第几帧,和 fps字段配合使用。按序将数据包拼接。
  3. 设备通道号字段,字段名channel,数据格式uint16_t,由于数据是多源传感器来的,因此可能出现同一时刻不同设备的数据需要存储,因此需要加设备通道号字段
  4. 图像包序号字段,字段名index,数据格式uint16_t, 标记当前数据包的序号,为了检验接收端是否完整接收数据包
  5. 数据包大小字段,字段名size,数据格式uint32_t, 标记当前数据包实际发送了多少字节的数据,为了接收端校验是否完整接收数据包
  6. 设计了4个标志位,结合状态机编程以实现应用层的校验,重传,确认机制,由于状态位用0, 1即可表示,因此设计成位图(bitmap)存储,可以节省32倍空间
  1. 确认号标志位,字段名ACK, 数据格式bitmap,数据包是否接收标志位,如果已经接收则字段值设为1,如果没有则设为0

使用状态机例子,利用位运算实现状态转换:

#define ACK 0x01

#define NOACK 0x00

Package package;//收到的数据包

if (数据包传输成功) {

package.ack |= ACK;

} else {

package.ack |= NOACK;

}

if (package.ack & ACK) {

//成功传输后续处理

} else if (package.ack & ACKNO) {
//传输失败,丢弃数据 or 重传数据包

}

  1. 重传标志位,字段名RST,  数据格式bitmap,数据包如果接收端经校验之后丢包,则开启重传标志, 发送端开始重传数据包代码,需要重传则设为1, 否则设为0

例子可参考ACK

  1. 检验位,字段名CHECK, 数据格式bitmap,接收端完成对数据包大小和序号的校验之后,如果正确则将校验位置为1,代表校验成功,否则代表校验失败,需要 经过重传处理

例子可参考ACK

  1. 完成标志位,字段名FINISH,数据格式bitmap,接收端经校验后收到完整的数据包,则完成标志位的值置为1,表示当前数据包完成发送,可开启下个数据包的发送,否则一直值为0,发送端与接收端继续保持当前数据包的传输

例子可参考ACK

  1. 保留标志位,字段名OFFSET,可扩充字段,当程序需要加入新的状态的时候可以直接增加标志位,而不用改变之前的数据结构的内存布局,同时作为偏移量补充之前状态位。
  1. 原始数据,字段rawdata,数据类型位uint16_t[],大小控制在1000字节/包以内,考虑到底层传输效率与拆包丢包,数据包所有字节加一起应小于链路层的最小传输单元(1500字节),去除网络层的ip包头(20字节,用于寻址),传输层的tcp包头(20字节,用于可靠传输)/udp包头(8字节,基本多路复用/多路分解),应用层包头(31字节),因此一个包的数据应控制在1000字节。

 

二.协议设计

字段

内存

数据格式

说明

时间戳

time_stamp

0~14

time_t

(short year

short mon

short day

short hour

short min

short sec

short microsec)

格式:年-月-日 时-分-秒-毫秒

大小:16 * 7 = 112bit = 14bytes

Fps(1秒几帧图像)

14~16

uint16_t

表示一秒有几帧图像,用于应用层将图像数据拼接

大小:2bytes

帧数

frame

16~18

uint16_t

表示当前图像数据属于第几帧

大小:2bytes

设备通道号

channel

18~20

uint16_t

表示应用层数据属于哪个设备的,对设备进行编号

大小:2bytes

图像包序号

index

20~22

uint16_t

应用层数据包编号,校验数据包的顺序,为了按序将图像包拼接

大小:2bytes

数据包大小

size

22~30

uint32_t

数据包发送有多少字节,为了接收端校验是否完整接受数据包

大小:4bytes

确认号标志位

ack

30~31

(1bit)

bitmap

数据包是否接收标志位,已接收为1,没有收到则一直为0

大小:1bit

重传标志位

rst

30~31

(2bit)

bitmap

数据包如果接收端经校验之后丢包,则开启重启重传标志,发送端开始重传数据

需要重传值为1,否则一直为0

大小:1bit

 

完成标志位

finish

30~31

(3bit)

bitmap

接收端经校验后收到完整的数据包,则完成标志位为1,表示当前数据包完成发送,可开启下个数据包的发送,否则则一直值为0,发送端与接收端继续保持当前数据包的传输

大小:1bit

校验位

check

30~31

(4bit)

bitmap

接收端完成对数据包大小和序号的校验之后,如果正确则将校验位为1代表校验成功,否则代表校验失败,需要经过重传处理

大小:1bit

保留位

offset

30~31

(5~8bit)

bitmap

预留4个bit的标志位,支持扩展更多的状态机,同时做字节对齐

大小:4bits

原始数据

rawdata

31~1000

uint16_t[]

要发送的传感器数据

 

3.协议内存设计

                                     32位

               16  year                 

         16   mon

               16  day         

         16   hour

               16  min                 

         16   sec

               16  microsec

         16   fps

               16  frame

         16   channel

               16  index

ack        

rst

fin

che

4  offset

 

                                    32   size

                                  n   rawdata  

                                        .

                                        .

                                        .

 

四代码示例

1)数据包设计

//日期

typedef struct Date {

    bool use_string;

    uint16_t year;

    uint16_t month;

    uint16_t day;

    uint16_t hour;

    uint16_t minute;

    uint16_t second;

    string time; // YYYY-MM-DD h-m-s

} Date_t;

//通道

typedef enum Channel {

    A = 1,

    B = 2,

C = 3,

D = 4

} Channel_t;

//1秒6帧图像

typedef enum Frame {

    ONE = 1,

    TWO = 2,

    THREE = 3,

    FOUR = 4,

    FIVE = 5,

    SIX = 6

} Frame_t;

 

typedef struct PackageHead {

    Date_t time_stamp; //时间戳

uint16_t fps;//1秒几帧图像

    Frame_t frame; //帧数

    Channel_t channel; //通道编号

    uint8_t index; //图像包序号,一帧图像会拆包,底层有最大传输单元限制,一个包最多有1500字节 - TCP/UDP包头 - 应用层协议包头

    uint16_t data_size; //传输的数据包大小

    bool reliable; //可靠传输标志位

    bool ack; //确认号标志位

    bool rst; //重传标志位

    uint16_t seq; //udp包序号

    bool finish; //一帧图像是否传完

    uint8_t offset[16]; //16字节可扩容字段

} PackageHead;

 

typedef struct Package {

    PackageHead head; //包头

    uint8_t raw_data[1000]; //最好不要超过1000字节

} Package;

 

  1. .位图设计

class Bitmap {

public:

    Bitmap(int size, int _key = 32)

    {

        key = _key;

        buffer.resize(size / key + 1);

    }

    void insert(int value)

    {

        int seg_index = value / key;

        int index = value % key;

        buffer[seg_index] |= (1 << index);

    }

    void get(int value)

    {

        int seg_index = value / key;

        int index = value % key;

        if (buffer[seg_index] & (1 << index)) {

            cout << value << " is in bitmap" << endl;

        } else {

            cout << value << "is not int bitmap" << endl;

        }

    }

 

private:

    vector<uint32_t> buffer;

    int key;

};

  1. 状态机编程

#define ACK 0x01

#define NOACK 0x00

Package package;//收到的数据包

if (数据包传输成功) {

package.ack |= ACK;

} else {

package.ack |= NOACK;

}

if (package.ack & ACK) {

//成功传输后续处理

} else if (package.ack & ACKNO) {
//传输失败,丢弃数据 or 重传数据包

}