TCP(Transmission Control Protocol)提供可靠的、面向连接的传输服务。​

TCP协议的可靠性
  • 应用程序分割为TCP认为最合适发送的数据块。由TCP传递给IP的信息单位叫做报文段。
  • 当TCP发出一个报文段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能记时收到一个确认,它 就重发这个报文段。
  • 当TCP收到发自TCP连接另一端的数据,它将发送一个确认。这个确认不是立即发送,通常延迟几分之一秒。
  • TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化如果收到报文段的检验和有差错,TCP将丢弃这个报文段和不确认收到这个报文段。
  • 既然TCP报文段作为IP数据报来传输,而IP数据报的到达可能失序,因此TCP报文段的到达也可能失序。如果必要,TCP将对收到的数据进行排序,将收到的数据以正确的顺序交给应用层。
  • 既然IP数据报会发生重复,TCP连接端必须丢弃重复的数据。
  • TCP还能提供流量控制,TCP连接的每一方都有固定大小的缓冲空间。TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲区溢出。
TCP头部格式

说明
  • 每个TCP段都包括源端和目的端的端口号,用于寻找发送端和接收端的应用进程。这两个值加上IP首部的源端IP地址和目的端IP地址唯一确定一个TCP连接。
  • 序号用来标识从TCP发送端向接收端发送的数据字节流,它表示在这个报文段中的第一个数据字节。如果将字节流看作在两个应用程序间的单向流动,则TCP用序号对每个字节进行计数。
  • 当建立一个新连接时,SYN标志变1。序号字段包含由这个主机选择的该连接的初始序号ISN,该主机要发送数据的第一个字节的序号为这个ISN加1,因为SYN标志使用了一个序号。
  • 既然每个被传输的字节都被计数,确认序号包含发送确认的一端所期望收到的下一个序号。因此,确认序号应当时上次已成功收到数据字节序号加1。只有ACK标志为1时确认序号字段才有效。
  • 发送ACK无需任何代价,因为32位的确认序号字段和ACK标志一样,总是TCP首部的一部分。因此一旦一个连接建立起来,这个字段总是被设置,ACK标志也总是被设置为1。
  • TCP为应用层提供全双工的服务。因此,连接的每一端必须保持每个方向上的传输数据序号。
  • TCP可以表述为一个没有选择确认或否认的华东窗口协议。因此TCP首部中的确认序号表示发送方已成功收到字节,但还不包含确认序号所指的字节。当前还无法对数据流中选定的部分进行确认。
  • 首部长度需要设置,因为任选字段的长度是可变的。TCP首部最多60个字节。
  • 6个标志位中的多个可同时设置为1
    • URG-紧急指针有效
    • ACK-确认序号有效
    • PSH-接收方应尽快将这个报文段交给应用层
    • RST-重建连接
    • SYN-同步序号用来发起一个连接
    • FIN-发送端完成发送任务
  • TCP的流量控制由连接的每一端通过声明的窗口大小来提供。窗口大小为字节数,起始于确认序号字段指明的值,这个值是接收端期望接收的字节数。窗口大小是一个16为的字段,因而窗口大小最大为65535字节
  • 检验和覆盖整个TCP报文端:TCP首部和TCP数据。这是一个强制性的字段,一定是由发送端计算和存储,并由接收端进行验证。TCP检验和的计算和UDP首部检验和的计算一样,也使用伪首部;
  • 紧急指针是一个正的偏移量,黄蓉序号字段中的值相加表示紧急数据最后一个字节的序号。TCP的紧急方式是发送端向另一端发送紧急数据的一种方式。
  • 最常见的可选字段是最长报文大小MMS,每个连接方通常都在通信的第一个报文段中指明这个选项。它指明本端所能接收的最大长度的报文段。
TCP标志位
#define TH_FIN  0x01    // 结束连接
​#define TH_SYN  0x02    // 同步序号
​#define TH_RST  0x04    // 复位连接​
#define TH_PSH  0x08    // 推送数据​
#define TH_ACK  0x10    // 确认号有效
​#define TH_URG  0x20    // 紧急指针有效
TCP三次握手​

TCP 三次握手是 TCP/IP 协议中建立可靠连接的过程,其核心目的在于确保通信双方都具备数据发送和接收的能力,并同步初始序列号,为可靠传输奠定基础。具体过程如下:​

  • 第一步:SYN(同步请求)​

客户端 向服务器发送一个 SYN 报文段,其中:​

SYN 标志位 设置为 1,表示请求建立连接。​

序列号(seq) 设置为一个随机值 x,作为客户端初始序列号。​

此时,客户端进入 SYN-SENT 状态。​

  • 第二步:SYN-ACK(同步并确认)​

服务器 收到客户端的 SYN 报文后,如果同意建立连接,会回复一个 SYN-ACK 报文段,其中:​

SYN 和 ACK 标志位 均设置为 1。​

序列号(seq) 设置为服务器自己的随机初始值 y。​

确认号(ack) 设置为 x + 1,表示已收到客户端的序列号 x,并期望下一次收到从 x+1 开始的数据。​

此时,服务器进入 SYN-RECEIVED 状态。​

  • 第三步:ACK(最终确认)​

客户端 收到服务器的 SYN-ACK 报文后,会向服务器发送一个 ACK 报文段作为最终确认,其中:​

ACK 标志位 设置为 1。​

确认号(ack) 设置为 y + 1,表示已收到服务器的序列号 y,并期望下一次收到从 y+1 开始的数据。​

服务器收到此 ACK 后,双方确认连接已建立,共同进入 ESTABLISHED 状态,随后即可开始数据传输。

核心目的总结

三次握手不仅解决了网络中延迟的重复分组问题,更重要的是通过序列号的交换与确认,双向地验证了通信路径的畅通,确保了后续数据传输的可靠性。

TCP四次挥手​

TCP 四次挥手是 TCP/IP 协议中终止连接的过程。由于 TCP 连接是全双工的,因此每个方向都必须单独进行关闭。这个过程旨在确保双方数据都能完整传输后才安全地关闭连接。​

  • 第一步:FIN(连接终止请求)​

主动关闭方(客户端) 发送一个 FIN 报文段,其中:​

FIN 标志位 设置为 1。​

序列号(seq) 设置为 u,等于客户端已传送数据的最后一个字节的序列号加 1。​

发送后,客户端进入 FIN-WAIT-1 状态,表示没有数据要发送了。​

  • ​第二步:ACK(确认请求)​

被动关闭方(服务器) 收到 FIN 报文后,发出 ACK 报文作为响应,其中:​

ACK 标志位 设置为 1。​

确认号(ack) 设置为 u + 1。​

服务器进入 CLOSE-WAIT 状态,客户端收到此 ACK 后进入 FIN-WAIT-2 状态。此时,从客户端到服务器方向的连接已关闭,但服务器仍可向客户端发送数据。​

  • ​第三步:FIN(反向终止请求)​

当 服务器 也没有数据要发送时,它会发送自己的 FIN 报文,其中:​

FIN 标志位 设置为 1。​

序列号(seq) 设置为 v。​

服务器随后进入 LAST-ACK 状态。​

  • ​第四步:ACK(最终确认)​

客户端 收到服务器的 FIN 报文后,发送一个 ACK 报文进行确认,其中:​

ACK 标志位 设置为 1。​

确认号(ack) 设置为 v + 1。​

客户端随后进入 TIME-WAIT 状态,等待 2MSL(两倍的最大报文段生存时间)后彻底关闭连接。服务器收到此 ACK 后立即进入 CLOSED 状态。

核心特点
  • 双向关闭:确保两个方向的数据传输都能独立、有序地结束。​
  • 可靠性:通过 TIME-WAIT 状态确保最后一个 ACK 能到达服务器,防止旧连接的数据包干扰新连接。​
  • 资源释放:双方在确认通信完全结束后才释放连接资源。

TCP状态转换图
CLOSED -> LISTEN (服务器监听)
​CLOSED -> SYN_SENT (客户端发起连接)​
LISTEN -> SYN_RCVD (收到SYN)​
SYN_SENT -> ESTABLISHED (连接建立)
​ESTABLISHED -> FIN_WAIT_1 (主动关闭)​
ESTABLISHED -> CLOSE_WAIT (被动关闭)
​...
TCP流量控制
// 滑动窗口示例
​struct tcp_window {​
    uint32_t send_base;      // 发送基序号​
    uint32_t next_seq_num;   // 下一个序号​
    uint32_t window_size;    // 窗口大小
​    uint32_t recv_base;      // 接收基序号
​    uint32_t expected_seq;   // 期望序号
​};

TCP拥塞控制

TCP 拥塞控制是一套核心算法机制,旨在动态探测网络容量并有效应对拥塞,以在保证公平性的前提下尽可能提高网络利用率。其核心组成部分包括:​

  • 慢启动:连接建立之初,拥塞窗口从1个MSS开始,每收到一个ACK确认就呈指数增长,快速探测可用带宽。​
  • 拥塞避免:当拥塞窗口达到慢启动阈值后,转为线性增长阶段,每经过一个RTT才将窗口增加1,谨慎地逼近网络瓶颈。​
  • 快速重传:当发送方连续收到3个重复的ACK时,即判定数据包可能丢失,无需等待重传超时,立即重传丢失的报文段。​
  • 快速恢复:在触发快速重传后,TCP并非将窗口骤降至1重新慢启动,而是将窗口减半,并直接进入拥塞避免阶段,从而更平滑地维持数据流。
// 拥塞窗口控制
​struct tcp_congestion {
​    uint32_t cwnd;          // 拥塞窗口
​    uint32_t ssthresh;      // 慢启动阈值
​    uint32_t duplicate_acks; // 重复ACK计数
​    enum {
​        SLOW_START,
​        CONGESTION_AVOIDANCE,
​        FAST_RECOVERY
​    } state;
​};
TCP协议常见面试题
为什么关闭连接的需要四次挥手,而建立连接却只要三次握手呢?

关闭连接时,被动断开方在收到对方的FIN结束请求报文时,很可能业务数据没有发送完成,并不能立即关闭连接,被动方只能先回复一个ACK响应报文,告诉主动断开方:“你发的FIN报文我收到了,只有等到我所有的业务报文都发送完了,我才能真正的结束,在结束之前,我会发你FIN+ACK报文的,你先等着”。所以,被动断开方的确认报文,需要拆开成为两步,故总体就需要四步挥手。

而在建立连接场景中,Server端的应答可以稍微简单一些。当Server端收到Client端的SYN连接请求报文后,其中ACK报文表示对请求报文的应答,SYN报文用来表示服务端的连接也已经同步开启了,而ACK报文和SYN报文之间,不会有其他报文需要发送,故而可以合二为一,可以直接发送一个SYN+ACK报文。所以,在建立连接时,只需要三次握手即可。

为什么连接建立的时候是三次握手,可以改成两次握手吗?

三次握手完成两个重要的功能:一是双方都做好发送数据的准备工作,而且双方都知道对方已准备好;二是双方完成初始SN序列号的协商,双方的SN序列号在握手过程中被发送和确认。

如果把三次握手改成两次握手,可能发生死锁。两次握手的话,缺失了Client的二次确认ACK帧,假想的TCP建立的连接时二次挥手,可以如下图所示:

图:假想的TCP建立的连接时二次握手的示意图

在假想的TCP建立的连接时二次握手过程中,Client发送Server发送一个SYN请求帧,Server收到后发送了确认应答SYN+ACK帧。按照两次握手的协定,Server认为连接已经成功地建立了,可以开始发送数据帧。这个过程中,如果确认应答SYN+ACK帧在传输中被丢失,Client没有收到,Client将不知道Server是否已准备好,也不知道Server的SN序列号,Client认为连接还未建立成功,将忽略Server发来的任何数据分组,会一直等待Server的SYN+ACK确认应答帧。而Server在发出的数据帧后,一直没有收到对应的ACK确认后就会产生超时,重复发送同样的数据帧。这样就形成了死锁。

为什么主动断开方在TIME-WAIT状态必须等待2MSL的时间?

**原因之一:**主动断开方等待2MSL的时间,是为了确保两端都能最终关闭。假设网络是不可靠的,被动断开方发送FIN+ACK报文后,其主动方的ACK响应报文有可能丢失,这时候的被动断开方处于LAST-ACK状态的,由于收不到ACK确认被动方一直不能正常的进入CLOSED状态。在这种场景下,被动断开方会超时重传FIN+ACK断开响应报文,如果主动断开方在2MSL时间内,收到这个重传的FIN+ACK报文,会重传一次ACK报文,后再一次重新启动2MSL计时等待,这样,就能确保被动断开方能收到ACK报文,从而能确保被动方顺利进入到CLOSED状态。只有这样,双方都能够确保关闭。反过来说,如果主动断开方在发送完ACK响应报文后,不是进入TIME_WAIT状态去等待2MSL时间,而是立即释放连接,则将无法收到被动方重传的FIN+ACK报文,所以不会再发送一次ACK确认报文,此时处于LAST-ACK状态的被动断开方,无法正常进入到CLOSED状态。

**原因之二:**防止“旧连接的已失效的数据报文”出现在新连接中。主动断开方在发送完最后一个ACK报文后,再经过2MSL,才能最终关闭和释放端口,这就意味着,相同端口的新TCP新连接,需要在2MSL的时间之后,才能够正常的建立。2MSL这段时间内,旧连接所产生的所有数据报文,都已经从网络中消失了,从而,确保了下一个新的连接中不会出现这种旧连接请求报文。

如果已经建立了连接,但是Client端突然出现故障了怎么办?

TCP还设有一个保活计时器,Client端如果出现故障,Server端不能一直等下去,这样会浪费系统资源。每收到一次Client客户端的数据帧后,Server端都的保活计时器会复位。计时器的超时时间通常是设置为2小时,若2小时还没有收到Client端的任何数据帧,Server端就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,Server端就认为Client端出了故障,接着就关闭连接。如果觉得保活计时器的两个多小时的间隔太长,可以自行调整TCP连接的保活参数。

请你说一下 TCP 的粘包和拆包?​

在网络通信中,TCP 是一种面向连接的、可靠的、基于字节流的传输层通信协议。而 TCP 的“粘包”和“拆包”是在使用 TCP 进行数据传输时可能会遇到的现象。​

  • 粘包​
    • 定义​:
      • 粘包是指在接收数据时,由于 TCP 是面向流的协议,接收方不知道消息之间的界限,可能会将多个发送方发送的数据包合并成一个数据包接收,从而导致一次接收的数据包含了多个逻辑消息。​
    • 产生原因​:
      • 发送方发送数据的速度较快,接收方接收数据的速度相对较慢,导致多个数据包在接收方的缓冲区中被合并。​
      • 发送方发送的数据包较小,TCP 为了提高传输效率,可能会将多个小数据包合并成一个较大的数据包进行发送。​
    • 影响​:
      • 如果接收方不能正确地处理粘包问题,可能会导致数据解析错误,影响程序的正常运行。​
  • 拆包​
    • 定义:​
      • 拆包是指在发送数据时,由于 TCP 协议对数据长度没有限制,一个较大的数据包可能会被拆分成多个小数据包进行发送,接收方在接收数据时需要将这些小数据包重新组合成完整的数据包。​
    • 产生原因:​
      • 发送的数据大小超过了 TCP 协议的最大传输单元(MTU),TCP 会将数据包进行拆分,以适应网络传输的要求。​
      • 网络拥塞等原因导致数据包丢失或延迟,接收方可能需要等待一段时间才能收到完整的数据包。​
    • 影响:​
      • 接收方需要进行额外的处理来组合拆分后的数据包,增加了程序的复杂性。如果处理不当,可能会导致数据丢失或不完整。​

总结:TCP是面向流,没有界限的一串数据。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。​

为什么会产生粘包和拆包呢,如何解决呢?​

要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包;接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包;​

要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包;待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。即TCP报文长度-TCP头部长度>MSS。​

解决方案:发送端将每个数据包封装为固定长度在数据尾部增加特殊字符进行分割将数据分为两部分,一部分是头部,一部分是内容体;其中头部结构大小固定,且有一个字段声明内容体的大小。​

  • MTU:一个网络包的最大长度​
  • MSS:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度;​

解决方法​

  • 定长消息:发送方和接收方约定好每个数据包的固定长度,接收方按照固定长度读取数据,不足长度的部分用特定字符填充。​
  • 特殊字符分隔:在发送的数据中添加特殊字符作为消息的分隔符,接收方根据分隔符来区分不同的消息。​
  • 消息长度前缀:在每个消息的开头添加一个表示消息长度的字段,接收方先读取长度字段,再根据长度读取相应的数据。
请说下三次握手的目的?​

TCP 三次握手主要是为了以下几个目的:​

  • 建立连接​,确保通信双方都有发送和接收数据的能力,建立起可靠的双向连接。具体过程如下:​
    • 第一次握手:客户端向服务器发送一个 SYN(同步)包,该包中包含客户端选择的初始序列号(seq)。此时客户端进入 SYN_SENT 状态。这个步骤表明客户端有发送数据的意愿。​
    • 第二次握手:服务器收到客户端的 SYN 包后,向客户端发送一个 SYN/ACK(同步确认)包,该包中包含服务器选择的初始序列号和对客户端序列号的确认(ack = 客户端序列号 + 1)。此时服务器进入 SYN_RCVD 状态。这个步骤表明服务器有接收和发送数据的能力。​
    • 第三次握手:客户端收到服务器的 SYN/ACK 包后,向服务器发送一个 ACK(确认)包,该包中确认号为服务器序列号加一(ack = 服务器序列号 + 1)。此时客户端进入 ESTABLISHED 状态。服务器收到这个 ACK 包后也进入 ESTABLISHED 状态。这个步骤表明客户端确认了服务器的接收和发送能力,连接正式建立。​
  • 协商初始序列号
    • 通过三次握手,双方可以确定初始序列号,确保数据传输的有序性。在后续的数据传输中,每一个数据包都有一个序列号,接收方可以根据序列号来判断数据包的顺序,并进行排序和组装。​
  • 防止过期连接请求的干扰​
    • 假设没有三次握手,客户端发送的一个连接请求在网络中延迟了,一段时间后这个延迟的连接请求到达服务器,服务器误以为是新的连接请求并进行响应。​
    • 如果只有两次握手,此时连接就会建立,但实际上客户端可能已经不需要这个连接了,这就会导致资源的浪费和错误的连接建立。​
    • 通过三次握手,客户端在收到服务器的响应后会再次确认,如果客户端没有发送新的连接请求,就不会进行第三次确认,服务器在一段时间后没有收到确认就会关闭连接,从而避免了这种错误情况的发生。
请说下TCP三次握手过程​

TCP 三次握手的过程如下:​

  • 第一次握手​
    • 客户端向服务器发送一个带有 SYN标志的数据包。这个数据包中还会包含一个随机生成的初始序列号(sequence number,记为 seq=x),用来标识本次连接中客户端发送的数据字节流的起始位置。此时客户端进入 SYN_SENT状态,表示客户端已准备好建立连接并等待服务器的确认。​
  • 第二次握手​
    • 服务器接收到客户端的 SYN 数据包后,知道客户端想要建立连接。服务器会向客户端发送一个带有 SYN 和 ACK标志的数据包作为回应。这个数据包中也有一个随机生成的初始序列号(记为 seq=y),同时确认号记为 ack=x+1是对客户端初始序列号加一,表示服务器已经接收到了客户端的 SYN 数据包,并期望下一个收到的数据包的序列号是 x+1。此时服务器进入 SYN_RCVD 状态,表示服务器正在等待客户端的确认以完成连接的建立。​
  • 第三次握手​
    • 客户端收到服务器的 SYN+ACK 数据包后,检查确认号是否正确(即确认号是否为自己发送的初始序列号加一)。如果正确,客户端会向服务器发送一个带有 ACK 标志的数据包,确认号为服务器的序列号加一(ack=y+1)。此时客户端进入 ESTABLISHED 状态,表示连接已建立成功。​
    • 服务器收到客户端的 ACK 数据包后,也进入 ESTABLISHED 状态。至此,TCP 三次握手完成,客户端和服务器之间可以开始进行数据传输。​

下面是举例说明及图片描述​

  • 发送端:我发送syn(同步序列编号)的数据包给你啦。​
  • 接收端:我已经收到你的数据包啦,我将syn/ack编号发送给你啦​
  • 发送端:明白,我同时将ack的包发送给你啦。

握手情况如下:

三次握手中,第二次握手的时候为什么还要传回 SYN ?​

传回 SYN 标志的数据包是服务端也要向客户端发送链接请求。​

当服务器收到客户端发送的 SYN 包后,回传带有 ACK 和 SYN 标志的数据包,ACK 表示服务器已经接收到了客户端的连接请求,并同意建立连接,SYN 则是服务端向客户端发送链接请求​

为什么要三次握手,4次握手可以吗?​
  • 三次握手才可以阻止重复历史连接的初始化(主要原因)​
  • 三次握手才可以同步双方的初始序列号​
  • 三次握手才可以避免资源浪费​

重复连接问题​

  • 因为网络会堵塞,所以有可能因为网络堵塞,第一个客户端发出第一个SYN = 10包之后,迟迟收不到SYN/ACK,所以就进行补发 SYN = 20,此时服务端收到了 SYN = 10 的旧包,然后发送ACK和SYN,但是呢?此时客户端发现我应该接收的是 SYN = 20 的ACK,而不是历史的连接,所以就会发送RST拒绝连接,等SYN= 20的 ACK来了之后,再进行连接。三次握手可以让客户端通过上下文来进行判断。​

同步双方的初始序列号​

  • 序列号同步是可靠传输的基础,通过三次握手可以保证双方的序列号同步,其实四次握手也可以,只不过第二次握手把两个包合成一个了。​

避免资源浪费​

  • 防止历史连接的建立,如果使用两次握手的话,现在已经连接成功,但是之前因为网络问题延迟传输的报文,再一次发到服务器则有可能再次造成连接。​
  • TCP 建立连接时,通过三次握手能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。​
为什么不使用「两次握手」和「四次握手」?​
  • 两次握手:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号; ​
  • 四次握手:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。​
请简要介绍一下 websocket​
  1. Websocket是一种建立在 TCP协议之上的网络通信协议。TCP协议作为一种可靠的传输层协议,为 Websocket提供了稳定的底层连接基础,确保数据能够准确无误地在客户端与服务器端之间传输。​
  2. Websocket与 HTTP 协议有良好的兼容性。默认端口的设置上,它与 HTTP协议常用的端口保持一致,即 80(用于非加密通信)和 443(用于加密通信)在网络环境中,Websocket的部署和使用无需额外为其开辟特殊端口,降低了网络配置的复杂性。​
  3. Websocket的数据格式具有轻量的特点。它不像一些其他协议那样数据结构臃肿,而是以一种简洁高效的方式对要传输的数据进行封装。这种轻量的数据格式在网络传输过程中,能够减少不必要的开销,使得数据能够以更快的速度在客户端和服务器端之间传递。​
  4. Websocket具备强大的数据类型支持能力,它既可以发送文本数据,也可以发送二进制数据​
  5. Websocket赋予了客户端极大的通信灵活性,客户端可以与任意服务器通信。这意味着只要服务器端支持 Websocket协议,客户端就可以与其建立连接并进行通信。​
  6. Websocket的协议标识符是 ws(如果加密,则为 wss),这是用于在网络请求和标识中明确表示所采用的协议类型。同时,服务器网址就是 URL。在实际应用中,当客户端要与服务器建立 Websocket连接时,就可以通过指定带有 ws 或 wss 协议标识符的 URL 来实现。
为什么要四次挥手?

TCP 连接的双向性决定​

TCP 连接是全双工通信,这意味着数据可以在两个方向上同时传输。因此,当要关闭连接时,需要考虑两个方向的数据通道的关闭。​

例如,在一个网络文件传输场景中,客户端可能在向服务器上传文件的同时,服务器也在向客户端发送文件的校验信息。每个方向都有自己独立的发送和接收缓存,所以需要分别关闭这两个方向的通信通道。​

确保数据传输完整​

  • 第一次挥手(主动关闭方发起):主动关闭连接的一方(如客户端)发送一个 FIN(结束标志)数据包,表示自己已经没有数据要发送给对方了。但是,此时它还可以接收对方(服务器)发送的数据。这就好比在一个对话中,一方先说 “我说完了”,但还可以听对方说话。​
  • 第二次挥手(被动关闭方回应):被动关闭方(服务器)收到 FIN 后,会发送一个 ACK(确认)数据包给主动关闭方。这是告诉主动关闭方,“我已经知道你没有数据要发送了”。不过,此时被动关闭方可能还有数据没发送完,所以它不能立刻关闭连接。就像在对话中,听到对方说 “我说完了” 后,回应 “我知道了”,但自己可能还没说完。​
  • 第三次挥手(被动关闭方发起):当被动关闭方(服务器)也发送完自己的数据后,它会发送一个 FIN 数据包给主动关闭方,表示自己也没有数据要发送了。这类似于对话中,自己也说完了,告诉对方 “我也说完了”。​
  • 第四次挥手(主动关闭方回应):主动关闭方收到被动关闭方的 FIN 后,发送一个 ACK 数据包进行确认。这样,双方都确认了彼此的数据传输已经完成,连接可以安全关闭。这就好比在对话结束时,双方都确认了彼此都已经说完,然后结束对话。​

防止数据丢失和错误关闭​

如果只有三次挥手,可能会出现这样的情况:被动关闭方还没来得及发送完所有数据,就收到了主动关闭方最后的确认,导致数据丢失。​

四次挥手通过明确双方的数据发送状态,确保在连接关闭时,不会有数据丢失或者被错误截断的情况。

为什么建立连接是三次握手,关闭连接却是四次挥手呢?​

建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。 ​

而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接。​

因此,己方ACK和FIN一般都会分开发送,从而导致多了一次.​

如果已经建立了连接,但是客户端突然出现故障了怎么办?​

在 TCP 协议中,设有一个保活计时器机制来应对可能出现的客户端异常情况。当客户端与服务器建立连接后,如果长时间没有数据交互,可能会导致服务器资源的无效占用。​

此时,保活计时器开始发挥作用,其初始时长通常设置为 2 小时。​

在正常的数据传输过程中,每当服务器收到客户端发送的请求,保活计时器就会被重置,重新开始计时。​

如果在长达 2 小时的时间内,服务器都未收到客户端传来的任何数据,此时服务器便会主动向客户端发送一个探测报文段。此后,每隔 75 秒,服务器会持续向客户端发送探测报文段,以此来检测客户端是否仍处于正常连接状态。​

若服务器连续发送了 10 个探测报文段,但始终未得到客户端的任何回应,那么服务器就会判定客户端出现了故障。基于这种判断,服务器为了合理利用资源,会将与该客户端相关的连接关闭,释放相应的资源,从而避免因等待一个可能已经故障的客户端而造成资源浪费。​

这种机制有效保障了服务器在面对客户端异常情况时的资源管理和连接维护。​

TCP的特点​

提供可靠传输,实行顺序控制或重发控制机制。具有流控制、拥塞控制、提高网络利用率等众多功能。​

充分实现了数据传输时各种控制功能,可以进行丢包时的重发机制,还可以对次序乱掉的分包进行顺序控制。​

TCP 通过检验和、连接管理、确认应答、 重发超时机制、序列号机制、以段为单位发送数据、窗口控制、流控制等机制进行可靠传输。​

  • 检验和:这个原理相当于MD5校验,目检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。​
  • 连接管理:采用三次握手的方式建立可靠的通信传输,​
  • 确认应答 : 确认应答就是会发送一条已经收到段的应答消息。​
  • 重发超时机制:这个含义在上面的序列号机制已经提到,当发送端长时间没有接收到,确认信息时,则会对该条报文进行确认,因为此时他认为,是报文发送失败。所以这个超时时间的设定就显得尤为重要。TCP采用了一个很巧妙的方法,那就是每次发包时都会,计算往返时间及其偏差,重传的时间就是比两者之和稍大一点的值。​
    • RTT:往返时间​
    • RTO:重发时间,重发时间略大于多次RTT的平均值​
  • 序列号机制:当发送的数据到达接收主机时,接收端主机会返回已经收到的ACK确认号,确认应答。但是确认应答有可能出现以下这种情况,报文发送时丢失,确认应答信息丢失。这样就可能会导致发送端一直重发报文。​
    • 一般情况下,就发送端会等待一段时间后,如果没有收到确认应答,则会进行重发,但是我们也会遇到这种情况,当我们因为网络延迟之后,在我们发送方重传了报文之后,才接收到确认应答的信息,所以这样接收端就会收到无休止的重复包,所以这时候我们需要引入序列号机制,就是给每一个TCP报文添加一个序列号,告知发送方,我收到了哪条信息,下次传输时应该传输哪个报文。​
  • 以MSS为单位发送数据:建立TCP连接的同时,也可以确定发送数据包的单位,我们称其为最大发送长度MSS,该值是在三次握手时计算得出的,会在两者之间选择一个都可接受的最小值,TCP在发送数据时,重发时是以MSS为单位进行发送的。​
  • 流量控制,利用窗口控制提高速度:上面我们提到的,每次都要进行确认,如果往返时间较长,这会大大降低效率,所以TCP引入了窗口的概念,这样我们就可以使确认的不是每个分段,而是以最大的单位进行确认,就是发完一个段之后,不用等到确认信息,继续发段。​
    • 窗口大小就是无需等待确认应答, 而可以发送数据的最大值。当然这种情况也会出现丢失段的情况,因为TCP有序列号机制,所以知道哪些段需要重发,发送方的缓冲区,会将待重发的段保存到缓冲区内,知道收到确认应答。​
  • 流控制:可以让发送端根据接收端的接收实力进行发送。接收端会向发送端发送可以接收的数据大小。另外为了防止接收不到窗口更新通知,发送端则会时不时发送一个窗口探测的数据来获取窗口信息。​
  • 拥塞控制:为了进行拥塞控制,TCP 发送方要维持一个 拥塞窗口(cwnd) 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。 ​
    • TCP的拥塞控制采用了四种算法,即 慢开始 、 拥塞避免 、快重传 和 快恢复。 慢开始: 慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd初始值为1,每经过一个传播轮次,cwnd加倍 2,4,8,16,呈指数型增长。​
    • 当然慢开始的cwnd的大小不是无限制增长的,当小于 ssthresh 时,使用慢启动算法,大于等于时则启动拥塞避免算法。 ​
      • 拥塞避免: 那么进入拥塞避免算法后,它的规则是:每当收到一个 ACK 时,cwnd 增加 1/cwnd。此时则变成了线性增长。​
      • 当报文就这么一直增长着后,网络就会慢慢进入了拥塞的状况了,于是就会出现丢包现象,这时就需要对丢失的数据包进行重传。​
      • 当触发了重传机制,也就进入了拥塞发生算法​
        • 此时则采用两种方法解决丢包问题​
        • 超时重传:当发生了「超时重传」,则就会使用拥塞发生算法。​
          • 这个时候,ssthresh 和 cwnd 的值会发生变化:​
            • ssthresh 设为 cwnd/2,​
            • cwnd 重置为 1​
          • 然后就重新慢启动,这大起大落不太行。​
        • 快速重传:还有更好的方式,前面我们讲过「快速重传算法」。当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。​
        • TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分,则 ssthresh 和 cwnd 变化如下:cwnd = cwnd/2 ,也就是设置为原来的一半; ssthresh = cwnd; 进入快速恢复算法。​
        • 快速恢复:快速重传和快速恢复算法一般同时使用,快速恢复算法是认为,你还能收到 3 个重复 ACK 说明网络也不那么糟糕,所以没有必要像 RTO 超时那么强烈。正如前面所说,进入快速恢复之前,cwnd 和 ssthresh 已被更新了:cwnd = cwnd/2 ,也就是设置为原来的一半; ssthresh = cwnd;
          • 进入快速恢复算法如下:拥塞窗口cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了); 重传丢失的数据包; 如果再收到重复的 ACK,那么 cwnd 增加 1;​
          • 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;

简单总结​

  • 可靠性:TCP通过序号、确认和重传机制来确保数据的可靠传输,能够检测丢失的数据包并进行重传,保证数据的完整性和正确性。​
  • 面向连接:TCP在通信双方建立连接后才能进行数据传输,连接的建立和释放都需要经过三次握手和四次挥手的过程,保证了通信的可靠性。​
  • 有序性:TCP保证数据包的顺序传输和接收,接收端会按照发送端发送的顺序重新组装数据包,保证数据的有序性。​
  • 流量控制:TCP通过滑动窗口机制来控制数据的传输速率,避免发送方发送过多数据导致接收方无法处理。​
  • 拥塞控制:TCP通过拥塞窗口和拥塞避免算法来控制网络拥塞,避免网络过载导致数据丢失和延迟增加。
请详细说一下 TCP 的滑动窗口​

TCP的滑动窗口是一种流量控制机制,用于协调发送端和接收端之间的数据传输速度。​

它基于窗口的概念,窗口大小代表了在不需要等待对方确认的情况下,发送端可以发送的数据量。​

发送窗口主要由四个部分组成,分别是已发送并已确认、已发送但未确认、未发送但允许发送和不允许发送。​

窗口大小的动态调整:发送窗口的大小不是固定不变的,它会根据接收端的反馈进行动态调整。​

接收端会在确认信息(ACK)中告知发送端自己的接收窗口大小,发送端根据这个信息来调整自己的发送窗口。​

滑动窗口的作用与优势​

  • 流量控制:滑动窗口机制有效地实现了发送端和接收端之间的流量控制。​通过动态调整窗口大小,发送端可以根据接收端的接收能力发送适量的数据,避免接收端缓冲区溢出导致数据丢失。例如,在网络拥塞或者接收端处理能力有限的情况下,接收端可以减小接收窗口,从而使发送端降低发送速度,保证数据传输的稳定性。​
  • 提高传输效率:滑动窗口允许发送端在收到确认信息之前发送多个数据包,不需要等待每个数据包的确认后再发送下一个,大大提高了数据传输的效率。这在高带宽、低延迟的网络环境下尤其明显,可以充分利用网络带宽,加快数据传输速度。同时,接收端也可以根据自己的处理能力和缓冲区状态灵活地接收和处理数据,进一步提高了整个传输过程的效率。​
请详细说一下 TCP 的拥塞控制​

拥塞控制是一种在计算机网络中用于防止过多的数据注入网络,避免网络性能下降的机制。​

在网络中,当注入网络的流量超过网络的处理能力时,就会出现拥塞现象。​

拥塞控制的目标是在网络出现拥塞时,通过合理地调整发送端的数据发送速率,使得网络能够保持良好的性能,避免死锁和网络崩溃。​

拥塞产生的原因主要有以下几种,网络资源有限、拓扑和路由选择的复杂性、突发流量和流量模式的不均​

拥塞控制的主要算法​

慢启动(Slow - Start)​

在连接建立初期,发送端不知道网络的拥塞状况,因此采用慢启动算法来逐渐增加发送窗口的大小​

发送端从一个较小的初始发送窗口开始(通常为一个或几个最大报文段长度MSS)发送数据,每当收到一个确认(ACK),就将发送窗口大小加倍。​

例如,初始窗口大小为1MSS,收到一个ACK后,窗口大小变为2MSS,再收到一个ACK,窗口大小变为4MSS,以此类推。这种指数增长的方式可以快速探测网络的可用带宽。​

慢启动的目的是在不引起网络拥塞的前提下,尽快地增加发送数据的速率。​

它通过逐渐增加发送窗口大小来试探网络的承载能力,避免一开始就发送大量数据导致网络拥塞​

另外为了防止发送窗口无限制地增长,设置了一个慢启动阈值(ssthresh)。当发送窗口大小超过这个阈值时,就会进入拥塞避免阶段。​

拥塞避免(Congestion - Avoidance)​

当发送窗口大小超过慢启动阈值后,进入拥塞避免阶段,在这个阶段,发送窗口不再是指数增长,而是线性增长。​

发送端每收到一个ACK,就将发送窗口大小增加一个MSS的一小部分(通常为1/MSS)。​

例如,发送窗口大小为16MSS,每收到一个ACK,窗口大小增加1/16MSS,这样发送窗口会以较为缓慢的速度增长,避免过快地占用网络资源。​

拥塞避免阶段的主要目的是在网络已经有一定负载的情况下,更加谨慎地增加发送数据的速率,以防止网络出现拥塞。​

通过线性增长发送窗口大小,可以更好地利用网络的剩余带宽,同时降低拥塞的风险。​

快重传(Fast - Retransmit)​

当接收端收到一个失序的报文段时,会立即发送重复的ACK,通知发送端这个报文段可能丢失。​

如果发送端连续收到三个重复的ACK,就会认为这个报文段确实丢失了,不等超时定时器超时,就立即重传这个丢失的报文段。​

例如,发送端发送了报文段1、2、3,接收端收到了报文段1 和 3,但没有收到报文段2,此时认为 2 丢失了,因为已经收到了 3,但是未收到 2,接收端就会连续发送针对报文段 1 的ACK,当发送端收到三个这样的重复ACK时,就会快速重传报文段2。​

快恢复机制可以在网络出现少量丢包的情况下,更快地恢复数据传输的正常速率,避免了重新进行慢启动导致的发送速率过度降低,从而提高了网络的整体性能和传输效率。​

与传统的慢启动过程相比,它不需要从一个很小的初始窗口重新开始增长,而是基于网络可能还有一定传输能力的判断,发送端将慢启动阈值(ssthresh)设置为当前发送窗口大小的一半,然后将发送窗口大小设置为新的ssthresh加上3个MSS(这是因为收到了三个重复的ACK,说明网络可能还可以传输一定的数据)。​

之后,发送端在拥塞避免阶段的规则下继续增加发送窗口大小。​

说一下什么是半连接队列​

在TCP(Transmission Control Protocol)三次握手建立连接的过程中,服务器端会维护一个队列,这个队列用来保存处于SYN_RECV状态(即接收到客户端的SYN报文并已回复SYN + ACK报文,但还未收到客户端的ACK报文)的连接请求,这个队列就被称为半连接队列。​

半连接队列在TCP三次握手过程中的作用​

  • 第一次握手:客户端向服务器发送一个带有SYN(同步序列号)标志的TCP报文,请求建立连接。此时,客户端进入SYN_SENT状态,表示已经发送了连接请求。​
  • 第二次握手:服务器收到客户端的SYN报文后,会为这个连接请求分配资源(如创建一个新的连接控制块等),然后向客户端发送一个带有SYN + ACK标志的报文,表示同意建立连接,并确认客户端的SYN。此时,服务器将这个连接放入半连接队列中,并且进入SYN_RECV状态。​
  • 这个半连接队列的作用就是暂时保存这些已经完成了第一次握手和部分第二次握手的连接请求。​
  • 第三次握手:客户端收到服务器的SYN + ACK报文后,会向服务器发送一个ACK(确认)报文。服务器收到这个ACK报文后,会将该连接从半连接队列中移除,并将其放入全连接队列(用于已经完成三次握手,等待服务器进行后续处理的连接),然后服务器进入ESTABLISHED状态,表示连接已经成功建立。此时,客户端也进入ESTABLISHED状态,双方就可以开始进行数据传输了。​

半连接队列的大小设置及影响因素​

半连接队列的大小是可以配置的,在不同的操作系统和网络应用场景下,其默认大小和可配置范围有所不同。例如,在Linux系统中,可以通过调整内核参数(如net.ipv4.tcp_max_syn_backlog)来改变半连接队列的大小。​

  • 影响因素​:半连接队列过小,在高并发的网络环境下或者遭受SYN Flood攻击(一种利用TCP三次握手进行的恶意攻击,攻击者发送大量的SYN报文,使服务器的半连接队列被填满,导致正常的连接请求无法进入时,服务器可能会因为无法容纳更多的半连接请求而拒绝新的连接请求,从而影响服务的可用性。​
  • 服务器资源:半连接队列的大小也受服务器资源(如内存、CPU等)的限制。较大的半连接队列需要占用更多的服务器资源来维护,包括存储连接相关的信息和进行状态管理。因此,需要根据服务器的性能和实际网络流量情况合理设置半连接队列的大小。​
什么是 SYN 攻击?​

SYN攻击(SYN Flood攻击)是一种典型的分布式拒绝服务(DDoS)攻击方式。​

它利用了TCP(Transmission Control Protocol)三次握手协议的机制,通过向目标服务器发送大量伪造的TCP SYN(同步序列号)请求报文,使服务器的资源耗尽,从而无法正常处理合法用户的连接请求。​

SYN攻击的原理​

正常的TCP三次握手过程​

  • 第一次握手:客户端向服务器发送一个带有SYN标志的TCP报文,请求建立连接。这个报文中包含客户端的初始序列号(ISN)。​
  • 第二次握手:服务器收到客户端的SYN报文后,会为这个连接请求分配一定的资源,如创建一个新的连接控制块来记录连接的状态信息,然后向客户端发送一个带有SYN + ACK标志的报文。这个报文中包含服务器的初始序列号和对客户端SYN的确认序列号,同时服务器将这个连接请求放入半连接队列中,等待客户端的第三次握手。​
  • 第三次握手:客户端收到服务器的SYN + ACK报文后,会向服务器发送一个ACK报文,服务器收到这个ACK报文后,将该连接从半连接队列移到全连接队列,然后双方就可以开始进行数据传输,连接正式建立。​

SYN攻击过程​

  • 在SYN攻击中,攻击者会伪造大量源IP地址不同的SYN报文发送给目标服务器。服务器收到这些报文后,会按照正常的TCP三次握手流程进行处理,为每个SYN报文分配资源并发送SYN + ACK报文,然后将这些半连接请求放入半连接队列中等待客户端的ACK。​
  • 但是,由于攻击者伪造的源IP地址是不存在或者不可达的,服务器永远无法收到对应的ACK报文,这些半连接就会一直占用服务器的资源。​
  • 随着攻击流量的不断增加,服务器的半连接队列会被填满,导致服务器无法处理新的合法连接请求,最终造成服务瘫痪。​

SYN攻击的危害​

  • 服务不可用:最直接的危害是导致目标服务器无法正常提供服务。例如,对于一个网站服务器来说,遭受SYN攻击后,用户无法正常访问网站,出现网页无法加载、登录失败等情况。​对于提供在线服务的应用服务器,如游戏服务器、金融交易服务器等,也会因为无法建立新的连接而使服务中断,给用户带来严重的损失。​
  • 资源耗尽:SYN攻击会消耗服务器大量的资源,包括内存和CPU资源。服务器需要为每个半连接请求分配内存来存储相关的连接信息,随着半连接数量的不断增加,内存会被迅速耗尽。同时,处理大量的连接请求也会占用大量的CPU时间,使得服务器无法有效地执行其他正常任务。​

如何防范SYN攻击​

  • 调整系统参数​
    • 增大半连接队列的大小:在服务器操作系统中,可以通过调整相关参数来增加半连接队列的容量。​
    • 例如,在Linux系统中,可以调整net.ipv4.tcp_max_syn_backlog参数来增大半连接队列的大小,使服务器能够容纳更多的半连接请求,从而在一定程度上抵御小规模的SYN攻击。​
    • 但是,这种方法有一定的局限性,因为增大队列大小也会占用更多的服务器资源,并且如果攻击流量过大,仍然可能导致资源耗尽。​
  • 使用防火墙和入侵检测系统​
    • 防火墙可以配置规则来过滤掉一些明显异常的SYN请求。​
    • 通过检测源IP地址是否为伪造地址、是否来自恶意IP地址黑名单等来阻止可疑的连接请求。​
    • IDS可以实时监测网络流量,识别SYN攻击的特征模式,如短时间内大量来自不同源IP地址的SYN请求,一旦发现攻击迹象,及时发出警报并采取相应的防御措施,如暂时阻断攻击流量。​
  • 采用SYN Cookie技术​
    • SYN Cookie是一种对TCP服务器的扩展选项,它的基本原理是在服务器收到SYN请求时,不立即分配资源,而是通过一种加密算法根据请求的一些信息(如源IP地址、端口号、时间戳等)生成一个SYN Cookie,并将其作为SYN + ACK报文中的序列号发送给客户端。​
    • 当客户端返回ACK报文时,服务器可以通过验证ACK报文中的序列号是否符合之前生成的SYN Cookie来判断这个连接请求是否合法。​
    • 如果合法,则为这个连接分配资源并建立连接;如果不合法,则丢弃这个连接请求。这样就避免了在半连接阶段为每个请求分配大量资源,从而有效地抵御SYN攻击。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐