Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2168561
  • 博文数量: 317
  • 博客积分: 5670
  • 博客等级: 大校
  • 技术积分: 3677
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-10 17:51
文章分类

全部博文(317)

文章存档

2016年(2)

2015年(44)

2014年(68)

2013年(42)

2012年(23)

2011年(51)

2010年(67)

2009年(17)

2008年(3)

分类: LINUX

2015-06-03 18:05:17

最近一个长连接服务经常被反馈连接失败,刚开始怀疑是网络问题,也就没有细查。今天仔细抓包分析了一下,原来碰到了在开启tcp_tw_recycle和tcp_timestamps的机器上,当多个客户端使用同一个外网IP( NAT)时可能出现连接建立不成功的坑,具体表现为客户端发送了SYN 包给服务器,服务器也收到了,但就是不回复SYN+ACK 给客户端,从而导致客户端重传SYNC,直至一分钟左右才能成功。

从客户端这边抓包的结果是这样的:大量SYN重传:

QQ截图20140529095320

期初怀疑是网络不稳定导致的,因为公司大量机器连接到服务器,所以抓包难度比较大,也就不好在服务器抓包分析。最近问题不断出现,也就开始怀疑一定什么地方出了问题了。

首先用电脑建立个无线wifi, 然后使用2个手机,一个Android,一个IPone都连接到这个wifi下面,在电脑上抓包, IPhone先连接上服务器的X端口,然后再用Android去连接,发现android总是连接不上,出现SYN重传的情况。

所以断定应该是2着的SYN包有不一样的地方,如下图可以看出,2个地方不一样,WS 也就是window scaling, 窗口扩大选项,这个应该没关系,剩下的一个就是TSVal,也就是timestamp value, 客户端的时间戳,本身这个时间戳2个端不一样是没问题的,但悲剧的是他们公用一个外网IP,使用NAT网络连接的,所以他们到服务器的时候,服务器首先接受到一个IPhone的比较大的TSVal, 记录起来,然后碰到Android的TSVal的时候发现这个值特别小,所以认为这是一个错误的数据包,于是忽略了!

简单啰嗦一下服务器这么做的原因, 由于可能之前系统初始化的时候没注意,设置了 /proc/sys/net/ipv4/tcp_tw_recycle (默认关闭的) 和 /proc/sys/net/ipv4/tcp_timestamps (默认打开的), 前者用来快速回收处于TIME_WAIT的连接,后者用来支持RTT的来回时间计算。

关于TIME_WAIT状态不多介绍,注意这个状态只在主动关闭的一方才会出现,但是很多不明真相的同学总会喜欢不管如何,都设置这个参数,其实像web服务器这样的,开启了keepalive的服务器,是不需要去care这个状态的,因为一般都是客户端主动关闭连接,所以是客户端的责任区处理TIME_WAIT。

简单来说,tcp_tw_recycle  机制允许协议不需要真的等待2个最大段生存时间MSL 那么长,就可以关闭一个连接了,只需要等待2个数据包来回时间,这个相对很短,所以TIME_WAIT状态的连接就可以及时回收了,免得占用系统资源。但2*MSL改为了2*RTT, 那么问题很明显,可能出现数据包错乱,比如被动关闭一方的FIN迟迟没有到来,服务器这边会回收这个连接,然后之后的新连接可能就会复用了这个端口port信息,然后突然之间客户端的老的FIN到达了服务器,然后服务器以为这个FIN包对应的Port正好是刚刚建立的新连接的一个FIN包,于是服务器就把新连接给干掉了·····

也就是说,本身TCP协议规定了,主动关闭一方必须等待2*MSL 的时间,可能长达几分钟,但你现在改为2*RTT,明显短太多了,会出问题的。所以怎么办呢?

由于我们在/proc/sys/net/ipv4/tcp_tw_recycle设置后,会清除掉TCP的四元组信息,释放内存,所以没法进行端口判断了,但是IP还是可以的,所以退而求其次:比较这个IP的最新更新的时间戳,如果碰到一个很老的时间戳TSVal的数据包过来,那么服务器认为这应该是之前很老的数据包的重传,只能忽略它。因此实践中协议采取的策略是:60秒内同一个IP建立2个连接的话,后面一个SYN连接的时间戳必须大于之前的SYN里面的TSVal,否则服务器会认为这是一个老连接的数据包,忽略它

从而导致客户端明明发送了SYN,服务端却偏偏就是不回复SYN-ACK。

遇到这个坑的人不少,比如:

1. :  ;

2.火丁笔记   ;

3. tcp_tw_recycle和tcp_timestamps导致connect失败问题 ;

类似的不少人碰到,就不多重复了。解决方法一般就是关闭/proc/sys/net/ipv4/tcp_tw_recycle 就OK了,不用这么激进的去回收TIME_WAIT。

解决办法:
1. 修改proc:    echo 0 > /proc/sys/net/ipv4/tcp_tw_recycle; 建议别去设置这个,因为重启后又没了,没必要给自己挖坑。

2. 或者更正规的的,修改/etc/sysctl.conf,然后sysctl -p, 指令为:

echo “net.ipv4.tcp_tw_recycle = 0″ >> /etc/sysctl.conf  ; sysctl -p ;

官方文档上明确这么说了:

tcp_tw_recycle – BOOLEAN
Enable fast recycling TIME-WAIT sockets. Default value is 0.
It should not be changed without advice/request of technical
experts.

再次重复一下:TIME_WAIT 只在主动关闭的一端出现,所以在前端WEB机器上设置这些选项是没用的····。

阅读(1624) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~