在实际编程应用中,两个或多个功能服务(模块)之间 需要通过消息交互进行协作完成用户想要的逻辑功能,这里的消息交互指的是应用层的交互。最终数据传输(无论是TCP/IP还是其它)都是以二进制形式完成,但对于应用层协议来说有两种,一种是二进制协议,一种是文本协议。不管是哪种协议,对于协议字段的设计都有一些约定俗成的定义。
一、定义
1.1 二进制协议
二进制协议是以结构的形式进行协议定义,在Linux 中以结构体的形式进行处理,一般由消息头和消息数据组成,如下:
struct msg_hdr {
int id;
int type;
int length;
};
struct msg_data {
char data[0];
};
1.2 文本协议
文本协议的展示方式是比较容易识别和理解的ASCII码,包括数字,字母,一些特殊符号等,典型代表就是JSON和XML
xml 示例:
<student>
<info>
<name>shenliu</name>
<id>100</id>
</info>
</student>
json示例:
{
"csdnUser": {
"name": "shenliu",
"salary": 56000,
"married": true
}
}
二、协议特点
- 二进制协议不需要包含 定义数据的结构信息,数据量相比文本协议的要小
- 文本协议的优点是不用预先定义数据的传输结构,省下了开发时间
- 二进制协议需要通信双方事先约定协议定义(数据结构)
- 文本协议可读性高
三、协议的使用
3.1 文本协议的使用
xml和json一般指可扩展标记语言,可以用于两个程序之间的数据传输。主要应用在以下场景:
- 客户端或服务端程序的配置文件
- 双方的信息交互
由于json更简洁、清晰的定义,越来越多的替代xml的使用场景。尤其在Restful API设计流行之后,与http配合,越来越被广大程序员所接受。
3.2 二进制的使用
二进制协议由于其传输数据量小,广泛用于底层网络协议中,如IP层、网络层、传输层等,也常用于Linux C服务模块间的通信。
四、实际编程
4.1 二进制
定义结构体如下
消息头:
struct msg_hdr
{
int msg_type;
int msg_length;
};
消息数据:
struct msg_data
{
char data[0];
};
示例:
char buf[4096] = {0};
int msg_len = 0;
struct msg_hdr *msg_hdr = NULL;
struct msg_data *msg_data = NULL;
msg_hdr = (struct msg_hdr *)buf;
msg_data = (struct msg_data*)(buf + sizeof(struct msg_hdr));
snprintf(msg_hdr->data, 0, "%s", "hello world");
msg_hdr->msg_type = 0;
msg_hdr->length = strlen("hello world");
send_len = msg_hdr->length + sizeof(struct msg_hdr);
msg_send_xx(send_len, buf);
五、协议设计
网络上有很多耳熟能详的应用层的网络协议,http、sip等,在实际的软件设计中,也会自定义网络协议。不管是RFC定义的协议还是软件开发者自定义的协议,都有一些公认的设计技巧或实用设计。
5.1 消息头和消息体
应用协议一般都分消息头和消息体两部分。消息体的长度和类型由消息头中的相应字段表明。如下SIP协议示例:
5.2 消息类型
在一般的通信协议中, 会有很多不同的消息,对应枚举定义或宏定义。消息类型又分为主类型和子类型
5.3 变长数据
一般协议设计中,消息体的长度都是变化的,消息类型会有一种或多种,针对在Linux c编程中的二进制协议,一般使用0字节数组表示变长数据。
5.4 序列号
序列号是通信协议的重要字段,它有以下几个作用:
作用一: 网络存在抖动、时延、拥塞等情况,因此收到的网络数据存在乱序的可能,序列号可以标识每条消息应有的顺序,在出现乱序时,进行重排列,使逻辑功能正常运行。
作用二:网络存在丢包的可能,如果没有序列号,可能无法判断是否有丢包情况发生
5.5 时间戳
时间戳是通信协议中重要的字段,可以表明发送端发送的起始时间戳,在双方都支持NTP机制的前提下,可以确认消息收发是否有延时。在不支持NTP机制的情况下,也可以通过相对时间分析出是否有时延出现。
.