tcp协议
在TCP(Transmission Control Protocol, 传输控制协议)层,有个FLAGS字段,这个字段有以下几个标识:SYN
, FIN
, ACK
, PSH
, RST
, URG
。其含义为:
SYN
synchronous 建立连接 用于建立和释放连接,稍后会详细介绍。ACK
acknowledgement 确认 仅当 ACK=1 时确认号字段才有效。建立 TCP 连接后,所有报文段都必须把 ACK 字段置为 1。PSH
push 传送 若 TCP 连接的一端希望另一端立即响应,PSH 字段便可以“催促”对方,不再等到缓存区填满才发送。FIN
finish 结束 用于释放连接,当 FIN=1,表明发送方已经发送完毕,要求释放 TCP 连接。RST
reset 重置 若 TCP 连接出现严重差错,RST 置为 1,断开 TCP 连接,再重新建立连接。URG
urgent 紧急 当 URG=1 时,它告诉系统此报文中有紧急数据,应优先传送(比如紧急关闭),这要与紧急指针字段配合使用。Sequence number
顺序号码Acknowledge number
确认号码
其中,ACK
(确认)是可能与SYN
(建立连接),FIN
(结束)等同时使用的,比如SYN
和ACK
可能同时为1
,它表示的就是建立连接之后的响应,如果只是单个的一个SYN
,它表示的只是建立连接。TCP的几次握手就是通过这样的ACK
表现出来的。但SYN
与FIN
是不会同时为1
的,因为前者表示的是建立连接,而后者表示的是断开连接。RST
一般是在FIN
之后才会出现为1
的情况,表示的是连接重置。一般地,当出现FIN
包或RST
包时,我们便认为客户端与服务器端断开了连接;而当出现SYN
和SYN+ACK
包时,我们认为客户端与服务器建立了一个连接。
PSH
为1的情况,一般只出现在 DATA内容不为 0
的包中,也就是说PSH
为 1
表示的是有真正的TCP数据包内容被传递。 TCP的连接建立和连接关闭,都是通过请求-响应的模式完成的。
seq
和 ack
号存在于TCP报文段的首部中, seq
是序号, ack
是确认号,大小均为4字节(注意与大写的ACK
不同,ACK
是6个控制位之一,大小只有一位, 仅当 ACK=1
时ack
字段才有效。建立 TCP 连接后,所有报文段都必须把 ACK
字段置为 1
。)
seq:占 4 字节,序号范围[0,2^32-1]
,序号增加到 2^32-1
后,下个序号又回到 0
。TCP 是面向字节流的,通过 TCP 传送的字节流中的每个字节都按顺序编号,而报头中的序号字段值则指的是本报文段数据的第一个字节的序号。
ack:占 4 字节,期望收到对方下个报文段的第一个数据字节的序号。
三次握手
一个TCP连接的建立是通过三次握手来实现的
(A) –> [SYN] –> (B)
假如服务器B和客户机A通讯. 当A要和B通信时,A首先向B发一个SYN
(Synchronize) 标记的包,告诉B请求建立连接.
注意: 一个 SYN
包就是仅SYN
标记设为 1
的TCP包(参见TCP包头Resources). 认识到这点很重要,只有当B收到A发来的 SYN
包,才可建立连接,除此之外别无他法。因此,如果你的防火墙丢弃所有的发往外网接口的 SYN
包,那么你将不能主动连接外部任何主机,除非不是TCP协议。
(A) <– [SYN/ACK] <–(B)
接着,B收到后会发一个对SYN
包的确认包(SYN/ACK
)回去,表示对第一个SYN
包的确认,并继续握手操作.
注意: SYN/ACK
包是仅 SYN
和 ACK
标记为 1
的包.
(A) –> [ACK] –> (B)
A收到 SYN/ACK
包,A发一个确认包(ACK
),通知B连接已建立。至此,三次握手完成,一个TCP连接完成
Note: ACK
包就是仅 ACK
标记设为1
的TCP包. 需要注意的是当三此握手完成、连接建立以后,TCP连接的每个包都会设置ACK位
示例
握手阶段:
序号 | 方向 | seq | ack | SYN | ACK |
---|---|---|---|---|---|
1 | A->B | 10000 | 0 | 1 | 0 |
2 | B->A | 20000 | 10000+1=10001 | 1 | 1 |
3 | A->B | 10001 | 20000+1=20001 | 0 | 1 |
解释:
-
A向B发起连接请求,以一个
随机数
初始化A的seq
,这里假设为10000
,此时ACK=0
-
B收到A的连接请求后,也以一个
随机数
初始化B的seq
,这里假设为20000
,意思是:你的请求我已收到,我这方的数据流就从这个数开始。B的ACK
是A的seq
加1,即10000+1=10001
-
A收到B的回复后,它的
seq
是它的上个请求的seq加1,即10000+1=10001
,意思也是:你的回复我收到了,我这方的数据流就从这个数开始。A此时的ACK
是B的seq加1,即20000+1=20001
数据传输过程
序号 | 方向 | seq | ack | size |
---|---|---|---|---|
23 | A->B | 40000 | 70000 | 1514 |
24 | B->A | 70000 | 40000+1514-54=41460 | 54 |
25 | A->B | 41460 | 70000+54-54=70000 | 1514 |
26 | B->A | 70000 | 41460+1514-54=42920 | 54 |
解释:
-
B接收到A发来的
seq=40000
,ack=70000
,size=1518
的数据包 -
于是B向A也发一个数据包,告诉A,你的上个包我收到了。A的
seq
就以它收到的数据包的ack
填充,ack
是它收到的数据包的seq
加上数据包的大小(不包括:以太网协议头=14字节,IP头=20字节,TCP头=20字节),以证实B发过来的数据全收到了。 -
A在收到B发过来的
ack
为41460
的数据包时,一看到41460
,正好是它的上个数据包的seq
加上包的大小,就明白,上次发送的数据包已安全到达。于是它再发一个数据包给B。这个正在发送的数据包的seq
也以它收到的数据包的ack
填充,ack
就以它收到的数据包的seq(70000)
加上包的size(54)
填充,即ack=70000+54-54
(全是头长,没数据项)。
减去54的原因见下图(链路层使用的是Ethernet II 格式,这个格式有14字节以太网首部+4字节以太网尾部):
应用数据=size-14-20-20=size-54
。(假设IP首部和TCP首部都没有可选选项)
为什么不减去以太网尾部的4字节呢?因为在物理层上网卡要先去掉前导同步码和帧开始定界符,然后对帧进行CRC检验,如果帧校验和错,就丢弃此帧。如果校验和正确,就判断帧的目 的硬件地址是否符合自己的接收条件(目的地址是自己的物理硬件地址、广播地址、可接收的多播硬件地址等),如果符合,就将帧交“设备驱动程序”做进一步处 理。这时我们的抓包软件才能抓到数据,因此,抓包软件抓到的是去掉前导同步码、帧开始分界符、FCS之外的数据,
四次挥手
TCP连接的结束是四次挥手的过程,ACK
一直等于1
序号 | 方向 | seq | ack | FIN | ACK |
---|---|---|---|---|---|
1 | A->B | 80000 | 90000 | 1 | 1 |
2 | B->A | 90000 | 80000+1=80001 | 0 | 1 |
3 | B->A | 95000 | 80001 | 1 | 1 |
4 | A->B | 80001 | 95000+1=95001 | 0 | 1 |
-
(A) –> [FIN/ACK] –> (B) 客户端A没有要发送给服务端B的数据了,想要关闭链接,则发送一个
FIN=1,ACK=1
的包,告诉B可以关闭连接了,我没有什么数据要给你了。 - (A) <– [ACK] <– (B) 然后B会发送ACK=1的包给A,告诉A我知道你没有什么想给我的了,但是我还有数据要给你,你先等下,我先不想FINISH呢。
- (A) <– [FIN/ACK] <– (B)
等B把数据都发送给A之后,B会再次发送一个包,这次
FIN=1
,表示我这边也想关闭了,咱俩一起关把。在2和3之间,可能还会有很多B->A
的传递,ack均为80001
。 - (A) –> [ACK] –> (B)
然后A回应一个
ACK
,表示我知道了,一起关吧。B收到这个ACK
后,就会CLOSE
。但是实际上A不会直接CLOSE
,还会进入一个等待时间状态TIME_WAIT
,持续2倍的MSL(Maximum Segment Lifetime,报文段在网络上能存活的最大时间)。过了这个状态,才会CLOSE
。为什么要等待一段时间?原因有二:-
保证TCP的全双工连接能够可靠关闭
假如A发送的最后一次
ACK
丢包了,没有被B收到,那B超时之后,会再次发送一个FIN
包,然后这个包被处于TIME_WAIT
状态的A收到,A会再次发送一个ACK
包,并重新开始计时,一直循环这个过程,直到A在TIME_WAIT
的整个过程中都没有收到B发过来的FIN
包,这说明B已经收到了A的ACK
包并CLOSE
了,因此A这时候才可以安心CLOSE
。如果A没有TIME_WAIT
状态而是直接close
,那么当ACK
丢包之后,B会再次发送一个FIN
包,但是这个包不会被A回应,因此B最终会收到RST
,误以为是连接错误,不符合可靠连接的要求,因此需要等待ACK
报文到达B。 -
保证这次连接的重复数据段从网络中消失
如果A直接close了,然后向B发起了一个新的TCP连接,可能两个连接的端口号相同。一般不会有什么问题,但是如果旧的连接有一些数据堵塞了,没有达到B呢,新的握手连接就已经到B了,那么这时候,由于区分不同TCP连接是依据套接字,因此B会将这批迟到的数据认为是新的连接的数据,导致数据混乱(源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字,新旧连接的套接字很有可能相同)
-
总结
确认号ack
在握手和结束时确认号应该是对方序列号加1,传输数据时则是对方序列号加上对方携带应用层数据的长度,如果对方携带应用层数据长度为0,则ack
与对方序列号相同,不要+1,比如25的ack
与24的seq
相同。也可以这样理解,因为24没有发送数据,所以A期待B下次发送过来的第一个字节的序号不变,因此25的ack
与23的ack
相同。
序列号seq
在握手和结束时序列号应该是上次序列号+1,传输数据时则是上次的序列号加上上次应用层数据发送长度,如果数据长度为0,则seq
与上次一样,不要+1,比如26的seq
和24的seq
相同。