- 应用层报文协议设计
为了让数据能够按时间序列存储以及按时间序列设计索引取出数据,并且能够在应用层对数据包进行校验,能够用过状态机编程以实现数据的检验,重传,成功发送等多种状态。
具体设计如下:
- 时间戳字段,字段名time_stamp, 数据格式 time_t, 由七个uint16_t构成的结构体,
分别表示年-月-日-时-分-秒-毫秒。时间戳字段一方面用于作为数据传输的校验功能,另一方面用于数据的存储与按时间回放,将时间戳作为key构建索引。
- 1秒几帧图像字段,字段名fps, 数据格式uint16_t,表示一秒由几帧图像,用于在 应用层将图像数据的拼接。
- 帧数字段,字段名frame,数据格式uint16_t, 表示当前图像数据属于第几帧,和 fps字段配合使用。按序将数据包拼接。
- 设备通道号字段,字段名channel,数据格式uint16_t,由于数据是多源传感器来的,因此可能出现同一时刻不同设备的数据需要存储,因此需要加设备通道号字段
- 图像包序号字段,字段名index,数据格式uint16_t, 标记当前数据包的序号,为了检验接收端是否完整接收数据包
- 数据包大小字段,字段名size,数据格式uint32_t, 标记当前数据包实际发送了多少字节的数据,为了接收端校验是否完整接收数据包
- 设计了4个标志位,结合状态机编程以实现应用层的校验,重传,确认机制,由于状态位用0, 1即可表示,因此设计成位图(bitmap)存储,可以节省32倍空间
- 确认号标志位,字段名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 重传数据包
}
- 重传标志位,字段名RST, 数据格式bitmap,数据包如果接收端经校验之后丢包,则开启重传标志, 发送端开始重传数据包代码,需要重传则设为1, 否则设为0
例子可参考ACK
- 检验位,字段名CHECK, 数据格式bitmap,接收端完成对数据包大小和序号的校验之后,如果正确则将校验位置为1,代表校验成功,否则代表校验失败,需要 经过重传处理
例子可参考ACK
- 完成标志位,字段名FINISH,数据格式bitmap,接收端经校验后收到完整的数据包,则完成标志位的值置为1,表示当前数据包完成发送,可开启下个数据包的发送,否则一直值为0,发送端与接收端继续保持当前数据包的传输
例子可参考ACK
- 保留标志位,字段名OFFSET,可扩充字段,当程序需要加入新的状态的时候可以直接增加标志位,而不用改变之前的数据结构的内存布局,同时作为偏移量补充之前状态位。
- 原始数据,字段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;
- .位图设计
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;
};
- 状态机编程
#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 重传数据包
}