1. 数据包封装

传输层及其以下的机制由内核提供,应用层由用户进程提供(后面将介绍如何使用 socket API编写应用程序),应用程序对通讯数据的含义进行解释,而传输层及其以下 处理通讯的细节,将数据从一台计算机通过一定的路径发送到另一台计算机。应用层 数据通过协议栈发到网络上时,每层协议都要加上一个数据首部(header),称为封装 (Encapsulation),如下图所示

Android C++系列:Linux网络(三)协议格式_数据

不同的协议层对数据包有不同的称谓,在传输层叫做段(segment),在网络层叫做数 据报(datagram),在链路层叫做帧(frame)。数据封装成帧后发到传输介质上,到达目 的主机后每层协议再剥掉相应的首部,最后将应用层数据交给应用程序处理。

2. 以太网帧格式

其中的源地址和目的地址是指网卡的硬件地址(也叫MAC地址),长度是48位,是在网 卡出厂时固化的。用ifconfig命令看一下,“HWaddr 00:15:F2:14:9E:3F”部分就是硬件地 址。协议字段有三种值,分别对应IP、ARP、RARP。帧末尾是CRC校验码。

Android C++系列:Linux网络(三)协议格式_客户端_02

以太网帧中的数据长度规定最小46字节,最大1500字节,ARP和RARP数据包的长度不够 46字节,要在后面补填充位。最大值1500称为以太网的最大传输单元(MTU),不同的网络 类型有不同的MTU,如果一个数据包从以太网路由到拨号链路上,数据包长度大于拨号链路 的MTU了,则需要对数据包进行分片(fragmentation)。ifconfig命令的输出中也有“MTU: 1500”。注意,MTU这个概念指数据帧中有效载荷的最大长度,不包括帧首部的长度。

3. ARP数据包格式

在网络通讯时,源主机的应用程序知道目的主机的IP地址和端口号,却不知道目的主机 的硬件地址,而数据包首先是被网卡接收到再去处理上层协议的,如果接收到的数据包的 硬件地址与本机不符,则直接丢弃。因此在通讯前必须获得目的主机的硬件地址。ARP协议 就起到这个作用。源主机发出ARP请求,询问“IP地址是192.168.0.1的主机的硬件地址是多 少”,并将这个请求广播到本地网段(以太网帧首部的硬件地址填FF:FF:FF:FF:FF:FF表示 广播),目的主机接收到广播的ARP请求,发现其中的IP地址与本机相符,则发送一个ARP应 答数据包给源主机,将自己的硬件地址填写在应答包中。
每台主机都维护一个ARP缓存表,可以用arp -a命令查看。缓存表中的表项有过期时间 (一般为20分钟),如果20分钟内没有再次使用某个表项,则该表项失效,下次还要发ARP 请求来获得目的主机的硬件地址。想一想,为什么表项要有过期时间而不是一直有效?
ARP数据报的格式如下所示

Android C++系列:Linux网络(三)协议格式_数据_03

注意到源MAC地址、目的MAC地址在以太网首部和ARP请求中各出现一次,对于链路层为 以太网的情况是多余的,但如果链路层是其它类型的网络则有可能是必要的。硬件类型指链 路层网络类型,1为以太网,协议类型指要转换的地址类型,0x0800为IP地址,后面两个地 址长度对于以太网地址和IP地址分别为6和4(字节),op字段为1表示ARP请求,op字段为2 表示ARP应答。
下面举一个具体的例子。 请求帧如下(为了清晰在每行的前面加了字节计数,每行16个字节): 以太网首部(14字节)
0000: ffffffffffff00055d6158a80806
ARP帧(28字节)
0000: 00 01
0010: 08000604000100055d6158a8c0a80037
0020: 000000000000c0a80002
填充位(18字节)
0020: 00 77 31 d2 50 10
0030: fd7841d30000000000000000
以太网首部:目的主机采用广播地址,源主机的MAC地址是00:05:5d:61:58:a8,上层协议类型0x0806表示ARP。
ARP帧:硬件类型0x0001表示以太网,协议类型0x0800表示IP协议,硬件地址(MAC地址)长度为6,协议地址(IP地址)长度为4,op为0x0001表示请求目的主机的MAC地址,源 主机MAC地址为00:05:5d:61:58:a8,源主机IP地址为c0 a8 00 37(192.168.0.55),目的 主机MAC地址全0待填写,目的主机IP地址为c0 a8 00 02(192.168.0.2)。
由于以太网规定最小数据长度为46字节,ARP帧长度只有28字节,因此有18字节填充 位,填充位的内容没有定义,与具体实现相关。
应答帧如下:
以太网首部
0000: 00055d6158a800055da1b8400806
ARP帧
0000: 00 01
0010: 08000604000200055da1b840c0a80002
0020: 00055d6158a8c0a80037
填充位
0020: 00 77 31 d2 50 10
0030: fd7841d30000000000000000
以太网首部:目的主机的MAC地址是00:05:5d:61:58:a8,源主机的MAC地址是00:05:5d:a1:b8:40,上层协议类型0x0806表示ARP。
ARP帧:硬件类型0x0001表示以太网,协议类型0x0800表示IP协议,硬件地址(MAC地址)长度为6,协议地址(IP地址)长度为4,op为0x0002表示应答,源主机MAC地址为 00:05:5d:a1:b8:40,源主机IP地址为c0 a8 00 02(192.168.0.2),目的主机MAC地址为 00:05:5d:61:58:a8,目的主机IP地址为c0 a8 00 37(192.168.0.55)。
如果源主机和目的主机不在同一网段,ARP请求的广播帧无法穿过路由器,源 主机如何与目的主机通信?

4. IP段格式

Android C++系列:Linux网络(三)协议格式_首部_04

IP数据报的首部长度和数据长度都是可变长的,但总是4字节的整数倍。对于IPv4,4 位版本字段是4。4位首部长度的数值是以4字节为单位的,最小值为5,也就是说首部长度 最小是4x5=20字节,也就是不带任何选项的IP首部,4位能表示的最大值是15,也就是说首 部长度最大是60字节。8位TOS字段有3个位用来指定IP数据报的优先级(目前已经废弃不 用),还有4个位表示可选的服务类型(最小延迟、最大吐量、最大可靠性、最小成本), 还有一个位总是0。总长度是整个数据报(包括IP首部和IP层payload)的字节数。每传一 个IP数据报,16位的标识加1,可用于分片和重新组装数据报。3位标志和13位片偏移用于 分片。TTL(Time to live)是这样用的:源主机为数据包设定一个生存时间,比如64,每过 一个路由器就把该值减1,如果减到0就表示路由已经太长了仍然找不到目的主机的网络, 就丢弃该包,因此这个生存时间的单位不是秒,而是跳(hop)。协议字段指示上层协议 是TCP、UDP、ICMP还是IGMP。然后是校验和,只校验IP首部,数据的校验由更高层协议负 责。IPv4的IP地址长度为32位。选项字段的解释从略。
想一想,前面讲了以太网帧中的最小数据长度为46字节,不足46字节的要用填充字节补 上,那么如何界定这46字节里前多少个字节是IP、ARP或RARP数据报而后面是填充字节?

5. UDP数据包格式

Android C++系列:Linux网络(三)协议格式_数据_05
下面分析一帧基于UDP的TFTP协议帧。
以太网首部
0000: 00055d67d0b100055d6158a80800
IP首部
0000: 45 00
0010: 005393250000801125ecc0a80037c0a8
0020: 00 01
UDP首部
0020: 05 d4 00 45 00 3f ac 40
TFTP协议
0020: 00 01 ‘c”:‘”’q’
0030: ‘w’ ‘e’ ‘r’ ‘q”.‘’q’‘ w’ ‘e’ 00 ’n’ ‘e‘ ’t’ ‘a‘ ’s’ ‘c’ ‘i’
0040: ‘i’ 00 ’b’ ‘l’ ‘k‘ ’s’ ‘i’ ‘z’ ‘e’ 00 ’5’ ‘1’ ‘2’ 00 ’t’ ‘i’
0050: ’m’ ‘e’ ‘o’ ‘u‘ ’t’ 00 ‘1’ ‘0’ 00 ’t‘ ’s’ ‘i’ ‘z’ ‘e’ 00 ’0’
0060: 00
以太网首部:源MAC地址是00:05:5d:61:58:a8,目的MAC地址是00:05:5d:67:d0:b1,上层协议类型0x0800表示IP。
IP首部:每一个字节0x45包含4位版本号和4位首部长度,版本号为4,即IPv4,首部长度为5,说明IP首部不带有选项字段。服务类型为0,没有使用服务。16位总长度字段(包 括IP首部和IP层payload的长度)为0x0053,即83字节,加上以太网首部14字节可知整个帧长度是97字节。IP报标识是0x9325,标志字段和片偏移字段设置为0x0000,就是DF=0允许分 片,MF=0此数据报没有更多分片,没有分片偏移。TTL是0x80,也就是128。上层协议0x11表 示UDP协议。IP首部校验和为0x25ec,源主机IP是c0 a8 00 37(192.168.0.55),目的主机 IP是c0 a8 00 01(192.168.0.1)。
UDP首部:源端口号0x05d4(1492)是客户端的端口号,目的端口号0x0045(69)是 TFTP服务的well-known端口号。UDP报长度为0x003f,即63字节,包括UDP首部和UDP层pay- load的长度。UDP首部和UDP层payload的校验和为0xac40。
TFTP是基于文本的协议,各字段之间用字节0分隔,开头的00 01表示请求读取一个文 件,接下来的各字段是:

c:\qwerq.qwe 
netascii
blksize 512
timeout 10
tsize 0

一般的网络通信都是像TFTP协议这样,通信的双方分别是客户端和服务器,客户端主动 发起请求(上面的例子就是客户端发起的请求帧),而服务器被动地等待、接收和应答请 求。客户端的IP地址和端口号唯一标识了该主机上的TFTP客户端进程,服务器的IP地址和端 口号唯一标识了该主机上的TFTP服务进程,由于客户端是主动发起请求的一方,它必须知道 服务器的IP地址和TFTP服务进程的端口号,所以,一些常见的网络协议有默认的服务器端 口,例如HTTP服务默认TCP协议的80端口,FTP服务默认TCP协议的21端口,TFTP服务默认UDP 协议的69端口(如上例所示)。在使用客户端程序时,必须指定服务器的主机名或IP地址, 如果不明确指定端口号则采用默认端口,请读者查阅ftp、tftp等程序的man page了解如何 指定端口号。/etc/services中列出了所有well-known的服务端口和对应的传输层协议,这 是由IANA(Internet Assigned Numbers Authority)规定的,其中有些服务既可以用TCP也 可以用UDP,为了清晰,IANA规定这样的服务采用相同的TCP或UDP默认端口号,而另外一些 TCP和UDP的相同端口号却对应不同的服务。
很多服务有well-known的端口号,然而客户端程序的端口号却不必是well-known的, 往往是每次运行客户端程序时由系统自动分配一个空闲的端口号,用完就释放掉,称为 ephemeral的端口号,想想这是为什么。
前面提过,UDP协议不面向连接,也不保证传输的可靠性,例如:
发送端的UDP协议层只管把应用层传来的数据封装成段交给IP协议层就算完成任务了, 如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息。
接收端的UDP协议层只管把收到的数据根据端口号交给相应的应用程序就算完成任务 了,如果发送端发来多个数据包并且在网络上经过不同的路由,到达接收端时顺序已经错乱 了,UDP协议层也不保证按发送时的顺序交给应用层。
通常接收端的UDP协议层将收到的数据放在一个固定大小的缓冲区中等待应用程序来提 取和处理,如果应用程序提取和处理的速度很慢,而发送端发送的速度很快,就会丢失数据 包,UDP协议层并不报告这种错误。
因此,使用UDP协议的应用程序必须考虑到这些可能的问题并实现适当的解决方案,例 如等待应答、超时重发、为数据包编号、流量控制等。一般使用UDP协议的应用程序实现都 比较简单,只是发送一些对可靠性要求不高的消息,而不发送大量的数据。例如,基于UDP 的TFTP协议一般只用于传送小文件(所以才叫trivial的ftp),而基于TCP的FTP协议适用于 各种文件的传输。下面看TCP协议如何用面向连接的服务来代替应用程序解决传输的可靠性问题。

6. TCP数据报格式

Android C++系列:Linux网络(三)协议格式_客户端_06
和UDP协议一样也有源端口号和目的端口号,通讯的双方由IP地址和端口号标识。32位 序号、32位确认序号、窗口大小稍后详细解释。4位首部长度和IP协议头类似,表示TCP协 议头的长度,以4字节为单位,因此TCP协议头最长可以是4x15=60字节,如果没有选项字 段,TCP协议头最短20字节。URG、ACK、PSH、RST、SYN、FIN是六个控制位,本节稍后将解 释SYN、ACK、FIN、RST四个位,其它位的解释从略。16位检验和将TCP协议头和数据都计算 在内。紧急指针和各种选项的解释从略。

通信时序

下图是一次TCP通讯的时序图。

Android C++系列:Linux网络(三)协议格式_数据_07

在这个例子中,首先客户端主动发起连接、发送请求,然后服务器端响应请求,然后客户端主动关闭连接。两条竖线表示通讯的两端,从上到下表示时间的先后顺序,注意,数据从一端传到网络的另一端也需要时间,所以图中的箭头都是斜的。双方发送的段按时间顺 序编号为1-10,各段中的主要信息在箭头上标出,例如段2的箭头上标着SYN, 8000(0), ACK 1001, ,表示该段中的SYN位置1,32位序号是8000,该段不携带有效载荷(数据字节数为 0),ACK位置1,32位确认序号是1001,带有一个mss选项值为1024。

建立连接的过程:

  1. 客户端发出段1,SYN位表示连接请求。序号是1000,这个序号在网络通讯中用作临时 的地址,每发一个数据字节,这个序号要加1,这样在接收端可以根据序号排出数据包的正 确顺序,也可以发现丢包的情况,另外,规定SYN位和FIN位也要占一个序号,这次虽然没发 数据,但是由于发了SYN位,因此下次再发送应该用序号1001。mss表示最大段尺寸,如果一 个段太大,封装成帧后超过了链路层的最大帧长度,就必须在IP层分片,为了避免这种情 况,客户端声明自己的最大段尺寸,建议服务器端发来的段不要超过这个长度。
  2. 服务器发出段2,也带有SYN位,同时置ACK位表示确认,确认序号是1001,表示“我 接收到序号1000及其以前所有的段,请你下次发送序号为1001的段”,也就是应答了客户端 的连接请求,同时也给客户端发出一个连接请求,同时声明最大尺寸为1024。
  3. 客户端发出段3,对服务器的连接请求进行应答,确认序号是8001。

在这个过程中,客户端和服务器分别给对方发了连接请求,也应答了对方的连接请求, 其中服务器的请求和应答在一个段中发出,因此一共有三个段用于建立连接,称为’‘’三 方握手(three-way-handshake)”’。在建立连接的同时,双方协商了一些信息,例如双方发送序号的初始值、最大段尺寸等。
在TCP通讯中,如果一方收到另一方发来的段,读出其中的目的端口号,发现本机并没 有任何进程使用这个端口,就会应答一个包含RST位的段给另一方。
数据传输的过程:

  1. 客户端发出段4,包含从序号1001开始的20个字节数据。
  2. 服务器发出段5,确认序号为1021,对序号为1001-1020的数据表示确认收到,同时请求发送序号1021开始的数据,服务器在应答的同时也向客户端发送从序号8001开始的10个字 节数据,这称为piggyback。
  3. 客户端发出段6,对服务器发来的序号为8001-8010的数据表示确认收到,请求发送序 号8011开始的数据。

关闭连接的过程:

  1. 客户端发出段7,FIN位表示关闭连接的请求。
  2. 服务器发出段8,应答客户端的关闭连接请求。
  3. 服务器发出段9,其中也包含FIN位,向客户端发送关闭连接请求。
  4. 客户端发出段10,应答服务器的关闭连接请求。

7. 总结

本文介绍了网络协议格式:数据包封装、以太网帧格式、ARP数据包格式、IP段格式、UDP数据包格式、TCP数据包格式等。