当前位置: 首页 > news >正文

字节一面:TCP 三次握手,问的好细!

大家好,我是小林。

有位读者在面试字节时,被问到这么个问题:

图片

概括起来,是这两个问题:

  • TCP 三次握手中,客户端收到的第二次握手中 ack 确认号不是自己期望的,会发生什么?是直接丢弃 or 回 RST 报文?
  • 什么情况下会收到不正确的 ack(第二次握手中的 ack) 呢?

问题解答

不卖关子,直接说这个问题,是回 RST 报文。过程如下图:

图片三次握手避免历史连接

当客户端连续发送多次建立连接的 SYN 报文,然后在网络拥堵的情况,就会发生客户端收到不正确的 ack 的情况。具体过程如下:

  • 客户端先发送了 SYN(seq = 90) 报文,但是被网络阻塞了,服务端并没有收到,接着客户端又重新发送了 SYN(seq = 100) 报文,注意不是重传 SYN,重传的 SYN 的序列号是一样的。
  • 「旧 SYN 报文」比「最新的 SYN 」 报文早到达了服务端,那么此时服务端就会回一个 SYN + ACK 报文给客户端,此报文的确认号是 91(90+1)。
  • 客户端收到后,发行自己期望收到的确认号应该是 100+1,而不是 90 + 1,于是就会回 RST 报文。
  • 服务端收到 RST 报文后,就会中止连接。
  • 后续最新的 SYN 抵达了服务端后,客户端与服务端就可以正常的完成三次握手了。

上述中的「旧 SYN 报文」称为历史连接,TCP 使用三次握手建立连接的最主要原因就是防止「历史连接」初始化了连接

我们也可以从 RFC 793 知道 TCP 连接使用三次握手的首要原因:

The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.

简单来说,三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。RFC 给出的三次握手防止历史连接的案例图如下:

图片RFC 793

如果是两次握手连接,就无法阻止历史连接,那为什么 TCP 两次握手为什么无法阻止历史连接呢?

我先直接说结论,主要是因为在两次握手的情况下,「被动发起方」没有中间状态给「主动发起方」来阻止历史连接,导致「被动发起方」可能建立一个历史连接,造成资源浪费

你想想,两次握手的情况下,「被动发起方」在收到 SYN 报文后,就进入 ESTABLISHED 状态,意味着这时可以给对方发送数据给,但是「主动发」起方此时还没有进入 ESTABLISHED 状态,假设这次是历史连接,主动发起方判断到此次连接为历史连接,那么就会回 RST 报文来断开连接,而「被动发起方」在第一次握手的时候就进入 ESTABLISHED 状态,所以它可以发送数据的,但是它并不知道这个是历史连接,它只有在收到 RST 报文后,才会断开连接。

图片两次握手无法阻止历史连接

可以看到,上面这种场景下,「被动发起方」在向「主动发起方」发送数据前,并没有阻止掉历史连接,导致「被动发起方」建立了一个历史连接,又白白发送了数据,妥妥地浪费了「被动发起方」的资源。

因此,要解决这种现象,最好就是在「被动发起方」发送数据前,也就是建立连接之前,要阻止掉历史连接,这样就不会造成资源浪费,而要实现这个功能,就需要三次握手

源码分析

我说回 RST 就回 RST 吗?当然不是了,肯定得用源码证明我说的这个结论。

听到要源码分析,可能有的同学就怂了。

其实要分析我们今天这个问题,只要懂 if else 就行了,我也会用中文来表述代码的逻辑,所以单纯看我的文字也是可以的。

这次我们重点分析的是,在 SYN_SENT 状态下,收到不正确的确认号的 syn+ack 报文是如何处理的。

处于 SYN_SENT 状态下的客户端,在收到服务端的 syn+ack 报文后,最终会调用 tcp_rcv_state_process,在这里会根据 TCP 状态做对应的处理,这里我们只关注 SYN_SENT 状态。

// net/ipv4/tcp_ipv4.c
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
 ...
  
 int queued = 0;
  
  ...
  
 switch (sk->sk_state) {
 case TCP_CLOSE:
  ...
 case TCP_LISTEN:
  ...
 case TCP_SYN_SENT:
    ....
  queued = tcp_rcv_synsent_state_process(sk, skb, th);
  if (queued >= 0)
   return queued;
    ...
 }

可以看到,接下来,会继续调用 tcp_rcv_synsent_state_process 函数。

static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
      const struct tcphdr *th)
{
 ....

 if (th->ack) {
  /* rfc793:
   * "If the state is SYN-SENT then
   *    first check the ACK bit
   *      If the ACK bit is set
   *   If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send
   *        a reset (unless the RST bit is set, if so drop
   *        the segment and return)"
   */
    // ack 的确认号不是预期的
  if (!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_una) ||
      after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt))
      //回 RST 报文
   goto reset_and_undo;

  ...
}

从上面的函数,就可以得知了,客户端在 SYN_SENT 状态下,收到不正确的确认号的 syn+ack 报文会回 RST 报文。

小结

TCP 三次握手中,客户端收到的第二次握手中 ack 确认号不是自己期望的,会发生什么?是直接丢弃 or 回 RST 报文?

回 RST 报文。

什么情况下会收到不正确的 ack(第二次握手中的 ack) 呢?

当客户端发起多次 SYN 报文,然后网络拥堵的情况下,「旧的 SYN 报文」比「新的 SYN 报文」早抵达服务端,此时服务端就会按照收到的「旧的 SYN 报文」回复 syn+ack 报文,而此报文的确认号并不是客户端期望收到的,于是客户端就会回 RST 报文。

完!

用 RFC 文档+源码的方说明我的结论,这么严谨的小林,没谁了吧

相关文章:

  • 一个功能齐全的,多用途管理后台模板
  • 【C语言】三子棋小游戏
  • Python自动化:Windows下不用任务管理器也可以轻松定时执行任务
  • 基于HTML美中华传统文化题材网页项目的设计与实现 (纯HTML+CSS制作中国茶文化网站)...
  • 网络请求+基于Node.js的WebSocket
  • 两例典型的C++软件异常排查实例分享
  • 第30讲:事务的基本概念以及如何实现事务
  • 【C++初阶】类和对象(二)
  • C++位图
  • 人生重开模拟器(Python实现)
  • 数据结构与算法----栈和队列(Stack Queue)
  • 零基础小白学Node-RED(06):网络功能
  • 巧用GitHub Action实现自动化部署Java项目
  • 【UI自动化】给好友发大批量图片
  • 美国信息交换标准代码(ASCII)表
  • “寒冬”下的金三银四跳槽季来了,帮你客观分析一下局面
  • 2018天猫双11|这就是阿里云!不止有新技术,更有温暖的社会力量
  • express + mock 让前后台并行开发
  • iOS动画编程-View动画[ 1 ] 基础View动画
  • js ES6 求数组的交集,并集,还有差集
  • learning koa2.x
  • PHP面试之三:MySQL数据库
  • Python进阶细节
  • Spark RDD学习: aggregate函数
  • spring boot 整合mybatis 无法输出sql的问题
  • storm drpc实例
  • vue+element后台管理系统,从后端获取路由表,并正常渲染
  • 得到一个数组中任意X个元素的所有组合 即C(n,m)
  • 深度学习入门:10门免费线上课程推荐
  • 一道闭包题引发的思考
  • 正则与JS中的正则
  • 长三角G60科创走廊智能驾驶产业联盟揭牌成立,近80家企业助力智能驾驶行业发展 ...
  • ​一、什么是射频识别?二、射频识别系统组成及工作原理三、射频识别系统分类四、RFID与物联网​
  • %check_box% in rails :coditions={:has_many , :through}
  • (13)[Xamarin.Android] 不同分辨率下的图片使用概论
  • (2022 CVPR) Unbiased Teacher v2
  • (4)(4.6) Triducer
  • (poj1.2.1)1970(筛选法模拟)
  • (附源码)ssm跨平台教学系统 毕业设计 280843
  • (力扣)1314.矩阵区域和
  • (算法)求1到1亿间的质数或素数
  • ***监测系统的构建(chkrootkit )
  • 、写入Shellcode到注册表上线
  • ./configure,make,make install的作用
  • .NET Core 网络数据采集 -- 使用AngleSharp做html解析
  • .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况
  • .net6Api后台+uniapp导出Excel
  • .net使用excel的cells对象没有value方法——学习.net的Excel工作表问题
  • .NET委托:一个关于C#的睡前故事
  • @Service注解让spring找到你的Service bean
  • @四年级家长,这条香港优才计划+华侨生联考捷径,一定要看!
  • []指针
  • [BZOJ] 1001: [BeiJing2006]狼抓兔子
  • [C#小技巧]如何捕捉上升沿和下降沿
  • [C++]STL之map