序号(seq)用来标识从TCP发端向TCP收端发送的数据字节流,它表示在这个报文段中的的第一个数据字节。如果将字节流看作在两个应用程序间的单向流动,则TCP用序号对每个字节进行计数。序号是32bit的无符号数,序号到达232-1后又从0开始。
当建立一个新的连接时,SYN标志变1。序号字段包含由这个主机选择的该连接的初始序号ISN(InitialSequenceNumber)。该主机要发送数据的第一个字节序号为这个ISN加1,因为SYN标志消耗了一个序号。既然每个传输的字节都被计数,确认序号包含发送确认的一端所期望收到的下一个序号。因此,确认序号(ack)应当是上次已成功收到数据字节序号加1。只有ACK标志(下面介绍)为1时确认序号字段才有效。
发送ACK无需任何代价,因为32bit的确认序号字段和ACK标志一样,总是TCP首部的一部分。因此,我们看到一旦一个连接建立起来,这个字段总是被设置,ACK标志也总是被设置为1。
TCP为应用层提供全双工服务。这意味数据能在两个方向上独立地进行传输。因此,连接的每一端必须保持每个方向上的传输数据序号。
TCP连接的建立是通过三次握手来实现的
序号 | 方向 | seq | ack | SYN | ACK |
---|---|---|---|---|---|
1 | A->B | 10000(ISN) | 0 | 1 | 0 |
2 | A<-B | 20000(ISN) | 10000+1=10001 | 1 | 1 |
3 | A->B | 10001 | 20000+1=20001 | 0 | 1 |
解释:
1:(A) –> [SYN] –> (B)
A向B发起连接请求,以一个随机数初始化A的seq,这里假设为10000,此时ACK=0
2:(A) <– [SYN/ACK] <–(B)
B收到A的连接请求后,也以一个随机数初始化B的seq,这里假设为20000,意思是:你的请求我已收到,我这方的数据流就从这个数开始。B的ACK是A的seq加1,即10000+1=10001
3:(A) –> [ACK] –> (B)
A收到B的回复后,它的seq是它的上个请求的seq加1,即10000+1=10001,意思也是:你的回复我收到了,我这方的数据流就从这个数开始。A此时的ACK是B的seq加1,即20000+1=20001
序号 | 方向 | seq | ack | 数据长度 | 数据包长度 |
---|---|---|---|---|---|
23 | A->B | 40000 |
70000 |
1460 |
1514 |
24 | A<-B |
70000 |
40000+1514-54=41460 |
0 |
54 |
25 | A->B | 41460 | 70000+54-54=70000 | 1460 | 1514 |
26 | A<-B | 70000 |
41460+1514-54=42920 |
0 |
54 |
解释:
23:B接收到A发来的seq=40000,ack=70000,size=1518的数据包
24:于是B向A也发一个数据包,告诉A,你的上个包我收到了。A的seq就以它收到的数据包的ack填充,ack是它收到的数据包的seq加上数据包的大小(不包括:以太网协议头=14字节,IP头=20字节,TCP头=20字节),以证实B发过来的数据全收到了。
25:A在收到B发过来的ack为41460的数据包时,一看到41460,正好是它的上个数据包的seq加上包的大小,就明白,上次发送的数据包已安全到达。于是它再发一个数据包给B。
26:B->A这个正在发送的数据包的seq也以它收到的数据包的ack填充,ack 就以它收到的数据包的seq(70000)加上包的size(54)填充,即ack=70000+54-54(全是头长,没数据项)。通过tcpdump发现确认包ack,确认传输过程中最后字节长度。
减去54的原因 ,以太网封装格式(链路层使用的是Ethernet II 格式,这个格式有14字节以太网首部+4字节以太网尾部):
应用数据=size-14-20-20=size-54。(假设IP首部和TCP首部都没有可选选项)
为什么不减去以太网尾部的4字节呢?
因为在物理层上网卡要先去掉前导同步码和帧开始定界符,然后对帧进行CRC检验,如果帧校验和错,就丢弃此帧。如果校验和正确,就判断帧的目的硬件地址是否符合自己的接收条件(目的地址是自己的物理硬件地址、广播地址、可接收的多播硬件地址等),如果符合,就将帧交“设备驱动程序”做进一步处 理。这时我们的抓包软件才能抓到数据,因此,抓包软件抓到的是去掉前导同步码、帧开始分界符、FCS之外的数据。
序号 | 方向 | seq | ack | FIN | ACK |
---|---|---|---|---|---|
1 | A->B | 80000 | 90000 | 1 | 1 |
2 | A<-B | 90000 | 80000+1=80001 | 0 | 1 |
3 | A<-B | 90000 | 80001 | 1 | 1 |
4 | A->B | 80001 | 90000+1=90001 | 0 | 1 |
1:(A) –> [FIN/ACK] –> (B)
客户端A没有要发送给服务端B的数据了,想要关闭链接,则发送一个FIN=1,ACK=1的包,告诉B可以关闭连接了,我没有什么数据要给你了。
2:(A) <– [ACK] <– (B)
然后B会发送ACK=1的包给A,告诉A我知道你没有什么想给我的了,但是我还有数据要给你,你先等下,我先不想FINISH呢。
3:(A) <– [FIN/ACK] <– (B)
等B把数据都发送给A之后,B会再次发送一个包,这次FIN=1,表示我这边也想关闭了,咱俩一起关把。在2和3之间,可能还会有很多B->A的传递,ack均为80001。
4:(A) –> [ACK] –> (B)
然后A回应一个ACK,表示我知道了,一起关吧。B收到这个ACK后,就会CLOSE。但是实际上A不会直接CLOSE,还会进入一个等待时间状态TIME_WAIT,持续2倍的MSL(Maximum Segment Lifetime,报文段在网络上能存活的最大时间)。过了这个状态,才会CLOSE。
1.保证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+BRST是TCP数据报中6个控制位之一,6个控制位的作用如下:
URG 紧急:当 URG=1 时,它告诉系统此报文中有紧急数据,应优先传送(比如紧急关闭),这要与紧急指针字段配合使用。
ACK 确认:仅当 ACK=1 时确认号字段才有效。建立 TCP 连接后,所有报文段都必须把 ACK 字段置为 1。
PSH 推送:若 TCP 连接的一端希望另一端立即响应,PSH 字段便可以“催促”对方,不再等到缓存区填满才发送。
RST复位:若 TCP 连接出现严重差错,RST 置为 1,断开 TCP 连接,再重新建立连接。
SYN 同步:用于建立和释放连接,稍后会详细介绍。
FIN 终止:用于释放连接,当 FIN=1,表明发送方已经发送完毕,要求释放 TCP 连接。
2.保证这次连接的重复数据段从网络中消失
如果A直接close了,然后向B发起了一个新的TCP连接,可能两个连接的端口号相同。一般不会有什么问题,但是如果旧的连接有一些数据堵塞了,没有达到B呢,新的握手连接就已经到B了,那么这时候,由于区分不同TCP连接是依据套接字,因此B会将这批迟到的数据认为是新的连接的数据,导致数据混乱(源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字,新旧连接的套接字很有可能相同)如果我们终止一个客户程序,并立即重新启动这个客户程序,则这个新客户程序将不能重用相同的本地端口。服务端处于被动关闭,不会出现该状态。
通常TCP在接收到数据时并不立即发送ACK;相反,它推迟发送,以便将ACK与需要沿该方向发送的数据一起发送(有时称这种现象为数据捎带ACK)。
挥手关闭过程中,处于半关闭状态,被动关闭状态传输的数据ack都是一致的。
类型 | 握手(SYN)或终止(FIN) | 传输 | 数据包为0 |
---|---|---|---|
seq(自己发送) | 上次发送 seq+1 | 上次发送seq+数据长度 | 上次发送seq |
ack(接收对方) | 上次接收 seq+1 | 上次接收seq+数据长度 | 上次接收seq |
4、
seq:上一次发送时为【1】,【1】中seq为0且为SYN数据包,所以这一次的seq为1(0增加1)。
ack:上次接收到时为【2】,【2】中seq为0,且为SYN数据包,所以可预计,server端下一次seq为1(0增加1)。
5、
seq:上一次发送时为【2】,【2】中seq为0,且为SYN数据包,所以这一次的seq为1(0增加1)。
ack:上一次接收时为【4】,【4】中的seq为1,数据包的长度为725,所以可以预计,下一次client端的seq为726(1+725)。
6、
seq:上一次发送时为【5】,【5】中seq为1,但【5】为ACK数据包,所以数据长度为0且不会驱使seq加1,所以这一次的seq为1(1+0)。
ack:上一次接收时为【4】,【4】中的seq为1,数据包的长度为725,所以可以预计,下一次client端的seq为726(1+725)。