本文通过一次通信实例对使用TCP传输控制协议传输数据时可能遇到的问题进行探讨。
假设有通信双方,记发送方为A,接收方为B,信息分别如下:
A:32位linux操作系统,IP地址是172.16.1.216,只有eth0一块网卡,其MAC为00:0C:29:2F:50:B8,为虚拟机。
B:64位linux操作系统,IP地址是10.10.1.153,只有eth0一块网卡,其MAC为00:50:56:81:58:27,为实体机。
实验过程
A往B发送255包数据,每包数据的长度为100字节,每个字节的值是当前包的序号。在B端对整个通信过程进行抓包。
实验结果
除TCP建连断连数据包外,B总共接收到的有效数据包有20个,序号分别从3至22。下图是抓包结果部分截图。
下表是根据抓包结果整理的各个数据包的详细信息。
序号 | 包大小 | 数据长度 | 数据内容 |
3 | 166 | 100 | 01 * 100 |
4 | 1514 | 1448 | (02 ~ 0f) * 100 + 10 * 48 |
5 | 1514 | 1448 | 10 * 52 + (11 ~ 1d) * 100 + 1e * 96 |
6 | 1514 | 1448 | 1e * 4 + (1f ~ 2c) * 100 + 2d * 44 |
7 | 1514 | 1448 | 2d * 56 + (2e ~ 3a) * 100 + 3b * 92 |
8 | 1514 | 1448 | 3b * 8 + (3c ~ 49) * 100 + 4a * 40 |
9 | 1514 | 1448 | 4a * 60 + (4b ~ 57) * 100 + 58 * 88 |
10 | 1514 | 1448 | 58 * 12 + (59 ~ 66) * 100 + 67 * 36 |
11 | 1514 | 1448 | 67 * 64 + (68 ~ 74) * 100 + 75 * 84 |
12 | 82 | 16 | 75 * 16 |
13 | 1514 | 1448 | db * 64 + (dc ~ e8) * 100 + e9 * 84 |
14 | 1514 | 1448 | e9 * 16 + (ea ~ f7) * 100 + f8 * 32 |
15 | 834 | 768 | f8 * 68 + (f9 ~ ff) * 100 |
16 | 1514 | 1448 | (76 ~ 83) * 100 + 84 * 48 |
17 | 1514 | 1448 | 84 * 52 + (85 ~ 91) * 100 + 92 * 96 |
18 | 1514 | 1448 | 92 * 4 + (93 ~ a0) * 100 + a1 * 44 |
19 | 1514 | 1448 | a1 * 56 + (a2 ~ ae) * 100 + af * 92 |
20 | 1514 | 1448 | af * 8 + (b0 ~ bd) * 100 + be * 40 |
21 | 1514 | 1448 | be * 60 + (bf ~ cb) * 100 + cc * 88 |
22 | 1514 | 1448 | cc * 12 + (cd ~ da) * 100 + db * 36 |
说明:从结果可知,在发送第12包数据后,部分数据未被成功接收到,第16至22包为重传数据包。
从表中数据容易看出,每个数据包在应用数据的前面都有额外的66个字节,这66个字节从前往后分别是链路层、网络层和传输层添加的消息头(封包),各个消息头长度分别为14字节、20字节和32字节。
下图说明了数据进入协议栈时的封装过程。
为方便说明起见,下面以序号为5的数据包为例,分别对各层封包进行说明。
以太网首部内容如下:
以太网首部占14个字节,包含了源地址和目的地址网卡MAC信息,其中前6个字节为目的网卡的MAC,这里是00:50:56:81:58:27,中间6个字节为源网卡的MAC,这里是00:0f:e2:60:8c:f0(与实际不符,多次测试发现是由于机器为虚拟机导致),最后两个字节代表协议类型,这里是0800,表示类型为IP协议。
一般地,链路层从网络层收到数据后,会在数据前添加目的MAC、源网卡MAC以及网络层采用的协议类型,在数据后添加4字节的CRC校验结果,用于帧内后续字节差错的循环冗余码校验。封包过程示意图如下:
由于数据包尾部的4字节CRC校验数据是在数据进入物理线路时加上的,而tcpdump、wireshark等抓包工具工作在链路层之下、物理线路之上,因此在抓包结果中没有体现。另外提一下,如果发送端和接收端布署在同一台机器上,通过回环链路进行通信,那么抓包结果中链路层是没有数据的,因为数据只到网络层,不经过链路层。
以下是第5包数据IP首部截图。
从截图可以看出,IP首部占20个字节,第一个字节的高四位为4,表示协议版本为IPv4,低四位为5,表示本IP数据报为普通数据报,不含任何选项(首部占20个字节)。IP首部的最后8个字节中前4个字节表示源IP地址,这里是ac 10 01 d8,转换成点分十进制表示方式后为172.16.1.216,后4个字节表示目的IP地址,这里是0a 0a 01 99,对应的点分十进制形式为10.10.1.153。一般地,IP数据报的格式及首部中各字段的含义如下图所示。
下面是第5包数据的TCP首部截图。
该数据包的TCP首部占32字节,前两字节表示源应用程序使用的端口号,这里是33164,第3、4字节表示目的应用程序使用的端口号,这里是9527。一般地,TCP包的数据格式及首部各字段含义如下图所示。
纵观整个通信过程,容易看出只有少数几个数据包的大小不规则(开始、结束以及出现错误重传时),正常情况下每包数据包含1448字节的应用层数据,下面对此做简单解释。
以太网对数据帧的长度有限制,其最大值为1500字节,链路层的这个特性被称作MTU,即最大传输单元。除去20字节的IP首部和32字节的TCP首部后,就剩下1448字节的应用数据了。与此相关的另一个参数是MSS(Maximum Segment Size,最大分段大小),表示TCP数据包每次能够传输的最大数据分段。为了达到最佳传输效能,TCP协议在建立连接时通常要协商双方的MSS值,TCP在实现的时候这个值往往用MTU值代替(需要减去IP数据包首部的20字节和TCP数据段首部的20字节,即1460)。下图为本次实验的SYN包信息。
从抓包结果还可以看出,虽然发送端分255次发出数据,每次发送100字节,但接收端并不是接收到255包数据,而是只收到20包,各包大小有差异,多数为1448字节,但通信内容都能按顺序准确无误地到达接收端。这是为何?一方面,TCP为了提高传输效率,采用优化算法,发送方往往要收集到足够多的数据或者等待了足够长的时间后才将数据打成一包发出去;另一方面,如果IP层有一个数据报要传,且其长度比链路层的MTU还大,那么IP层就会把数据分成若干片,使每一片数据长度都小于MTU。
参考资料:《TCP/IP协议详解卷1:协议》