tcp 3次握手

tcp

  • 面向连接;可靠的传输 协议
  • 输入输出确认
  • 确认机制 ack

连接建立

在这里插入图片描述

第一次握手时的 seq=x

  • x 随机生成

在数据传输时 seq

  • 此时的 seq 是代表 data 部分第一位的位置
  • 抛开第一次生成的 Sequence number,后续的 TCP 头中的 Sequence number 都指的是 data 部分第一位的序号。
  • 比如:
    • 我这次发送的 Sequence number 为 100,数据长度为 50,
    • 那么我下一次发送的 Sequence number 就应该是 150,再假定数据长度为 100,
    • 如果要进行第三次发送,那么 Sequence number 的值应为 250。

下面的简图 简单说明了 发送两个连续的 segment 的 Sequence number 变化情况,忽略了TCP头,TCP头 并不计入 length
在这里插入图片描述

ack

  • Acknowledgement number
  • 回复收到的最大 Sequence number + 1,表示期望收到的 Sequence number 的值。
  • 和上面的 seq 一样的道理,比如收到 seq 为 100,数据长度为 70,那么我们就回复 Acknowledgement number = 170(ack=170)。

seq 和 ack 的 重要性

  • 根本性问题在于 网络传输是不可靠的
  • 网络可能延时或者丢包,没有seq、ack 则 无法正确拼接数据,想想都可怕啊
  • 比如:你看一个电影,两个数据包,后发送的先到达了,想像一下那场景
  • 有了 seq 和 ack ,自己发送的 seq 被 ack 说明对方 收到了,

tcp 如果改为2次握手的问题

  • 网络中延时数据包 d1 到达后,如果 server 直接建立连接就会导致 server 端网络资源占用且无法释放,当然也不会一直持续,下面的保活计时器解决这个问题
  • d2 分组丢失,则 client 将不知道 server 是否准备好,将忽略 server 发来的任何其他 数据包 ,一直等待 d2(连接确认应答数据分组)。

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

  • TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。
  • 服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。
  • 若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

连接释放

在这里插入图片描述

  • 因为TCP连接是双向的,所以在关闭连接的时候,两个方向各自都需要关闭。
  • 先发FIN包的一方执行的是主动关闭;后发FIN包的一方执行的是被动关闭。主动关闭的一方会进入TIME_WAIT状态,并且在此状态停留两倍的MSL时长。
  • 由 client 或 server 任何一方先发起 结束请求都可以

seq

  • seq=u,是上次数据传输的下个seq 不是随机来的
  • w 与 v 是否相等,取决于 中间是否有数据传输

为什么4次

  • 建立连接时实际 d2 次是把(SYN,ACK )一起发送给连接发起方了,所以少了一次
  • 释放连接时,因为如果 B 在收到FIN时,可能还有数据未传输完,则先回复关于 FIN 的 ACK,告知对方我已经知道你要断开了。则等待传输完毕后,被断开方 B 再发送 FIN,告知自己也已经可以断开连接。
  • 如果 B 在 收到 FIN 后也确实时没有数据要传输了,也可以变成 3次 挥手

MSL

  • MSL指的是报文段的最大生存时间,如果报文段在网络活动了MSL时间,还没有被接收,那么会被丢弃。
  • 关于MSL的大小,RFC 793 协议中给出的建议是两分钟,不过实际上不同的操作系统可能有不同的设置,
  • 以Linux为例,通常是半分钟,两倍的MSL就是一分钟,也就是60秒,并且这个数值是硬编码在内核中的,也就是说除非你重新编译内核,否则没法修改它。

TIME_WAIT状态存在的必要性。

  • 为什么主动关闭的一端不直接进入closed状态,而是要先进入time_wait并且停留两倍的MSL时长呢?这是因为TCP是建立在不可靠网络上的可靠协议。如果主动关闭的一端收到被动关闭一端的发出的FIN包后,返回ACK包,同时进入TIME_WAIT,但是由于网络的原因,主动关闭一端发送的ACK包可能会延迟,从而触发被动关闭一方重传FIN包,这样一来一回极端情况正好是2MSL。
  • 如果主动关闭的一端直接close或者不到两倍MSL时间就关闭,那么被动关闭发出重传FIN包到达,可能出现的问题是:
    • 旧的连接不存在,系统只能返回RST包;
    • 新的TCP连接已经建立,延迟包可能会干扰新连接。这都可能导致TCP不可靠。

time-wait 等待 2MSL

  • 使本次连接中网络中的所有数据包都消失,不影响下次连接
  • 如果 d4 数据包丢失, server 将会在 2msl 时间内重发 d3 数据包,client 重新响应发送 d4 数据包,并重新计时 2msl 保证双方都关闭连接
  • MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。

TIME_WAIT过多的危害

  • 在生产过程中,如果服务器使用短连接,那么完成一次请求后会主动断开连接,就会造成大量time_wait状态。

  • 因此我们常常在系统中会采用长连接,减少建立连接的消耗,同时也减少TIME_WAIT的产生,但实际上即使使用长连接配置不当时,当TIME_WAIT的生产速度远大于其消耗速度时,系统仍然会累计大量的TIME_WAIT状态的连接。

  • TIME_WAIT状态连接过多就会造成一些问题:

    • 如果客户端的TIME_WAIT连接过多,同时它还在不断产生,将会导致客户端端口耗尽,新的端口分配不出来,出现错误。
    • 如果服务器端的TIME_WAIT连接过多,可能会导致客户端的请求连接失败。
  • 案例:

    • 案例一:将nginx作为反向代理时,后连tomcat等服务器。测试中不同并发压力下多次、反复出现nginx服务器端口资源耗尽的问题。
    • 表现为nginx服务器出现大量time_wait状态连接,端口资源耗尽(nginx报错:cannot assign requested address )。
    • 首先检查nginx开启了长连接keepalive,但是系统仍然出现了大量的TIME_WAIT,这就和之前提到的当系统产生TIME_WAIT的速度大于其消耗速度时,就会累计TIME_WAIT。
    • 原因是:keepalive取配置太小,将其增大后问题得以解决。(PS:nginx总的 keepalive连接池大小 = keepalive取值 * nginx worker数)
    • 案例二:某应用其中一层系统架构Nginx+Tomcat,客户端发出的请求为HTTP HEAD,客户端TPS有段时间接近为0,返回Connection time out错误。
    • 观察大部分错误请求响应时间刚好是30s,这正好是nginx的连接超时时间配置,Tomcat没有收到这些错误请求,这意味着请求Nginx连接Tomcat都没有成功。这是为什么呢?Tomcat的连接池不够用吗? 实际上Tomcat work线程不到200个,远小于MaxThread(1024)的值,同时还观察到Tomcat上的TIME_WAIT连接数量不正常,达到了近两万个。
    • 系统是设置了长连接的,为什么还有这么多TIME_WAIT,难道长连接没有生效吗?
    • Nginx作为反向代理,长连接配置主要有三项,
    • upstream 中的 keepalive 设置单个worker最大请求数,
    • 参数proxy_http_version 1.1强制转换为http1.1协议(默认支持长连接),
    • proxy_set_header Connection 将请求头部 connection 为空(http1.0请求默认connection头部为close)
    • Tomcat端增加配置maxKeepAliveRequests=“10000”,表示一个连接上最大请求数达到10000才会断开。
    • 定位长连接问题,最简单直接的方法就是抓包,通过wireshark分析Nginx和Tomcat直接连接果然没有生效,一条连接只处理了一个请求。
    • 为什么设置了长连接相关的配置,还是没有生效呢?经过排查发现,proxy_http_version 1.1; proxy_set_header Connection “” 这两项配置放在Nginx的Http域中,实际上他们要放在server域才会生效,将其位置修改后,长连接生效了,所有问题都解决了。
    • 但是我们不禁会 疑问TIME_WAIT出现在Tomcat而不是在Nginx上?从抓包可以看出Nginx发送给Tomcat包头部Connection为close,所以Tomcat在处理完head请求后就主动关闭,所以TIME_WAIT出现在Tomcat服务器。 配置修改后,问题解决了,TPS也上去了,之前出现的连接失败问题也没有了。
    • 但是为什么 Tomcat 服务器上的 TIME_WAIT 过多会导致Nginx连接失败呢?
    • 理论上说,服务器只监听在一个端口,但是会new出很多socket去处理请求,难道是socket不够用吗?
    • 再观察资源使用发现虽然TIME_WAIT连接数多,但是句柄数并不多,而socket的数量是受制于句柄数。那真正的原因是什么呢?
    • 在系统TIME_WAIT较多时,dmesg 系统出错日志为:nf_conntrack: table full, dropping packet
    • 真相大白了,conntrack 表用于记录每个连接的状态,在tcp协议中用 源ip/port+目的ip/port 唯一标识一个连接。记录TCP连接状态的表满了导致请求失败。查看系统ip_contrack_max配置为: 65536,在极端的情况下超出了其配置所有导致连接失败。

控制TIME_WAIT数量

  • 通过以上的 TIME_WAIT 问题,我们可以看到 TIME_WAIT 这个状态不存在不行,但是过多就对系统会造成困扰。那么我们应该如何控制TIME_WAIT的数量呢?

  • 1、长连接

    • 对于反向代理和应用服务器,最好是要配置成支持keepalive长连接,否则在系统并发增加时会导致一系列的连接问题。对于nginx+tomcat长连接的配置前面有一些介绍可以参考,其它服务器一般也是提供支持长连接配置的,设置后建议抓包测试验证
  • 一般来说长连接设置正确了TIME_WAIT数量不会暴涨,但是长连接最大请求数也是有效的,但如果应用的处理速度很快导致TIME_WAIT的产生的速度远快于 TIME_WAIT 的消耗速度时系统就会累计TIME_WAIT状态连接。这时候可能就要修改一些系统配置了。

  • 2、ip_conntrack

    • 用于跟踪TCP连接。一旦激活了此模块,就能在系统参数里发现很多用来控制网络连接状态超时的设置,其中自然也包括TIME_WAIT,默认ip_conntrack_max最大为65536,可以将其设置得更大一些。一般不建议此模块,如果系统安装使用iptable会启动该模块。
  • 3、tcp_tw_recycle

    • 在网上搜索TIME_WAIT问题的解决方法,大多都会提到这个参数,不过官方网站上不建议开启这个参数,原因是会导致一些安全问题。例如,当多个客户端通过NAT方式联网并与服务端交互时,服务端看到的是同一个IP,由于这些客户端的时间戳可能存在差异,所以从服务端的视角看,便可能出现时间戳错乱的现象,进而直接导致时间戳小的数据包被丢弃。
  • 4、tcp_tw_reuse

    • 当创建新连接的时候,如果可能的话会考虑复用相应的TIME_WAIT连接。官方文档里提到的是如果从协议视角看它是安全的,那么就可以使用。这个很难判定这个参数是否应该开启,不到万不得已的时候,即使我们要开启这个参数复用连接,也应该在连接的发起方使用,而不能在被连接方使用。
  • 5、tcp_max_tw_buckets

    • 用于控制TIME_WAIT总数。这个选项是为了阻止一些简单的DoS攻击,平常不要人为的降低它。如果TIME_WAIT已经成为最棘手的问题,那么即便此时并不是DoS攻击的场景,也可以尝试通过设置它来减少TIME_WAIT数量。

tcp header

在这里插入图片描述

标志位含义
SYN请求建立连接,并在其序列号的字段进行序列号的初始值设定。建立连接,设置为1。
ACK确认号是否有效,一般置为1。
FIN希望断开连接。
URG紧急指针是否有效。为1,表示某一位需要被优先处理。
PSH提示接收端应用程序立即从TCP缓冲区把数据读走。
RST对方要求重新建立连接,复位。

参考文章:
链接:https://blog.csdn.net/qq_38950316/article/details/81087809

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值