后端每日一题 1:说一下三次握手

· 浏览次数 : 0

小编点评

**三次握手的流程** **1. 第一次握手** - 客户端发送 SYN 报文段给服务端。 - 服务端收到 SYN 报文段,并回复 SYN+ACK 报文段。 - 客户端状态变为 SYN_SEND。 **2. 第二一次握手** - 客户端再次发送 SYN 报文段给服务端。 - 服务端收到 SYN 报文段,但没有回复 ACK 报文段。 - 客户端状态变为 SYN_SEND。 **3. 第三一次握手** - 客户端发送 ACK 报文给服务端。 - 服务端没有收到 ACK 报文段,因此认为请求已失效。 - 客户端状态变为 FINISHED。

正文

本文首发于公众号:腐烂的橘子

三次握手的流程

第 1 步 - 初始连接请求 SYN(Synchronize)

  • 服务端状态 LISTEN,客户端向服务端发送一个 SYN 标志位的报文段(TCP segment)
  • 这个报文段包含初始序列号 x,以及最大报文段大小等字段
  • 客户端发送报文后,状态设置为 SYN_SEND

第 2 步 - 服务端回复 SYN—ACK(Synchronize-Acknowledge)

  • 服务端收到 SYN 报文段后,为建立连接分配必要的资源并生成自己的 SYN 初始序列号
  • 服务端发送带有 SYN 和 ACK 的报文段回复给客户端,状态置为 SYN_RCVD
  • 这个报文段有两个序列号,一个是服务器自己生成的初始序列号,另一个是回复序列号,回复序列号用来回复第 1 步客户端发送来的序列号,值是 x + 1

第 3 步 - 客户端回复 ACK(Acknowledge)

  • 客户端接收到 SYN-ACK 报文段后,回复 ACK 报文段
  • ACK 报文段设置了 ACK 报文段且确认了 SYN 序列号,状态进入 ESTABLISHED,服务端收到 ACK 报文段后状态进入 ESTABLISHED
  • 至此,客户端和服务端都交换了各自的 SYN 序列号建立了连接
  • 现在他们可以使用确认的序列号传输数据了

SYN 标志位、ACK 标志位、序列号到底是什么?——TCP 报文段结构

简单了解 TCP 连接之后,我们不免想知道这些标志位是什么,TCP 报文段结构就是使用 TCP 发送数据时报文段时,这个报文段具体的样子。结构如下[1]:


图片来源于:https://www.geeksforgeeks.org/services-and-segment-structure-in-tcp/

TCP 报文首部(header)大小是 20-60 字节(bytes)。40 字节是可选的,所以典型的 TCP 报文首部大小为 20 字节。

这里需要说明一个基本知识:1字节 = 8比特,即 1bytes = 8bits。所以图中上面浅绿色部分共 20 字节,160 比特;下面深绿色部分共 40 字节,320比特。

  • 源端口号(Source Port Address):16 比特
  • 目标端口号(Destination Port Address):16 比特
  • 序列号(Sequence Number):32 比特
  • 确认应答号(Acknowledgement Number):32 比特
  • 头部长度(HLEN,Header Length):4 比特
  • 保留字段(Reserved):6 比特,当前没有用,如果要新增标志位,可以使用这个保留区域
  • 6 个标志位,每个 1 比特,值为 0 或 1:
    • URG:用来指示报文段里存在被上层发送段置为“紧急”的数据,实际上,URG 和 PSH 都没有用到
    • ACK:如果值为 1,代表这个报文段里包含一个确认信息,该信息确认了一个已接收的报文段
    • PSH:和 USG 一样,目前没用到,也是一个紧急数据的指针
    • SYN:序列号标志位,建立连接、传输数据时会用到
    • FIN:终止连接的标志位,用于在断开连接时使用
  • 接收窗口大小(Window Size):16 比特,用于流量控制,用于表示接收方愿意接受的字节数量
  • 校验和(CheckSum):16 比特,该字段保存错误控制的校验和。与 UDP 不同,它在 TCP 中是强制性的
  • 紧急指针(Urgent Pointer):16 比特,该字段仅当 URG 控制标志置位时才有效,用于指向迫切需要的、需要最早到达接收进程的数据。该字段的值与序列号相加即可得到最后一个紧急字节号

以上一共 160 比特,共 20 字节,加上可选的 40 字节,所以一个 TCP 报文的大小处于 20~60 字节。

为什么是三次握手不是两次?

第三次握手是客户端发送确认消息给服务端,主要原因是为了通知服务端,服务端发送的初始序列号已被客户端确认。

不同握手阶段的报文丢失,会发生什么

因为一共有三个阶段,如果每个阶段都发生丢包,有以下三种情况:

  1. 第一次握手,客户端发送的请求包没有到达服务端
  2. 第二次握手,服务端回复的 SYN+ACK 包没有到达客户端
  3. 第三次握手,客户端发送的 ACK 包没有到达服务端

TCP 在丢包后都会触发重传,重传有超时重传、快速重传等很多类型,我们会在后面的文章中详细描述。这里触发的重传,都是使用最基本的“超时重传”的机制。所以上面的三种情况都会触发超时重传,区别只是客户端还是服务端来重传、和重传什么内容。

第一次握手失败,会发生什么?

第一次握手是客户端将 SYN 包发送到服务端,客户端状态变为 SYN_SEND。如果此阶段丢包,服务端没有收到来自客户端的 SYN 包,一直处于 LISTEN 状态。客户端这边则会触发超时重传机制,在发送 SYN 包的 x 秒后,重新发送一次 SYN 包。

超时时间 x 的值是多少?

其中,x 是超时时间,这个时间是由操作系统内核代码 /include/net/tcp.h 中的 TCP_TIMEOUT_INIT 字段定义的[2],例如 linux v4.14.12 中的定义如下:

#define TCP_TIMEOUT_INIT ((unsigned)(1*HZ))	/* RFC6298 2.1 initial RTO value	*/

这里的超时时间是 1*HZ,取决于 TCP/IP 规范文档 RFC6298 2.1 中的初始 RTO (RTO,Retransmission Time Out,超时重传时间)时间,RFC6298 2.1 中的描述如下[3]:

“Note that the previous version of this document used an initial RTO of 3 seconds [PA00]. ”这句话表明 RTO
的初始值是 3 秒,所以超时时间是 3 秒,在发送 SYN 包的 3 秒后,会重新发送一次 SYN 包。

我们知道了超时时间是 3 秒后会再次发送 SYN 包,假如这个包再次超时,时间也是 3 秒吗?一共要重传多少次呢?

我们先来看重传次数,重传次数可以在 linus 系统中使用命令查看:

sysctl net.ipv4

输入命令后会展示很多系统配置,从中我们可以看到两个关于 SYN 的配置:

net.ipv4.tcp_syn_retries = 6
net.ipv4.tcp_synack_retries = 5

其中 net.ipv4.tcp_syn_retries = 6 就是我们想要的超时重传次数,这时超时重传 6 次后会停止重试,重试 6 次后如果还是没有收到服务端发送的 ACK,客户端就会停止请求连接。

假如这个包再次超时,时间也是 3 秒吗?这个时间是不固定的,因为这个超时时间是基于 RTO 来计算的,RTO 的计算方式在 RFC6298 文档(或其他版本文档)中有定义,可以自行研究一下。总体来说,RTO 的时间会逐渐变长。

第二次握手失败,会发生什么?

第二次握手时,服务端会返回 ACK+SYN 报文。如果报文丢失,意味着客户端的请求没有被确认,且服务端的序列号也没有发送到客户端。因此会有如下结果:

  1. 客户端由于没有收到第一次握手的 ACK 报文,所以客户端会重传 SYN 报文
  2. 服务端由于没有收到客户端第三次握手的 ACK 报文,所以会重传 SYN+ACK 报文。

在前面我们知道 net.ipv4.tcp_syn_retries = 6 代表 SYN 会重传 6 次,即第一次握手会重试 6 次,net.ipv4.tcp_synack_retries = 5 就代表第二次握手的 SYN+ACK 报文会重试 5 次,即第二次握手会重试 5 次。

第三次握手失败,会发生什么?

第三次握手是客户端发送 ACK 报文给服务端,目的是让服务端知道,客户端已经确认了服务端的初始序列号。如果第三次握手失败,则服务端收不到确认,所以还是会重传 ACK + SYN 报文,根据 net.ipv4.tcp_synack_retries = 5,仍然会重试 5 次。

总结

知其然还要知其所以然,无论第几次握手失败,本质上还是利用了超时重传的机制。如果不了解 SYN、ACK 和序列号的关系,那就说明不了解 TCP 报文段的格式。其实重传机制、RTO、RTT 等等这些概念都需要我们一步步了解之后才能更深入理解三次握手,以及网络中的其他流程机制。

参考

  1. https://www.geeksforgeeks.org/services-and-segment-structure-in-tcp/
  2. https://elixir.free-electrons.com/linux/v4.14.12/source/include/net/tcp.h
  3. https://datatracker.ietf.org/doc/html/rfc6298

与后端每日一题 1:说一下三次握手相似的内容:

后端每日一题 1:说一下三次握手

本文首发于公众号:腐烂的橘子 三次握手的流程 第 1 步 - 初始连接请求 SYN(Synchronize) 服务端状态 LISTEN,客户端向服务端发送一个 SYN 标志位的报文段(TCP segment) 这个报文段包含初始序列号 x,以及最大报文段大小等字段 客户端发送报文后,状态设置为 SY

后端每日一题 2:DNS 解析过程

本文首发于公众号:腐烂的橘子 本文梗概: DNS 是什么,有什么作用 一条 DNS 记录是什么样的 DNS 域名解析原理 DNS 服务器如何抵御攻击 DNS 是什么,有什么作用 DNS(Domain Name System)是一种应用层协议,用于映射域名和 ip 地址。 为什么要做映射呢?就像可以用

每日一题:无感刷新页面(附可运行的前后端源码,前端vue,后端node)

1、前言 想象下,你正常在网页上浏览页面。突然弹出一个窗口,告诉你登录失效,跳回了登录页面,让你重新登录。你是不是很恼火。这时候无感刷新的作用就体现出来了。 2、方案 2.1 redis设置过期时间 在最新的技术当中,token一般都是在Redis服务器存着,设置过期时间。只要在有效时间内,重新发出

买条新内存给台式机扩容,没想到出现玄学花屏

背景 我目前的配置是i5-8400,16G内存(两条威刚8G 2400) 然后在日常使用中,16G内存已经捉襟见肘了,无论是Android开发还是后端开发,每次编译都卡得很 正好双十一,就想着买条16G内存来扩容,组个32G的双通道。 某东看了一圈,2400的16G内存基本绝迹了,只能选择2666的

[转帖]Etcd+Confd实现Nginx配置文件自动管理

https://www.cnblogs.com/zhengchunyuan/p/9681954.html 一、需求 我们使用Nginx做七层负载均衡,后端是Tomcat。项目采用灰度发布方式,每次项目升级,都要手动先从Nginx下摘掉一组,然后再升级这组,当项目快速迭代时,手动做这些操作显然会增加部

Nginx的负载均衡策略

Nginx的负载均衡策略 共六种: 轮询、权重、ip_hash、least_conn、fair、url_hash 1、轮询(Round Robin)负载均衡策略:这是一种基本的负载均衡策略,将请求顺序转发给每个后端服务器,每个后端服务器依次处理请求,而轮询正是按照这样的方式平均地为每个后端服务器分配

每个后端都应该了解的OpenResty入门以及网关安全实战

简介 在官网上对 OpenResty 是这样介绍的(http://openresty.org): “OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 We

开源分布式任务调度系统就选:DolphinScheduler

分布式任务调度这个话题是每个后端开发和大数据开发都会接触的话题。因为应用场景的广泛,所以有很多开源项目专注于解决这类问题,比如我们熟知的xxl-job。 那么今天要给大家推荐的则是另一个更为强大的开源项目:DolphinScheduler 介绍 DolphinScheduler是一款开源的分布式任务

[转帖]优秀后端都应该具备的开发好习惯

https://juejin.cn/post/7157508782874968094 前言 大家好,我是捡田螺的小男孩。 记录一下一个优秀的后端开发程序员,应该有哪些好的开发习惯。 公众号:捡田螺的小男孩 github地址,感谢每颗star:github 1.注释尽可能全面,写有意义的方法注释 接口

Linux服务器使用Redis作为数据缓存,并用log4j2进行日志记录

前言 个人网站使用Vue作为前端,SpringBoot作为后端,MySQL作为数据库,但前端每次请求都会从MySQL数据库中读取数据,而MySQL数据库的数据是存储于服务器磁盘中,所以响应速度有一定影响。之前了解过一点Redis数据库,该数据库数据存储于内存中(也可以持久化于磁盘中),数据读取速度就