《Linux高性能服务器编程》读书笔记
为了理解TCP头部中每个字段的定义,从10.31.90.106执行telnet命令,并用tcpdump抓取这个过程中telnet客户端和telnet服务器程序之间交换的数据包。
一、执行tcpdump抓包
# tcpdump -ntx -i lo
二、在另一个终端输入telnet命令查询
# telnet 127.0.0.1
Trying 127.0.0.1...
telnet: connect to address 127.0.0.1: Connection refused
三、查看tcpdump抓到的包(开启了-x选项,使之输出数据包的二进制码)
# tcpdump -ntx -i lo
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes
IP 127.0.0.1.57777 > 127.0.0.1.telnet: Flags [S], seq 3611485706, win 32792, options [mss 16396,sackOK,TS val 1023622707 ecr 0,nop,wscale 7], length 0
0x0000: 4510 003c d839 4000 4006 6470 7f00 0001
0x0010: 7f00 0001 e1b1 0017 d742 e60a 0000 0000
0x0020: a002 8018 7540 0000 0204 400c 0402 080a
0x0030: 3d03 3e33 0000 0000 0103 0307
IP 127.0.0.1.telnet > 127.0.0.1.57777: Flags [R.], seq 0, ack 3611485707, win 0, length 0
0x0000: 4510 0028 0000 4000 4006 3cbe 7f00 0001
0x0010: 7f00 0001 0017 e1b1 0000 0000 d742 e60b
0x0020: 5014 0000 12b7 0000
四、分析TCP包头(数据包共包含60字节,其中前20字节是IP头部,后40字节是TCP头部,不包含应用程序数据(length值为0))
-
Flags [S]
,表示该TCP报文段包含SYN标志,因此它是一个同步报文段。(SYN ACK FIN RST PSH URG) -
seq 3611485706
,从127.0.0.1.57777 > 127.0.0.1.telnet这个方向上的第一个TCP报文段,所以这个序号值也就是此次通信过程中该传输方向的ISN值。 -
win 32792
,win是接收通告窗口的大小。因为这是一个同步报文段,所以win值反映的是实际的接收通告窗口大小。? -
options
,options是TCP选项,其具体内容列在方括号中。 -
mss 16396
,是发送端(客户端)通告的最大报文段长度。(通过ifconfig命令查看lo接口的MTU为16436字节,所以TCP报文段的MSS为16396(16436字节的MTU-20字节的IP头-20字节TCP头部)字节) -
sackOK
,表示发送端支持并同意使用SACK选项。 -
TS val 1023622707
,是发送端的时间戳 -
ecr 0
,是时间戳回显应答(因为这是一次TCP通信的第一个TCP报文段,所以它针对对方的时间戳的应答为0(尚未收到对方的时间戳) -
nop
,空操作选项 -
wscale 7
,发送端使用的窗口扩大因子为7 -
length 0
,表示数据部分长度为0字节 - TCP头部,从
0xe1b1
到0x0307
(前20字节是IP头部,0x4510 到 0x0001)
0x0010: 7f00 0001 e1b1 0017 d742 e60a 0000 0000
0x0020: a002 8018 7540 0000 0204 400c 0402 080a
0x0030: 3d03 3e33 0000 0000 0103 0307
十六进制数 | 二进制表示 | 十进制表示 | TCP头部信息 |
---|---|---|---|
0xe1b1 | 01110000110110000 | 57777 | 源端口号 |
0x0017 | 00010111 | 23 | 目的源口号 |
0xd742e60a | 3611485706 | 序号 | |
0x00000000 | 0 | 确认号 | |
0xa | 00000100 | 10 | TCP头部长度为10个32位(40字节) |
0x002 | 00000002 | 设置了SYN标志 | |
0x8018 | 32792 | 接收窗口大小 | |
0x7540 | 30016 | 头部校验和 | |
0x0000 | 没设置URG标志,所以紧急指针值无意义 | ||
0x0204 | 0x02-->2,0x04-->4 | 最大报文段长度选项的kind值和length值(kind=2,length=4) | |
0x400c | 16396 | 最大报文段长度(Maximum segment size) | |
0x0402 | 0x04-->4,0x02-->2 | 允许SACK选项(kind=4,length=2) | |
0x080a | 0x08-->8,0x0a-->10 | 时间戳选项的kind值和length值(kind=8,length=10) | |
0x3d033e33 | 1023622707 | 时间戳 | |
0x00000000 | 0 | 回显应答时间戳 | |
0x01 | 0x01-->1 | 空操作选项(nop,kind=1) | |
0x0303 | 0x03-->3,0x03-->3 | 窗口扩大因子选项的kind值和length值(kind=3,kind=3) | |
0x07 | 7 | 窗口扩大因子为7 |
对比下图,说明TCP头部结构
-
16位端口号(port number):告知主机该报文段是来自哪里(源端口)以及传给哪个上层协议或应用程序(目的端口)的。进行TCP通信时,客户端通常使用系统自动选择的临时端口号,而服务器则使用知名服务端口号。所有知名服务使用的端口号都定义在/etc/services文件中。
-
32位序号(sequence number):一次TCP通信(从TCP连接建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。假设主机A和主机B进行TCP通信,A发送给B的第一个TCP报文段中,序号值被系统初始化为某个随机值ISN(Initial Sequence Number,初始序号值)。那么在该传输方向上(从A到B),后续的TCP报文段中序号值将被系统设置成ISN加上该报文段所携带数据的第一个字节在整个字节流中的偏移。例如,某个TCP报文段传送的数据是字节流中的第1025~2048字节,那么该报文段的序号值就是ISN+1025。另外一个传输方向(从B到A)的TCP报文段的序号值也具有相同的含义。
-
32位确认号(acknowledgement number):用作对另一方发送来的TCP报文段的响应。其值是收到的TCP报文段的序号值加1。假设主机A和主机B进行TCP通信,那么A发送出的TCP报文段不仅携带自己的序号,而且包含对B发送来的TCP报文段的确认号。反之,B发送出的TCP报文段也同时携带自己的序号和对A发送来的报文段的确认号。
-
4位头部长度(header length):标识该TCP头部有多少个32bit字(4字节)。因为4位最大能表示15,所以TCP头部最长是60字节。
-
6位标志位包含如下几项:
- URG标志,表示紧急指针(urgent pointer)是否有效。
- ACK标志,表示确认号是否有效。我们称携带ACK标志的TCP报文段为确认报文段。
- PSH标志,提示接收端应用程序应该立即从TCP接收缓冲区中读走数据,为接收后续数据腾出空间(如果应用程序不将接收到的数据读走,它们就会一直停留在TCP接收缓冲区中)。
- RST标志,表示要求对方重新建立连接。我们称携带RST标志的TCP报文段为复位报文段。
- SYN标志,表示请求建立一个连接。我们称携带SYN标志的TCP报文段为同步报文段。
- FIN标志,表示通知对方本端要关闭连接了。我们称携带FIN标志的TCP报文段为结束报文段。
-
16位窗口大小(window size):是TCP流量控制的一个手段。这里说的窗口,指的是接收通告窗口(Receiver Window,RWND)。它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。
-
16位校验和(TCP checksum):由发送端填充,接收端对TCP报文段执行CRC算法以检验TCP报文段在传输过程中是否损坏。注意,这个校验不仅包括TCP头部,也包括数据部分。这也是TCP可靠传输的一个重要保障。
-
16位紧急指针(urgent pointer):是一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一字节的序号。因此,确切地说,这个字段是紧急指针相对当前序号的偏移,不妨称之为紧急偏移。TCP的紧急指针是发送端向接收端发送紧急数据的方法。我们将在后面讨论TCP紧急数据。
-
TCP头部的最后一个选项字段(options)是可变长的可选信息。这部分最多包含40字节,因为TCP头部最长是60字节(其中还包含前面讨论的20字节的固定部分)。典型的TCP头部选项结构如图所示。
选项的第一个字段kind说明选项的类型。有的TCP选项没有后面两个字段,仅包含1字节的kind字段。第二个字段length(如果有的话)指定该选项的总长度,该长度包括kind字段和length字段占据的2字节。第三个字段info(如果有的话)是选项的具体信息。常见的TCP选项有7种,如图所示。
-
kind=0是选项表结束选项。
-
kind=1是空操作(nop)选项,没有特殊含义,一般用于将TCP选项的总长度填充为4字节的整数倍。
-
kind=2是最大报文段长度选项。TCP连接初始化时,通信双方使用该选项来协商最大报文段长度(Max Segment Size,MSS)。TCP模块通常将MSS设置为(MTU-40)字节(减掉的这40字节包括20字节的TCP头部和20字节的IP头部)。这样携带TCP报文段的IP数据报的长度就不会超过MTU(假设TCP头部和IP头部都不包含选项字段,并且这也是一般情况),从而避免本机发生IP分片。对以太网而言,MSS值是1460(1500-40)字节。
-
kind=3是窗口扩大因子选项。TCP连接初始化时,通信双方使用该选项来协商接收通告窗口的扩大因子。在TCP的头部中,接收通告窗口大小是用16位表示的,故最大为65?535字节,但实际上TCP模块允许的接收通告窗口大小远不止这个数(为了提高TCP通信的吞吐量)。窗口扩大因子解决了这个问题。假设TCP头部中的接收通告窗口大小是N,窗口扩大因子(移位数)是M,那么TCP报文段的实际接收通告窗口大小是N乘2M,或者说N左移M位。注意,M的取值范围是0~14。我们可以通过修改/proc/sys/net/ipv4/tcp_window_scaling内核变量来启用或关闭窗口扩大因子选项。
和MSS选项一样,窗口扩大因子选项只能出现在同步报文段中,否则将被忽略。但同步报文段本身不执行窗口扩大操作,即同步报文段头部的接收通告窗口大小就是该TCP报文段的实际接收通告窗口大小。当连接建立好之后,每个数据传输方向的窗口扩大因子就固定不变了。关于窗口扩大因子选项的细节,可参考标准文档RFC 1323。 -
kind=4是选择性确认(Selective Acknowledgment,SACK)选项。TCP通信时,如果某个TCP报文段丢失,则TCP模块会重传最后被确认的TCP报文段后续的所有报文段,这样原先已经正确传输的TCP报文段也可能重复发送,从而降低了TCP性能。SACK技术正是为改善这种情况而产生的,它使TCP模块只重新发送丢失的TCP报文段,不用发送所有未被确认的TCP报文段。选择性确认选项用在连接初始化时,表示是否支持SACK技术。我们可以通过修改/proc/sys/net/ipv4/tcp_sack内核变量来启用或关闭选择性确认选项。
-
kind=5是SACK实际工作的选项。该选项的参数告诉发送方本端已经收到并缓存的不连续的数据块,从而让发送端可以据此检查并重发丢失的数据块。每个块边沿(edge of block)参数包含一个4字节的序号。其中块左边沿表示不连续块的第一个数据的序号,而块右边沿则表示不连续块的最后一个数据的序号的下一个序号。这样一对参数(块左边沿和块右边沿)之间的数据是没有收到的。因为一个块信息占用8字节,所以TCP头部选项中实际上最多可以包含4个这样的不连续数据块(考虑选项类型和长度占用的2字节)。
-
kind=8是时间戳选项。该选项提供了较为准确的计算通信双方之间的回路时间(Round Trip Time,RTT)的方法,从而为TCP流量控制提供重要信息。我们可以通过修改/proc/sys/net/ipv4/tcp_timestamps内核变量来启用或关闭时间戳选项。
网友评论