为了让数据能够按时间序列存储以及按时间序列设计索引取出数据,并且能够在应用层对数据包进行校验,能够用过状态机编程以实现数据的检验,重传,成功发送等多种状态。
具体设计如下:
时间戳字段,字段名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 重传数据包
}