引言:
在网络编程中,我经常听人提起过协议,标准协议、协议族、TCP协议、传输层协议…诸如此类的协议概念。
这些种类繁多的名词听着让人感觉头晕,所以今天继续学习和总结协议的相关知识:在前面一篇文章《网络编程之基础知识详解》中已经简单介绍过协议,本文将继续深入探讨协议及常用的协议格式。
协议及协议格式
协议
协议的基本概念
《网络编程之基础知识详解》一文中介绍了什么是协议、标准协议、和协议族的概念,本文就不再介绍,有兴趣的可以参看上文。
网络协议至少包括三要素:
- 语法:语法是用户数据与控制信息的结构与格式,以及数据出现的顺序。
- 语义:解释控制信息每个部分的意义。它规定了需要发出何种控制信息,以及完成的动作与做出什么样的响应。
- 时序:时序是对事件发生顺序的详细说明。
人们形象地把这三个要素描述为:语义表示要做什么,语法表示要怎么做,时序表示做的顺序。
协议的分类
协议的分类方式有很多种,除了《网络编程之基础知识详解》介绍的原始协议和标准协议之外,还有以下的分类方式:
(1)按编码方式
- 二进制协议,比如网络通信运输层中的 TCP 协议。
- 明文的文本协议,比如应用层的 HTTP、Redis 协议。
- 混合协议(二进制 + 明文),比如苹果公司早期的 APNs 推送协议。
(2)按协议边界
- 固定边界协议
能够明确得知一个协议报文的长度,这样的协议易于解析,比如 TCP 协议。
- 模糊边界协议
无法明确得知一个协议报文的长度,这样的协议解析较为复杂,通常需要通过某些特定的字节来界定报文是否结束,比如 HTTP 协议。
(3)按是否是自定义
常用协议的特点
(1)二进制协议
二进制协议就是一串字节流,通常包括消息头(header)和消息体(body),消息头的长度固定,并且消息头包括了消息体的长度。这样就能够从数据流中解析出一个完整的二进制数据。如下是一个典型的二进制协议:
- Guide 用于标识协议起始,常用 Guide 有 “FFEF”。
- Length 是消息体 Data 的长度,为了数据完整性,还会加上相应的校验(
DataCRC
,HeaderCRC
);
- Data 中又分为命令字(
CMD
),和命令内容。命令字是双方协议文档中规定好的,比如 0x01
代表登录, 0x02
代表登出等。一般数据字段的长度也是固定的,又因为长度的固定,所以少了冗余数据,传输效率较高。
优点
- 空间占用小(包括内存,带宽等)
- 运算规则简单(例如加密方便,毕竟就只有 0 和1 )
- 可靠性高(不是0就是1,还有校验和等技术实现验证。文本协议与之对应的就是数字签名)
- 部分技术场景实现方便(典型的底层硬件,如传感器。因为底层本就是 0 和 1 构成的数据,不需要转换)
缺点
- 可读性差(由此延伸出记忆困难等问题,毕竟位数太多了,还全是0和1,相当于机器码。所以协议的每条命令都要有对应的文档进行细致说明,包括二进制文件采用的是哪种编码方式等)
- 扩展性差(并不是不可以进行消息的扩展,而是已经确定的数据解析顺序,不可以随便改变)
- 无法跨处理器(据说是由于严格的内存到对象的转换。个人的理解是,由于不同处理器架构存在数据存储的大端小端问题而导致的。)
- 部分技术场景实现复杂(例如,原先只要通过
JSON
,就能获取所需数据。而现在,你首先要获取二进制流,可能还需要进行拆包与粘包工作,从而获得二进制数据。再根据协议,一条条地解析命令字与数据域。)
(2)文本协议
文本协议传输的是文本信息流,即字符流,一般是由一串 ACSII
字符组成的数据,包括数字,大小写字母、百分号,还有回车 (\r),换行 (\n) 以及空格等等。
文本协议设计的目的就是方便人们理解,读懂。所以协议中通常会加入一些特殊字符用于分隔,如下所示:
!set chl 003#
其中,以 ! 标识命令的开始,#标识命令结束,空格用来分隔命令字段,虽然我们不知道这条命令具体干什么,但通过字面我们大致知道可能是设置 set
某一个参数 chl
值为 003,这样在我们进行调试的时候,可以快速准确地看到当时发生了什么,更好地解决问题。
当然为了便于解析,文本协议也不得不添加一些冗余的字符用于分隔命令,降低了其传输的效率;而且只适于传输文本,很难嵌入其他数据,比如一张图片。
在进行网络开发中,文本协议和二进制协议是我们最常用的两种。 对比二进制协议,文本协议的优缺点如下:
(3)自定义协议
实际工作中传输层及其以下层协议的实现一般由操作系统内核提供,只有应用层协议的实现由用户进程提供,所以自定义协议一般都是自定义一些应用层的协议。
目前市面上已经有不少通用的应用层协议,例如 HTTP、HTTPS、JSON-RPC、FTP、IMAP、Protobuf 等。这些标准协议兼容性好,易于维护,各种异构系统之间可以实现无缝对接。**如果在满足业务场景以及性能需求的前提下,推荐采用通用协议的方案。**但相比通用协议,自定义协议也存在一些优点,例如:
-
极致性能:通用的通信协议考虑了很多兼容性的因素,必然在性能方面有所损失。
-
扩展性:自定义的协议相比通用协议更好扩展,可以更好地满足自己的业务需求。
-
安全性:通用协议是公开的,很多漏洞已经很多被黑客攻破。自定义协议更加安全,因为黑客需要先破解你的协议内容。
常见的标准协议
IP 协议格式
- 版本:IP协议的版本。通信双方使用过的IP协议的版本必须一致,目前最广泛使用的IP协议版本号为4(即IPv4 )
- 首部长度:单位是32位(4字节)
- 服务类型:一般不适用,取值为0
- 总长度:指首部加上数据的总长度,单位为字节
- 标识(identification):IP软件在存储器中维持一个计数器,每产生一个数据报,计数器就加1,并将此值赋给标识字段
- 标志(flag):目前只有两位有意义。
- 标志字段中的最低位记为MF。MF=1即表示后面“还有分片”的数据报。MF=0表示这已是若干数据报片中的最后一个。
- 标志字段中间的一位记为DF,意思是“不能分片”,只有当DF=0时才允许分片
- 片偏移:指出较长的分组在分片后,某片在源分组中的相对位置,也就是说,相对于用户数据段的起点,该片从何处开始。片偏移以8字节为偏移单位。
- 生存时间:TTL,表明是数据报在网络中的寿命,即为“跳数限制”,由发出数据报的源点设置这个字段。路由器在转发数据之前就把TTL值减一,当TTL值减为零时,就丢弃这个数据报。
- 协议:指出此数据报携带的数据时使用何种协议,以便使目的主机的IP层知道应将数据部分上交给哪个处理过程,常用的ICMP(1),IGMP(2),TCP(6),UDP(17),IPv6(41)
- 首部校验和:只校验数据报的首部,不包括数据部分。
- 源地址:发送方IP地址
- 目的地址:接收方IP地址
MAC 协议格式
MAC 协议也叫以太网帧格式(以太网可以简单理解为局域网):
-
源地址和目的地址是指网卡的硬件地址(也叫MAC地址)
-
类型字段有三种值,0x800 表示 IP、0x806 表示 ARP、0x835 表示 RARP。
-
以太网帧中的数据长度规定最小 46 字节,最大 1500 字节,ARP 和 RARP 数据包的长度不够46字节,要在后面补填充位。
最大值1500称为以太网的最大传输单元(MTU),不同的网络类型有不同的 MTU,如果一个数据包从以太网路由到拨号链路上,数据包长度大于拨号链路的 MTU,则需要对数据包进行分片(fragmentation)。ifconfig 命令输出中也有“MTU:1500”。注意,MTU这个概念指数据帧中有效载荷的最大长度,不包括帧头长度。
-
帧尾是 CRC 校验码。
ARP 协议格式
- 硬件类型: 表示 ARP 报文可以在哪种类型的网络上传输,值为1时表示为以太网地址。
- 协议类型: 表示硬件地址要映射的协议地址类型,映射 IP 地址时的值为 0x0800。
- 硬件地址长度:6
- 协议地址长度:4
- 操作:1 表示 ARP 请求,2 表示 ARP 应答,3 表示 RARP 请求,4 表示 RARP 应答
ARP 报文不是直接在网络层上发送的,它还是需要向下传输到数据链路层,所以当 ARP 报文传输到数据链路层之后,需要再次进行封装。以以太网为例,ARP 报文传输到以太网数据链路层后会形成 ARP 帧。ARP 帧如下图所示,他就是在ARP报文前面加了一个以太网帧头。
- Dest MAC:目的 MAC 地址。 如果它是一个广播帧(即请求包),一般要填上广播 MAC 地址(FF-FF-FF-FF-FF-FF),以表示其目标主机是网络上的所有主机;此时目的端以太网地址需要填充为00:00:00:00:00:00。
- Src MAC:源 MAC 地址
- 帧类型: 用来标识帧封装的上层协议,因为本帧的数据部分是ARP报文,所以直接用ARP的协议号0x0806表示就可以了。
APR 请求与应答的过程:
在网络通讯时,源主机的应用程序知道目的主机的 IP 地址和端口号,却不知道目的主机的硬件地址;数据包首先是被网卡接收到再去处理上层协议的,如果接收到的数据包的硬件地址与本机不符,则直接丢弃。因此在通讯前必须获得目的主机的硬件地址。ARP 协议就起到这个作用。
举例:源主机发出 ARP 请求,询问“ IP 地址是192.168.0.1的主机的硬件地址是多少”,并将这个请求广播到本地网段(以太网帧首部的硬件地址填 FF:FF:FF:FF:FF:FF 表示广播),如果本地网段(本局域网)的目的主机接收到广播的 ARP 请求,发现其中的 IP 地址与本机相符,则发送一个 ARP 应答数据包给源主机,将自己的硬件地址填写在应答包中。
如果本地网段无人应答,ARP 请求将发送到网关,网关将该请求发送到互联网中,本地网段外的目标主机收到 ARP 请求后,则发送一个 ARP 应答数据包给源主机,将自己的硬件地址填写在应答包中。
TCP 协议格式
TCP 传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据。
- 源端口号:发送方端口号
- 目的端口号:接收方端口号
- 序列号:本报文段的数据的第一个字节的序号
- 确认序号:期望收到对方下一个报文段的第一个数据字节的序号
- 首部长度(数据偏移):TCP报文段的数据起始处距离TCP报文段的起始处有多远,即首部长度。单位:32位,即以4字节为计算单位。
- 保留:占6位,保留为今后使用,目前应置为
- 紧急URG: 此位置1,表明紧急指针字段有效,它告诉系统此报文段中有紧急数据,应尽快传送
- 确认ACK: 仅当ACK=1时确认号字段才有效,TCP规定,在连接建立后所有传达的报文段都必须把ACK置1
- 推送PSH:当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能够收到对方的响应。在这种情况下,TCP就可以使用推送(push)操作,这时,发送方TCP把PSH置1,并立即创建一个报文段发送出去,接收方收到PSH=1的报文段,就尽快地(即“推送”向前)交付给接收应用进程,而不再等到整个缓存都填满后再向上交付
- 复位RST: 用于复位相应的TCP连接
- 同步SYN: 仅在三次握手建立TCP连接时有效。当SYN=1而ACK=0时,表明这是一个连接请求报文段,对方若同意建立连接,则应在相应的报文段中使用SYN=1和ACK=1.因此,SYN置1就表示这是一个连接请求或连接接受报文
- 终止FIN:用来释放一个连接。当FIN=1时,表明此报文段的发送方的数据已经发送完毕,并要求释放运输连接。
- 窗口:指发送本报文段的一方的接收窗口(而不是自己的发送窗口)
- 校验和:校验和字段检验的范围包括首部和数据两部分,在计算校验和时需要加上12字节的伪头部
- 紧急指针:仅在URG=1时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据),即指出了紧急数据的末尾在报文中的位置,注意:即使窗口为零时也可发送紧急数据
- 选项:长度可变,最长可达40字节,当没有使用选项时,TCP首部长度是20字节
UDP 协议格式格式
UDP 用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的
- 源端口号:发送方端口号
- 目的端口号:接收方端口号
- 长度:UDP用户数据报的长度,最小值是8(仅有首部)
- 校验和:检测UDP用户数据报在传输中是否有错,有错就丢弃
TCP 和 UDP 区别
TCP 和 UDP 都是传输层的协议 ,总体来说,TCP 与 UDP 区别如下:
- UDP:用户数据报协议,面向无连接,可以单播,多播,广播, 面向数据报,不可靠
- TCP:传输控制协议,面向连接的,可靠的,基于字节流,仅支持单播传输
详细区别如下:
- 连接
- TCP 是⾯向连接的传输层协议,传输数据前先要建⽴连接。
- UDP 是不需要连接,即刻传输数据。
- 服务对象
- TCP 是⼀对⼀的两点服务,即⼀条连接只有两个端点。
- UDP ⽀持⼀对⼀、⼀对多、多对多的交互通信
- 可靠性
- TCP 是可靠交付数据的,数据可以⽆差错、不丢失、不重复、按需到达。
- UDP 是尽最⼤努⼒交付,不保证可靠交付数据。
- 拥塞控制、流量控制
- TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
- UDP 则没有,即使⽹络⾮常拥堵了,也不会影响 UDP 的发送速率。
- ⾸部开销
- TCP ⾸部⻓度较⻓,会有⼀定的开销,首部在没有使⽤「选项」字段时是字 20 个字节,如果使⽤了「选项」段则会变⻓的。
- UDP ⾸部只有 8 个字节,并且是固定不变的,开销较⼩。
- 传输⽅式
- TCP 是流式传输,没有边界,但保证顺序和可靠。
- UDP 是⼀个包⼀个包的发送,是有边界的,但可能会丢包和乱序。
- TCP 和 UDP 应⽤场景:
- 由于 TCP 是⾯向连接,能保证数据的可靠性交付,因此经常⽤于:
- 由于 UDP ⾯向⽆连接,它可以随时发送数据,再加上UDP本身的处理既简单⼜⾼效,因此经常⽤于:
- 包总量较少的通信,如 DNS、SNMP 等
- 视频、⾳频等多媒体通信
- ⼴播通信
参考文档
《文本协议与二进制协议的选择 》
《二进制协议 VS 文本协议》
《手把手教你实现自定义的应用层协议》