0%

TCP的报文格式与连接

TCP是一个面向连接的、提供可靠传输的、全双工通信、面向字节流的传输层协议。本文先讲述TCP报文格式以及TCP连接的过程。

TCP报文段首部格式

TCP首部格式

  • 源端口(2): 数据发送方的端口号。

  • 目的端口(2): 数据接受方的端口号。

  • 序号(4):seq序号,记录本报文段所发送的第一个字节的序号。

    例如:一报文段字段值是301,携带数据共有100字节,因为在TCP连接中传送的字节流中的每一个字节都按顺序编号,所以本报文段的第一个字节的序号是301,最后一个字节的序号是400,那么下一个报文段应当从401开始

  • 确认号(4):ack序号,即接收端希望收到的下一个数据报文中的第一个字节的序号。

    ​ 只有ACK标志位为1时,确认序号字段才有效,ack=seq+1。(上面例子里确认号=401)

  • 数据偏移(4位):表示TCP报文段的数据起始处距离TCP报文段的起始处有多远(即TCP报文段的首部长度

    数据偏移的单位是32bit,即4字节;因为4位二进制能够表是的最大十进制数是15,所以数据偏移的最大长度是15*4=60字节,除去固定部分的20字节,选项字段最大不能超过40字节

  • 保留(6位)

  • 紧急比特URG(1位):当值为1时表示次报文段中有需要紧急处理。

  • 确认比特ACK(1位):确认序号是否有效,为1时有效,为0时无效。TCP规定连接建立后传送报文段ACK=1

  • 推送比特PSH(1位):接收方应该尽快将这个报文交给应用层。

  • 复位比特RST(1位):重置连接,值为1时表示TCP连接存在严重的错误,需要重新进行连接。

  • 同步比特SYN(1位):建立连接,在连接建立时同步序号,SYN=1时表示这是一个连接请求或连接接受报文

    当SYN=1且ACK=0时,表明这时一个连接请求报文段

    当SYN=1且ACK=1时,表明对方同意建立连接

    所以SYN=1时表示这是一个连接请求或连接接受报文

  • 终止比特FIN(1位): 用于释放连接,值为1表示要发送的数据报已经发送完毕,需要释放传送连接。

  • 窗口字段(2):接收端根据缓存空间的大小确定自己接受窗口的大小,限制发送放的窗口上限

  • 检验和:用来检验首部和数据两部分的正确性。

  • 紧急指针字段:本报文紧急报文的最后一个字节的序号。

需要注意的是:不要将确认序号ack与标志位中的ACK搞混了,确认方ack=发起方seq+1,两端配对。

TCP的连接

每一条TCP连接有两个端点,端点叫socket(套接字)

socket是一个 五元组,包括:源IP、源端口、目的IP、目的端口、类型:TCP or UDP

这个五元组,即标识了一条可用的连接。例如,本地IP是180.172.35.150,在浏览器中连接某一个Web服务器,例如百度,这条socket连接的五元组可能就是:

[180.172.35.150:45678, tcp, 180.97.33.108:80]

源IP为你的出口IP地址 180.172.35.150,源端口为随机端口 45678,目的IP为百度的某一个负载均衡服务器IP 180.97.33.108,端口为HTTP标准的80端口

三次握手

三次握手的过程:

三次握手

刚开始客户端处于 closed 的状态,服务端处于 listen 状态。然后

1、第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号seq=X(seq利用ISN生成)。此时客户端处于 SYN_Send (同步已发送) 状态。

2、第二次握手:服务器收到客户端的 SYN 报文后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号seq=Y,同时会把客户端的seq + 1 作为 Ack (确认序号)的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD(同步收到)的状态。

3、第三次握手:客户端收到服务器 SYN 报文之后,会发送一个 ACK应答 报文,也是一样把服务器的seq + 1 作为 Ack 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 establised 状态。(此时客户端可以通过ACK报文段发送数据,但如果不发送数据则不消耗序号)

4、服务器收到 ACK 报文之后,也处于 establised 状态,此时,双方已建立连接

三次握手的作用

1、确认双方的接受、发送能力是否正常,是否可以传送数据。

2、同步双方的初始化序列号,为后面的可靠传送做准备。

3、协商窗口大小,同时接收方预留数据缓存区,为后面的可靠传送做准备。

(ISN)是固定的吗

三次握手的一个重要功能是客户端和服务端交换ISN(Initial Sequence Number), 以便让对方知道接下来接收数据的时候如何按序列号组装数据。如果ISN是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。

1
2
3
ISN = M + F(localhost, localport, remotehost, remoteport)
M是一个计时器,每隔4毫秒加1。
F是一个Hash算法,根据源IP、目的IP、源端口、目的端口生成一个随机数值。所以要保证hash算法不能被外部轻易推算得出。

ISN是随机的,所以序列号容易就会超过2^31-1,就出现了“tcp序列号回绕”问题 参考博文

四次挥手

由于socket是全双工的工作模式,一个socket的关闭,是需要四次握手来完成的。

四次挥手的过程:

四次挥手

刚开始双方都处于 establised 状态,假如是客户端主动发起关闭请求(调用close()方法),则:

1、第一次挥手:客户端发送一个 FIN 终止报文给服务器,报文中指定一个初始化序列号seq=U。此时客户端处于FIN_WAIT1状态。

2、第二次挥手:服务端收到 FIN 之后,因为还有数据未发送完,所以服务器会先发送一个 ACK 应答报文,指定一个初始化序列号seq=V,且把客户端的序列号值seq + 1 作为确认序号的值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态。客户端等待服务器关闭,则进入FIN_WAIT_2状态;此时,客户端 等待 服务器 发起关闭请求

3、第三次挥手:服务器在完成所有数据发送后,主动发起关闭请求,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个初始化序列号seq=W。此时服务端处于 LAST_ACK 的状态。

4、第四次挥手:客户端收到 FIN 之后,发送一个 ACK 报文作为应答,且把服务端的序列号值 + 1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。

5、服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。等待2MSL(最长报文段寿命),主动关闭的一方,结束TIME-WAIT,进入CLOSED状态

四次挥手的作用:确保数据能够完成传输,而不是发送FIN报文后对方立即关闭连接

参考博文:关于3次握手与4次挥手

TIME_WAIT的作用或2MSL的作用

  1. 防止前一个连接上延迟的数据包或丢失重传的数据包,被后面复用的连接错误接收(异常:数据丢失或传输慢)

    imgq

    • SEQ=3的数据包丢失,重传第一次,没有得到ACK确认

    • 如果没有TIME_WAIT,或TIME_WAIT时间非常短,那么关闭的连接马上被重用,并连续发送SEQ=1,2 的数据包

      【180.172.35.150:45678, tcp, 180.97.33.108:80 的状态变为了CLOSED,源端口可被再次利用】【对180.97.33.108:80新建的连接,复用了之前的随机端口45678】

    • 此时,前面的连接上的SEQ=3的数据包再次重传,同时,seq的序号刚好也是3(这个很重要,不然,SEQ的序号对不上,就会RST掉),此时,前面一个连接上的数据被后面的一个连接错误的接收

    • 利用2MSL(最长报文段寿命)的等待时间,就可以**使本连接持续的时间内所产生的所有报文段都从网络中消失

  2. 确保连接方能在时间范围内,关闭自己的连接(也是因为丢包导致的),即为了保证主动关闭方发送的最后一个ACK报文能够到达被动关闭方。

    img

  3. 总的来说就是为了解决网络的丢包和网络不稳定所带来的其他问题

参考博文:你所不知道的TIME_WAIT