一、问题

最近测试一个音视频项目,APP在第一次流程中,所有信令都走完了,然后断掉连接,清理现场后。第二次重新连接服务,进行第二次业务的时候缺失败了。服务器是没用网卡没用绑定固定IP的云服务器。

测试1:

使用WEB测试,在不同分公司电脑打开是可以连接服务器的,成功。

测试2:

在本地局域网打开APP,然后打开web连接测试,失败。telnet别的端口也失败。

测试3:

在本地局域网打开APP,然后手机使用wifi打开web连接测试,失败。telnet别的端口也失败。

测试4:

在本地局域网打开APP,然后手机使用4G网络打开web连接测试,成功。

测试5:

在别的服务器机器,正常

针对失败情形抓包:

二、排查

1,发现系统没有任何负载,top命令看CPU和网络正常

2,网卡也没有丢包

3,iptables策略也都没问题

4,系统的SYN_RECV连接很少,也没超限

5,系统的文件描述符等资源也都没问题

6,messages和dmesg中没有任何提示或者错误信息

7,通过netstat命令查看系统上协议统计信息,发现很多请求由于时间戳的问题被rejected

root@005027:~# netstat -s | grep timestamp2984 packets rejects in established connections because of timestamp

大概猜测到什么,于是查看内核参数
 

再看正常机器的内核参数:

修改内核参数,重启机器正常

三、分析:

为何在同一个网络,连接服务器会失败(测试1,4),不同网络却是能成功连接服务器(测试2,3)

tcp_timestamps和recycle  选项开启之后,自动开启 per-host 的 PAWS 机制 ,首先PAWS 机制会自动开启,它的作用是防止 TCP 包中的序列号发生绕回。

PAWS 机制:正常来说每个 TCP 包都会有自己唯一的 SEQ,出现 TCP 数据包重传的时候会复用 SEQ 号,这样接收方能通过 SEQ 号来判断数据包的唯一性,也能在重复收到某个数据包的时候判断数据是不是重传的。但是 TCP 这个 SEQ 号是有限的,一共 32 bit,SEQ 开始是递增,溢出之后从 0 开始再次依次递增

per-host 机制:per-host 是对「对端 IP 做 PAWS 检查」,而非对「IP + 端口」四元组做 PAWS 检查。

假如客户端是在同一网络内就悲剧了。如果客户端网络环境是用了 NAT 网关,那么客户端环境的每一台机器通过 NAT 网关后,都会是相同的 IP 地址,在服务端看来,就好像只是在跟一个客户端打交道一样,无法区分出来。测试1,4就是同一网络测试时,失败了。

四、原理,SYN包什么时候会被丢弃:

客户端向服务端发起了连接,但是连接并没有建立起来,通过抓包分析发现,服务端是收到 SYN 报文了,但是并没有回复 SYN+ACK(TCP 第二次握手),说明 SYN 报文被服务端忽略了,然后客户端就一直在超时重传 SYN 报文,直到达到最大的重传次数。

接下来,我就给出我遇到过 SYN 报文被丢弃的两种场景:

  • 开启 tcp_tw_recycle 参数,并且在 NAT 环境下,造成 SYN 报文被丢弃

  • accpet 队列满了,造成 SYN 报文被丢弃

坑爹的 tcp_tw_recycle

TCP 四次挥手过程中,主动断开连接方会有一个 TIME_WAIT 的状态,这个状态会持续 2 MSL 后才会转变为 CLOSED 状态。

在 Linux  操作系统下,TIME_WAIT 状态的持续时间是 60 秒,这意味着这 60 秒内,客户端一直会占用着这个端口。要知道,端口资源也是有限的,一般可以开启的端口为 32768~61000 ,也可以通过如下参数设置指定范围:

 net.ipv4.ip_local_port_range

那么,如果如果主动断开连接方的 TIME_WAIT 状态过多,占满了所有端口资源,则会导致无法创建新连接。

但是 TIME_WAIT 状态也不是摆设作用,它的作用有两个:

  • 防止具有相同四元组的旧数据包被收到,也就是防止历史连接中的数据,被后面的连接接受,否则就会导致后面的连接收到一个无效的数据,

  • 保证「被动关闭连接」的一方能被正确的关闭,即保证最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭;

不过,Linux 操作系统提供了两个可以系统参数来快速回收处于 TIME_WAIT 状态的连接,这两个参数都是默认关闭的:

  • net.ipv4.tcp_tw_reuse,如果开启该选项的话,客户端(连接发起方) 在调用 connect() 函数时,内核会随机找一个 time_wait 状态超过 1 秒的连接给新的连接复用,所以该选项只适用于连接发起方。

  • net.ipv4.tcp_tw_recycle,如果开启该选项的话,允许处于 TIME_WAIT 状态的连接被快速回收;

要使得这两个选项生效,有一个前提条件,就是要打开 TCP 时间戳,即 net.ipv4.tcp_timestamps=1(默认即为 1)。

但是,tcp_tw_recycle 在使用了 NAT 的网络下是不安全的!

对于服务器来说,如果同时开启了 recycle 和 timestamps 选项,则会开启一种称之为「 per-host 的 PAWS 机制」。

首先给大家说说什么是  PAWS 机制?

tcp_timestamps 选项开启之后, PAWS 机制会自动开启,它的作用是防止 TCP 包中的序列号发生绕回。

正常来说每个 TCP 包都会有自己唯一的 SEQ,出现 TCP 数据包重传的时候会复用 SEQ 号,这样接收方能通过 SEQ 号来判断数据包的唯一性,也能在重复收到某个数据包的时候判断数据是不是重传的。但是 TCP 这个 SEQ 号是有限的,一共 32 bit,SEQ 开始是递增,溢出之后从 0 开始再次依次递增

所以当 SEQ 号出现溢出后单纯通过 SEQ 号无法标识数据包的唯一性,某个数据包延迟或因重发而延迟时可能导致连接传递的数据被破坏,比如:

上图 A 数据包出现了重传,并在 SEQ 号耗尽再次从 A 递增时,第一次发的 A 数据包延迟到达了 Server,这种情况下如果没有别的机制来保证,Server 会认为延迟到达的 A 数据包是正确的而接收,反而是将正常的第三次发的 SEQ 为 A 的数据包丢弃,造成数据传输错误。

PAWS 就是为了避免这个问题而产生的,在开启 tcp_timestamps 选项情况下,一台机器发的所有 TCP 包都会带上发送时的时间戳,PAWS 要求连接双方维护最近一次收到的数据包的时间戳(Recent TSval),每收到一个新数据包都会读取数据包中的时间戳值跟 Recent TSval 值做比较,如果发现收到的数据包中时间戳不是递增的,则表示该数据包是过期的,就会直接丢弃这个数据包

对于上面图中的例子有了 PAWS 机制就能做到在收到 Delay 到达的 A 号数据包时,识别出它是个过期的数据包而将其丢掉。

那什么是 per-host 的 PAWS 机制呢?

前面我提到,开启了 recycle 和 timestamps 选项,就会开启一种叫 per-host 的 PAWS 机制。

per-host 是对「对端 IP 做 PAWS 检查」,而非对「IP + 端口」四元组做 PAWS 检查。

但是如果客户端网络环境是用了 NAT 网关,那么客户端环境的每一台机器通过 NAT 网关后,都会是相同的 IP 地址,在服务端看来,就好像只是在跟一个客户端打交道一样,无法区分出来。

Per-host PAWS 机制利用TCP option里的 timestamp 字段的增长来判断串扰数据,而 timestamp 是根据客户端各自的 CPU tick 得出的值。

当客户端 A 通过 NAT 网关和服务器建立 TCP 连接,然后服务器主动关闭并且快速回收 TIME-WAIT 状态的连接后,客户端 B 也通过 NAT 网关和服务器建立 TCP 连接,注意客户端 A  和 客户端 B 因为经过相同的 NAT 网关,所以是用相同的 IP 地址与服务端建立 TCP 连接,如果客户端 B 的 timestamp 比 客户端 A 的 timestamp 小,那么由于服务端的 per-host 的 PAWS 机制的作用,服务端就会丢弃客户端主机 B 发来的 SYN 包

因此,tcp_tw_recycle 在使用了 NAT 的网络下是存在问题的,如果它是对 TCP 四元组做 PAWS 检查,而不是对「相同的 IP 做 PAWS 检查」,那么就不会存在这个问题了。

网上很多博客都说开启 tcp_tw_recycle 参数来优化 TCP,我信你个鬼,糟老头坏的很!

tcp_tw_recycle 在 Linux 4.12 版本后,直接取消了这一参数。

在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:

  • 半连接队列,也称 SYN 队列;

  • 全连接队列,也称 accepet 队列;

服务端收到客户端发起的 SYN 请求后,内核会把该连接存储到半连接队列,并向客户端响应 SYN+ACK,接着客户端会返回 ACK,服务端收到第三次握手的 ACK 后,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到 accept 队列,等待进程调用 accept 函数时把连接取出来。

在服务端并发处理大量请求时,如果 TCP accpet 队列过小,或者应用程序调用 accept() 不及时,就会造成 accpet 队列满了 ,这时后续的连接就会被丢弃,这样就会出现服务端请求数量上不去的现象。

我们可以通过 ss 命令来看 accpet 队列大小,在「LISTEN 状态」时,Recv-Q/Send-Q 表示的含义如下:

  • Recv-Q:当前 accpet 队列的大小,也就是当前已完成三次握手并等待服务端 accept() 的 TCP 连接个数;

  • Send-Q:当前 accpet 最大队列长度,上面的输出结果说明监听 8088 端口的 TCP 服务进程,accpet 队列的最大长度为 128;

如果 Recv-Q 的大小超过 Send-Q,就说明发生了 accpet 队列满的情况。

要解决这个问题,我们可以:

  • 调大 accpet 队列的最大长度,调大的方式是通过调大 backlog 以及 somaxconn 参数。

  • 检查系统或者代码为什么调用 accept()  不及时;

客户端第二次连接失败,SYN包发了,没有收到服务端回 SYN+ACK ,SYN包被丢弃了相关推荐

  1. mysql 57授权失败_MYSQL教程完美解决mysql客户端授权后连接失败的问题

    <MYSQL教程完美解决mysql客户端授权后连接失败的问题>要点: 本文介绍了MYSQL教程完美解决mysql客户端授权后连接失败的问题,希望对您有用.如果有疑问,可以联系我们. MYS ...

  2. mysql 授权 失败_完美解决mysql客户端授权后连接失败的问题

    在本地(192.168.1.152)部署好mysql环境,授权远程客户机192.168.1.%连接本机的mysql,在iptables防火墙也已开通3306端口. 如下: mysql> sele ...

  3. Java线程怎么发送消息_Java客户端Socket如何能在阻塞线程下收到服务端发送来的消息?...

    最近在写Socket客户端的时候遇到点问题 客户端在创建时创建了2个线程 一个监听键盘输入事件,使用的是buffered,当检测到输入完成时写入流发送给服务端. String content = &q ...

  4. 在以TCP为连接方式的服务器中,为什么在服务端设计当中需要考虑心跳?

    https://www.zhihu.com/question/35013918 在以TCP为连接方式的服务器中,为什么在服务端设计当中需要考虑心跳? 这个心跳包除了告知服务端我在线,还有其他作用吗?比 ...

  5. Android客户端之“微服私访”App的系统学习(一) 本地服务端环境的搭建和部署

    前言: 每晚睡觉前都会去看看微信公众号中来自各位大神的干货分享,偶然看到鸿洋大神推荐的一篇有关课工厂的一个公开课 高效Android工程师6周培养计划,根据学习内容,整理成笔记.本系列文章意在详细介绍 ...

  6. java(jfinal) 接入ios苹果内购(连续包月订阅),服务端将二次验证。

    大致流程: 1.ios端进行支付,然后收到苹果的一串数据(也叫收据),然后ios端将其转码为BASE64编码的字符串. 2.ios端请求服务端接口,将数据传给服务端,服务端拿到数据后,通过一系列处理后 ...

  7. 单机100万连接,每秒10万次请求服务端的设计与实现(一) - 前传

    因起 大概两年前,半途接手了一个项目,一个Python写的游戏服务器.倒腾倒腾弄上线后,在一台4核16G的服务器上,TPS不满百,平均响应延迟超百毫秒,勉强抗个500-1000人在线.慢的令人发指.业 ...

  8. 剑网三缘起正在连接服务器,剑网3缘起:因无法服务端无法匹配二测延迟

    原本决定今天启动二测的<剑网3缘起>怀旧服版本,因为官方升级了客户端导致了服务器端无法匹配,没有达到预期的优化效果,所以延迟了本次测试.下面来看看官方对这次延迟做出的公告. 官方公告: 大 ...

  9. 单机100万连接,每秒10万次请求服务端的设计与实现(三) - 变量共享、超线程与高性能队列

    简要构架 前文提到过一个框架性的服务器端架构思路,但没给出系统结构图,这里画个图吧,直观不少: M M M M M M 完成部分IO IO对象争用 M M M 网络IO 数据包分析线程 I/O队列 数 ...

  10. php ini 长连接秒数,php使用webSocket实现Echarts长连接自动刷新的解决方案(2):后端服务端代码返回json数据...

    $address = "127.0.0.1"; $port = 9090; //调试的时候,可以多换端口来测试程序! set_time_limit(0); $sock = sock ...

最新文章

  1. 一样是做鸭,绝味与周黑鸭的利润为何相差那么大?
  2. python 网络爬虫requests模块
  3. php一点按钮就下载功能源码,php实现强制文件下载方法的源码参考
  4. 如何在 ASP.Net Core 中使用 MiniProfiler
  5. MonogoDB 查询小结
  6. Kubernetes 微服务监控体系
  7. 公众号向特定用户主动推送消息_公众号助手——消息不仅可以群发,还不限制次数!...
  8. [译]MediaSession MediaController – Android TV 应用开发教程九
  9. win7桌面图片不显示缩略图问题
  10. 怎么测试本地网页在不同分辨率下电脑显示效果_汇总一波百万高清壁纸站,8K分辨率的都有...
  11. NodeManager 启动一会儿挂掉
  12. Java Web(1)高并发业务
  13. 小议C++中函数的参数的传递
  14. PopupWindwo和AlertDialog的区别
  15. jQuery使用规范总结
  16. 承包一座山能做什么_您为什么没有得到最好的承包商,又能做些什么(第2部分)...
  17. linux install 文件,linux命令安装msi文件
  18. 交互电子白板有哪些特点?电子白板功能介绍
  19. macbook卡在进度条开不了机_mac开机卡在进度条的问题
  20. Linux的网络编程面试题汇总

热门文章

  1. html表格制作实战
  2. 2020亚马逊创新日:深度解读人工智能和机器学习的数字驱动力
  3. C# vb .net实现相机视图效果滤镜
  4. java作品_50幅惊艳的分形艺术作品
  5. excel合并两列内容_Excel 两列合并成一列,又一种快捷方法!
  6. 在蚂蚁金服工作是一种什么体验
  7. 汇编中 rep指令 和 stos指令ollydbg图解
  8. oracle根据身份证号码 计算年龄、性别
  9. 也谈谈内卷化、996和程序员的发展
  10. ads滤波器仿真(3)——三阶发夹线带通滤波器及其优化