被印证的感觉,真爽!

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
简介: 神一般的抓包图

大家好,我是小林。

之前我在公众号解答了一位读者面试腾讯的面试题,问题如下:

1.jpg

当时我看到这个问题也没有思路,后面我就啃了 TCP 源码,把这个问题搞清楚了,就写了这篇文章:TCP 四次挥手收到乱序的 FIN 包会如何处理?

针对这个问题,我也没办法用实验来验证我的结论,所以当时结论是基于啃 TCP 源码得出来的。

但是,就在昨天!

有位读者在工作中抓到跟这个面试题场景类似的抓包图,我看了下,现象跟我之前啃 TCP 源码得出的结论是符合的。

这种被印证的感觉真爽!

我觉得这个案例还是挺有意思的,因为很好的说明是 TCP 传输协议是按序接收的。

所以,先来回顾腾讯一面的这个问题,再来看看跟这个问题相似的抓包图。


回顾问题


这道鹅厂的网络题可能是提问的读者表述有问题。

因为如果 FIN 报文比数据包先抵达客户端,此时 FIN 报文其实是一个乱序的报文,此时客户端的 TCP 连接并不会从 FIN_WAIT_2 状态转换到 TIME_WAIT 状态。

5.png

因此,我们要关注到点是看「在 FIN_WAIT_2 状态下,是如何处理收到的乱序的 FIN 报文,然后 TCP 连接又是什么时候才进入到 TIME_WAIT 状态?」。

我这里先直接说结论:

在 FIN_WAIT_2 状态时,如果收到乱序的 FIN 报文,那么就被会加入到内核中的「乱序队列」,并不会进入到 TIME_WAIT 状态。

等再次收到前面被网络延迟的数据包时,会判断乱序队列有没有数据,然后会检测乱序队列中是否有可用的数据,如果能在乱序队列中找到与当前报文的序列号保持的顺序的报文,就会看该报文是否有 FIN 标志,如果发现有 FIN 标志,这时才会进入 TIME_WAIT 状态

我也画了一张图,大家可以结合着图来理解。

6.png

TCP 源码分析这里我不再说明了,之前也分析过了,感兴趣的详见:TCP 四次挥手收到乱序的 FIN 包会如何处理?


神一般的抓包图


下图是昨天一位读者发给我的抓包图,图中的异常情况,跟前面这个问题的现象有点类似:

7.jpg

你可能会有疑问为什么 TCP 握手时,双方的 seq 都是 0 开始的?这个是抓包图做了优化,显示的是相对值,而不是真实值,显示相对值方便分析。

为了方便文字描述,我针对异常部分的报文进行编号如下:

89.png

图中端口号为 11710 的为客户端,端口号为 8080 的为服务端。另外,编号 4 是客户端发送的 http 请求,抓包图没有显示 TCP 信息,这里文字补充下:编号 4 数据报文 seq = 1,ack = 1,len = 27

编号 6 是 FIN 报文,也就是服务端向客户端发送的 FIN 报文(第一次挥手),但是是一个乱序的 FIN 报文,因为从编号 4 报文中的 ack = 1 知道,客户端期望下一次收到的报文的序列号为 1,而当前收到的 FIN 报文的 seq = 177,这并不是客户端下一次期望收到的报文,所以是乱序的。

客户端收到乱序 FIN 报文后,并不会从 establish 转为 close_wait 状态,而是把这个乱序的 FIN 报文放到内核中的乱序队列。因为如果这时候就进入了 close_wait 状态,就会马上发送 FIN 报文了(第三次挥手),而不会有客户端后面发送的编号 8 和 9 报文的事情了。

编号 8 是应答报文,是客户端对编号 6 乱序 FIN 报文的应答报文,可以看到这个应答报文中 seq = 28,ack=1,因为并不是我期望的下一个报文,所以应答报文中 ack 还是为 1。

编号 7 是数据报文,也就是服务端向客户端发送的数据报文,该报文 seq = 1,ack=28,len=176,因为 seq 为 1,所以是客户端期望收到的报文。客户端收到该报文后,就回了编号 9 应答报文,此应答报文 seq = 28,ack = 178,其中 ack = 178 是告诉服务端:“你发的 seq = 178 之前(不包括seq=178)的报文,我都收到了,我下次期望收到的报文的 seq 为 178”。

客户端收到编号 7 数据报文时还会做一件事情,会检测「乱序队列」中是否有可用的数据,如果能在乱序队列中找到与当前收到报文的序列号「保持的顺序」的报文,就把处于乱序队列的报文移到可以被正常处理的数据队列。比如,这次的案例中,编号 7 报文 seq = 1,len=176 的数据范围是 1~176,而乱序队列中的 FIN 报文的 seq = 177,这两个报文的 seq 正好是保持顺序的,所以会把  FIN 报文从乱序队列中拿出来一起处理,然后发现有 FIN 标志,于是就会转换状态。

所以,客户端在应答完编号 7 数据报文后,就立马发送 FIN 报文了(第三次挥手),接着服务端应答了该报文,至此四次挥手结束。

这个抓包图跟前面这个腾讯的面试题有一点差异,差异在于:

  • 腾讯的面试题是在 FIN_WAIT_2 状态下收到乱序的 FIN 报文(第三次挥手);
  • 抓包图是在 establish 状态收到了乱序的 FIN 报文(第一次挥手);

上面这两种 TCP 状态,收到乱序的 FIN 报文,并不会立即转换状态,只会被内核放到一个乱序队列里。等收到一个序列号符合「接收方」期望收到的序列号的数据包时,会检测「乱序队列」中是否有可用的数据,如果能在乱序队列中找到与当前收到报文的序列号「保持的顺序」的报文,就会看该报文是否有 FIN 标志,如果发现有 FIN 标志,就会转换状态

所以,从这里可以看到, TCP 传输协议是按序接收,如果收到一个乱序的报文时,并且在接收窗口范围内(序列号超过接收窗口范围外的报文就会被丢弃),就会缓存在内核中的乱序队列,不做其他处理。等收到能与乱序队列中报文的序列号保持顺序的报文,才会一起被处理。

TCP 层必须保证收到的字节数据是完整且有序的,所以如果序列号较低的 TCP 报文在网络传输中丢失了,即使序列号较高的 TCP 报文已经被接收了,应用层也无法从内核中读取到这部分数据。

举个例子,如下图:

8.jpg

图中发送方发送了很多个 packet,每个 packet 都有自己的序号,你可以认为是 TCP 的序列号,其中 packet 3 在网络中丢失了,即使 packet 4-6 被接收方收到后,由于内核中的 TCP 数据不是连续的,于是接收方的应用层就无法从内核中读取到,只有等到 packet 3 重传后,接收方的应用层才可以从内核中读取到数据。

完!

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
6月前
|
存储 Java
【JVM】 程序计数器(Program Counter Register)
【JVM】 程序计数器(Program Counter Register)
227 1
|
6月前
|
索引
15. 索引是越多越好嘛? 什么样的字段需要建索引, 什么样的字段不需要 ?
是否越多索引越好?并非如此。应根据需求建索引:主键自动索引,频繁查询、关联查询、排序、查找及统计分组字段建议建索引。但表记录少,频繁增删改操作,频繁更新的字段,以及使用频率不高的查询条件则不需要建索引。
115 0
|
5月前
|
容器 Cloud Native 安全
CTF本地靶场搭建——基于阿里云ACR实现动态flag题型的创建
本文介绍了如何利用阿里云ACR服务创建动态flag题型。阿里云容器镜像服务ACR是一个支持 OCI 标准的云原生制品托管和分发平台,提供全球化加速、大规模分发等功能,简化云原生应用交付。由于dockerhub访问不便,文章建议使用ACR作为替代。步骤包括在虚拟机内创建【GZCTF】->【WEB】->【src】文件夹,编写index.php和flag.sh文件,然后创建Dockerfile。接着,用户需在阿里云注册并使用ACR,构建、推送镜像,并在靶场部署动态容器。通过ACR,可以实现不同账号看到不同flag的动态更新,完成了动态flag题型的创建。
|
缓存 NoSQL 数据库
如何保证缓存(redis)与数据库(MySQL)的一致性
【说明】  对于热点数据(经常被查询,但不经常被修改的数据),我们可以将其放入redis缓存中,以增加查询效率,但需要保证从redis中读取的数据与数据库中存储的数据最终是一致的。本文基于“孤独烟”与“58沈剑”两位的文章,针对一致性的问题进行了汇总总结,两位的原文链接见文末。
25245 3
|
6月前
|
存储 SQL 关系型数据库
掌握高性能SQL的34个秘诀🚀多维度优化与全方位指南
掌握高性能SQL的34个秘诀🚀多维度优化与全方位指南
|
6月前
|
并行计算 安全 Java
深入理解Java并发编程:从基础到高级
【2月更文挑战第30天】 本文将深入探讨Java并发编程的核心概念和技术,包括线程、锁、同步、并发集合等。我们将从基础知识开始,逐步深入到高级主题,如Fork/Join框架、CompletableFuture和反应式编程。通过本文,你将能够理解并发编程的重要性,掌握Java中实现高效并发的关键技术和方法。
|
并行计算 Java 应用服务中间件
JUC并发编程超详细详解篇(一)
JUC并发编程超详细详解篇
1634 1
JUC并发编程超详细详解篇(一)
|
前端开发 数据可视化 定位技术
百度地图开发:根据指定手绘图纸照片行政区划自定义绘制对应区域边界生成geoJOSN的解决方案
百度地图开发:根据指定手绘图纸照片行政区划自定义绘制对应区域边界生成geoJOSN的解决方案
413 0
百度地图开发:根据指定手绘图纸照片行政区划自定义绘制对应区域边界生成geoJOSN的解决方案
|
安全 Java 编译器
java(Class 常用方法 获取Class对象六种方式 动态和静态加载 类加载流程)
java(Class 常用方法 获取Class对象六种方式 动态和静态加载 类加载流程)
817 0
|
缓存 监控 数据库连接
CPU飙高排查方案与思路
当CPU飙高时,可能是由于程序中存在一些性能问题或者死循环导致的。以下是一些排查CPU飙高的方案和思路
863 0