TCP协议

2022-05-15  本文已影响0人  鱼蛮子9527

TCP 协议是网络通信中最核心的协议之一,位于七层网络协议的第四层:传输层。

TCP 数据包结构

TCP数据包

整个 TCP 数据包的结构如上图所示,是一个嵌套结构,从外到内依次是以太网数据,IP数据,TCP数据。

以太网的数据包也叫帧是网络传输的基本单元,大小是固定的,最初是最大 1518 字节(18 字节标头 + 1500 字节数据),后来在以太网标头增加 4 字节可选标识变为最大 1522 字节。

IP 数据数据包则在以太网数据包里面,有自己的头信息至少 20 字节,所以IP数据包的数据大小为 1480 字节。

TCP 数据在IP数据包里面,一般有 20~60 字节的头信息,所以 TCP 数据包的大小一般为 1460~1420 字节。

TCP 头部信息

TCP头部

TCP链接建立和断开

TCP 使用三次握手过程建立连接,使用四次挥手过程断开连接。

三次握手

三次握手

TCP 用三次握手(或称三路握手,three-way handshake)过程创建一个连接。在连接创建过程中,很多参数要被初始化,例如序号被初始化以保证按序传输和连接的强壮性。

TCP 连接的正常建立

通常是由一端(服务器端)打开一个套接字(socket)然后监听来自另一方(客户端)的连接,这就是通常所指的被动打开(passive open)。服务器端被被动打开以后,客户端就能开始创建主动打开(active open)。

三次握手协议的过程

  1. 客户端向服务器端发送一个 SYN 包,请求一个主动打开。该包携带客户端为这个连接请求而设定的随机数 A 作为消息序号(Sequence Number)。客户端的状态变成: SYN_SEND。
  2. 服务器端收到一个合法的 SYN 包后,把该包放入 SYN 队列中;回送一个 SYN/ACK。ACK 的确认码应为 A+1,SYN/ACK 包本身携带一个随机产生的序号 B。服务端状态变成 SYN_RECEVIED。
  3. 客户端收到 SYN/ACK 包后,发送一个 ACK 包,该包的序号被设定为 A+1,而 ACK 的确认码则为B+1。客户端状态变成 ESTABLISHED,服务端收到确认后也变成 ESTABLISHED。
趣图

看到有位博主的图片非常有意思,用来比喻三次握手非常形象,哈哈。

四次挥手

四次挥手

TCP 连接终止使用四次挥手过程(或称四次握手,four-way handshake),在这个过程中连接的每一侧都独立地被终止。当一个端点要停止它这一侧的连接,就向对侧发送 FIN,对侧回复 ACK 表示确认。拆掉一侧的连接过程需要一对 FIN 和 ACK,分别由两侧端点发出。终止链接可以由双方任意方发起,下面只是按照客户端方发起描述。

四次挥手协议过程

  1. 客户端向服务器端发送一个 FIN 包,请求关闭连接,SEQ=A。客户端的状态变成 FIN1_WAIT。
  2. 服务器端收到后返回 ACK=A+1,服务端状态变成 CLOSE_WAIT,客户端状态变为 FIN_WAIT2。
  3. 服务端向客户端器端发送一个 FIN 包,请求关闭连接,SEQ=B。客户端的状态变成 LAST_ACK。
  4. 客户器端收到后返回 ACK=B+1。服务端状态变成 CLOSE,客户端状态变为 TIME_WAIT。

发出 FIN 的一侧,如果给对侧的 FIN 响应了 ACK(TIME_WAIT),那么就会等待 2*MSL 时间,然后关闭连接。在这段超时等待时间内,本地的端口不能被新连接使用(避免延时到达的包与随后的新连接混淆)。参数 tcp_max_tw_buckets 控制并发的 TIME_WAIT 的数量,默认值是 180000,如果超限,系统会把多的 TIME_WAIT 状态的连接清理掉。

PS:用 wireshark 之类分析 TCP 数据包的时候会发现四次挥手过程往往只有 3 个数据包,这是因为 TCP 规定 ACK 可以捎带在其他数据包当中,所以你看到的主动断开连接一方本应收到的 ACK,是被对方的 FIN 包捎带过来的,就变成了三个包了。但是如果当被动方还有数据未发送完的时候就会有如图所示的四次交互了,也是因为这样才不能称为三次挥手。

ACK 和滑动窗口

可靠传输 ACK

通常在每个 TCP 报文段中都有一对序号和确认号。TCP 报文发送者称自己的字节流的编号为序号(Sequence Number),称接收到对方的字节流编号为确认号(Acknowledgement Number)。
发送的时候,TCP 协议为每个包编号(Sequence Number),以便接收的一方按照顺序还原。万一发生丢包,也可以知道丢失的是哪一个包。

第一个包的编号是一个随机数。为了便于理解,这里就把它称为 1 号包。假定这个包的负载长度是 100 字节,那么可以推算出下一个包的编号应该是 101(握手、挥手阶段是+1)。这就是说,每个数据包都可以得到两个编号:自身的编号,以及下一个包的编号。接收方由此知道,应该按照什么顺序将它们还原成原始文件。

TCP 报文的接收者为了确保可靠性,在接收到一定数量的连续字节流后发送确认。这是对 TCP 的一种扩展,称为选择确认(Selective Acknowledgement),ACK 的值=收到包序号+数据长度。选择确认使得 TCP 接收者可以对乱序到达的数据块进行确认。

发送窗口

接收方在“窗口大小”域指出还可接收的字节数量。发送方在没有新的确认包的情况下至多发送“窗口大小”允许的字节数量。接收方可修改“窗口大小”的值。

通过 ACK 以及发送窗口进行传输数据过程如下图所示。

每个 ACK 都带有下一个数据包的编号,接收窗口的剩余容量。双方都会发送 ACK)

滑动窗口

TCP 以 1 个段为单位,每发送一个段进行一次确认应答的处理。这样的传输方式有一个缺点,就是包的往返时间越长通信性能就越低。

为解决这个问题,TCP 引入了窗口这个概念。确认应答不再是以每个分段,而是以更大的单位进行确认,转发时间将会被大幅地缩短。也就是说,发送端主机,在发送了一个段以后不必要一直等待确认应答,而是继续发送,类似于并行发送。如下图所示:

并行发送

在接收到所有的 ACK 之后(其实并不是每一个报文段都会回复 ACK,可能会对两个报文段发送一个 ACK,也可能会对多个报文段发送 1 个 ACK),窗口就会指向下一个要发送的数据区间,直至数据发送完毕。其实即使这样传输数据,在传输大量数据,如上传大文件的时候,如果客户端与服务端的网络延迟较高,还是会产生较大的耗时,因为即使并行传输,但并行的量是受限的,并行批次之间的网络延迟还是无法忽略的。

滑动窗口

滑动窗口文字描述起来还是很费劲,可以看B站的动画,非常形象。

慢启动

服务器发送数据包,当然越快越好,最好一次性全发出去。但是,发得太快,就有可能丢包。带宽小、路由器过热、缓存溢出等许多因素都会导致丢包。线路不好的话,发得越快,丢得越多。

最理想的状态是,在线路允许的情况下,达到最高速率。但是我们怎么知道,对方线路的理想速率是多少呢?答案就是慢慢试。

TCP 协议为了做到效率与可靠性的统一,设计了一个慢启动(slow start)机制。开始的时候,发送得较慢,然后根据丢包的情况,调整速率:如果不丢包,就加快发送速度;如果丢包,就降低发送速度。
Linux 内核里面设定了(常量 TCP_INIT_CWND),刚开始通信的时候,发送方一次性发送 10个 数据包,即"发送窗口"的大小为 10。然后停下来,等待接收方的确认,再继续发送。

发送方通过 ACK 中的“Ack Num”及“窗口大小”,再加上自己已经发出的数据包的最新编号,就会推测出接收方大概的接收速度,从而降低或增加发送速率。

数据包的遗失处理

每一个数据包都带有下一个数据包的编号。如果下一个数据包没有收到,那么 ACK 的编号就不会发生变化。

举例来说,现在收到了 4 号包,但是没有收到5号包。ACK 就会记录,期待收到 5 号包。过了一段时间,5 号包收到了,那么下一轮 ACK 会更新编号。如果 5 号包还是没收到,但是收到了 6 号包或 7 号包,那么 ACK 里面的编号不会变化,总是显示 5 号包。这会导致大量重复内容的 ACK。

如果发送方发现收到三个连续的重复 ACK 或者超时了还没有收到任何 ACK,就会确认丢包,即 5 号包遗失了,从而再次发送这个包。通过这种机制,TCP 保证了不会有数据包丢失。

重传机制

其他

TCP Dump

用于抓包分析,可以参照tcpdump详细教程,或者使用命令 man tcpdump 进行查看。

常用如:sudo tcpdump -nn net 100.83 -XX -w my.cap

实例分析

抓包

这是通过 wireshark 打开的 TCP dump 文件中的一次完整请求过程。

三次握手过程

syn数据包

首先看第一个是数据包是客户端发送链接请求 Syn=1,Seq=0

服务端ack数据包

服务的收到请求后,发送 SYN+ACK,Ack=1,Syn=1,Seq=0

客户端ack数据包

客户端收到 SYN+ACK 后返回 ACK,Seq=1,Ack=1。到这里三次握手过程就已经建立完成。

数据传输

客户端数据包

首先是客户端发送请求数据,Seq=1,Next Seq=716(Seq + TCP payload)。

服务端ack

服务的收到请求后的 ACK,Seq=1, Ack = 716。

后面的两个数据包就是服务端向客户端发送的结果数据,与客户端发起请求过程一致,只不过是由服务端发起。

四次挥手过程

客户端fin

客户端发送断开连接请求,Fin=1,Seq=716

服务端ack

服务端发送 ACK+FIN,Fin=1,Ack=717,Seq=1206

客户端ack

客户端发送 ACK,Ack=1207,这样两边的连接就都会断开了。

状态编码

下表为 TCP 状态码列表,以 S 指代服务器,C 指代客户端,S&C 表示两者,S/C 表示两者之一:

状态 说明
LISTEN S 服务器等待从任意远程 TCP 端口的连接请求。侦听状态。
SYN-SENT C 客户在发送连接请求后等待匹配的连接请求。通过 connect() 函数向服务器发出一个同步(SYNC)信号后进入此状态。
SYN-RECEIVED S 服务器已经收到并发送同步(SYNC)信号之后等待确认(ACK)请求。
ESTABLISHED S&C 服务器与客户的连接已经打开,收到的数据可以发送给用户。数据传输步骤的正常情况。此时连接两端是平等的。这称作全连接。
FIN-WAIT-1 S&C (服务器或客户)主动关闭端调用 close() 函数发出 FIN 请求包,表示本方的数据发送全部结束,等待 TCP 连接另一端的 ACK 确认包或 FIN&ACK 请求包。
FIN-WAIT-2 S&C 主动关闭端在 FIN-WAIT-1 状态下收到 ACK 确认包,进入等待远程 TCP 的连接终止请求的半关闭状态。这时可以接收数据,但不再发送数据。
CLOSE-WAIT S&C 被动关闭端接到FIN后,就发出 ACK 以回应 FIN 请求,并进入等待本地用户的连接终止请求的半关闭状态。这时可以发送数据,但不再接收数据。
CLOSING S&C 在发出 FIN 后,又收到对方发来的 FIN 后,进入等待对方对己方的连接终止(FIN)的确认(ACK)的状态。少见。
LAST-ACK S&C 被动关闭端全部数据发送完成之后,向主动关闭端发送 FIN,进入等待确认包的状态。
TIME-WAIT S/C 主动关闭端接收到 FIN 后,就发送 ACK 包,等待足够时间以确保被动关闭端收到了终止请求的确认包。【按照 RFC 793,一个连接可以在 TIME-WAIT 保证最大四分钟,即最大分段寿命(maximum segment lifetime)的 2 倍】
CLOSED S&C 完全没有连接。

维基百科
Tcp的三次握手和四次挥手
为什么tcp 连接断开只有3个包?
TCP 协议简介
太厉害了,终于有人能把TCP/IP 协议讲的明明白白了

上一篇 下一篇

猜你喜欢

热点阅读