数据格式
序列号
确认应答号
数据偏移
即TCP首部的长度,首部长度=值*4
如果该字段的值为5,那说明从TCP包的最一开始到20字节为止都是TCP首部 ,余下的部分为TCP数据。
保留
一般为0,不为0也不丢弃数据。
控制位
窗口大小
校验和
见文末的Wireshark实例分析。
紧急指针
选项
类型2——MSS表示最大段长度
类型3——WS表示窗口比例,窗口大小只有2字节,最大只有64KB,对于现在的技术来说太小了。
所以实际窗口大小= 窗口大小 * 2^WS
例如窗口大小为8212 WS为8
那么实际窗口大小= 8212 * 256 = 2102272
类型4——SACK_PERM在SYN包中告诉对端自己支持SACK
类型5——SACK表示选择性重发,避免无用重发,提高网络吞吐量
类型8——时间戳在高速通信中,可以防止序列号回绕。两端必须都分别在SYN包和SYN|ACK包中开启时间戳选项,时间戳功能才能生效
Wireshark实例分析
通信过程示意
连接(三次握手)—> 通信 —> 断开(四次挥手)
客户端192.168.1.164:59160
向服务端192.168.1.193:8888
发起连接并发送字符串1234,由于长时间未通信,服务端进行主动断开。
连接
SYN
以SYN为例进行协议分析(蓝色部分为TCP协议部分)
源端口:e718->59160
目标端口:22b8->8888
序列号Seq:df15f6db->3742758619,发起连接时生成的随机值,Wireshark为了让我们方便分析,会从0开始,即采用一个相对值。
确认应答号Ack:00000000,除了第一包的SYN包不需要ACK标志,其余所有数据都必须携带ACK标志。ACK为下次对端应该发送的序列号,即ACK=上一次发送的Seq+Len
数据偏移(首部长):a->10,由于SYN包是不可以携带数据的,所以这整个包的长度就是10*4 = 40
保留:0
控制位:02->0000 0010 即 SYN包
窗口大小:faf0->64240
检验和:5b54,首先得知道校验和的概念,可参考我的上一篇文章《计算机网络之UDP数据格式(一)》
由校验和概念可知伪首部为(UDP的协议号为17,TCP的协议号为6)
C0A801A4 C0A801C1 00 06 0028
校验和为伪首部
、首部
(校验位补0)、数据
(长度不为偶数补0)每16位一组进行二进制反码求和
,再将求和结果求反码
。
附上Python计算校验和代码
pseudo_header =[0xC0A8, 0x01A4, 0xC0A8, 0x01C1, 0x0006, 0x0028]
tcp_data = [0xe718, 0x22b8, 0xdf15, 0xf6db, 0x0000, 0x0000, 0xa002, 0xfaf0,
0x5b54, 0x0000, 0x0204, 0x0402, 0x080a, 0x7eec, 0x0e56, 0x0000, 0x0000, 0x0103, 0x0307]
def checksum(data):
s = 0
for i in data:
s += i
s = (s & 0xffff) + (s >> 16) # 取前16位,然后将17位(进位的值加到第一位上)
return (~s & 0xffff)
print(hex(checksum(pseudo_header+tcp_data)))
紧急指针:0000
选项——MSS:0204 05b4,即MSS为1460(0x05b4)
选项——SACK_PERM:0402,表示支持SACK
选项——时间戳:080a 7eec0e56 00000000,即TSval=2129399382,TSecr=0
选项——填充:01
选项——WS:0303 07,即实际窗口大小=64240 * 2^7
SYN|ACK
这里的Seq为0也是相对的,实际值为1675384059
看到这里或许会有疑问,为什么SYN包没有数据,这里的Ack会为1呢?因为SYN包和FIN包虽然不携带数据,但是Ack还是会加1。
这里也是有SACK_PERM,说明双方都支持SACK。
这里没有时间戳选项,两端必须都分别在SYN包和SYN|ACK包中开启时间戳选项,时间戳功能才能生效。
ACK
这里的Seq为1,是因为上一包的Ack为1。
这里的Ack为1,是因为上一包数据有SYN标志,虽然Len=0,但是依然会算一个长度。
通信
PSH|ACK
发送1234
seq = 1 ack = 1
PSH表示发送的数据不进行缓存,立即传给上层应用
ACK
seq = 1 ack = 5
收到了4个字节数据,这里的ack就变成5了
断开
FIN|ACK
seq = 1 ack = 5
ACK
seq = 5 ack = 2
虽然上一包数据的Len为0,但是上一包数据有FIN标志,所以依然会算一个长度。
RST|ACK
在断开的过程中,收到了FIN包回复ACK后,可以不用立即发送FIN包,可以等数据处理完之后再发送FIN包。
这里是因为超时未收到对端的FIN包,所以发送RST包进行强行断开。