昨天下午跟同事讨论TCP挥手断开的细节,越发感到TCP协议真的是非常令人讨厌,这个协议已经成了人们装逼的谈资,就是因为它非常复杂,且毫无确定性可言!如果你能说出它的任何细节方面的前因后果,那你一定就是牛人了,但这其实毫无意义。

如果你阅读TCP的诸多RFC,然后对比着4.4 BSD,Linux,Windows的实现,手边再放一本那被捧为圣经的《TCP/IP详解(卷1)》,你会发现太多类似下面的措辞:
XXX并没有严格规定YYY,但是ZZZ为了AAA是…并不明确SDJWFMCQ。。。
OH!shit!


我截一张TCP状态机的局部,来自RFC793[page 22]:https://tools.ietf.org/html/rfc793

这个图非常权威。我们注意到FINWAIT-2这个状态,它的转移条件只有一个,即收到对端的FIN,然后进入TIMEWAIT。

那么问题来了,如果对端死活不发送FIN,本端会一直待在FINWAIT-2状态吗?

按照TCP全双工的概念推论,答案显然是:

  • 收到对端的FIN之前,本端会一直保持FINWAIT-2状态

这是合理的,也是符合TCP规范的,因为TCP是一个双向全双工的传输协议,本端发送FIN仅仅意味着本端到对端这个方向上的传输结束了,而对端到本端的传输依然可以继续,直到对端也发送一个FIN过来。所以说我们看到断开连接的挥手动作是4次,其实就是两个来回,每一个来回关闭一个方向的数据传输。

非常完美的解释!非常完美的规范!

但是,….TCP总是伴随着这么些烦人的但是

如果对端故意不发送FIN,且也不传输数据,那么意味着本端始终处在FINWAIT-2状态而资源无法释放,这不正是一个DDoS的典型场景吗?但是如果不这么做,也不符合TCP双向全双工独立控制的规范啊!

是规范重要还是现实中的问题重要?

我们仔细想想迄今为止有多少TCP连接时关闭一半的,即客户端始发方向关闭连接,而服务端依然在传输大量数据这种情形,似乎几乎是没有的。相反,几乎很多的C/S模式的TCP连接都是单向的,比如文件下载,至始至终,数据几乎都是从服务器往客户端发送,在最初的客户端请求文件结束后,事实上就相当于客户端始发方向的连接已经被关闭了!

因此,为了实现双向全双工的语义,完全可以在应用层做,完全没有必要在挥手关闭连接的逻辑上照本宣科而较真儿,事实上,我认为,TCP当初这么设计就是错误的,至少是不合理的,除了增加了复杂性之外,毫无意义。也许吧,当初设计协议的都是学院派,始终保持着一种对完备性的笃信和追求,所以既然TCP取自传输控制之名,那么就必须完成传输与控制之完备性的逻辑,也许换个名字会好些吧。


从Telnet,FTP,到Apache,Nginx,几乎所有的TCP服务的实现均遵循了收到客户端的FIN之后立即发送FIN这么一个不成文的事实,也就是说,对于主动关闭的一方,当它发送完FIN进入FINWAIT-2状态后,可以在预期的时间内收到对端的FIN从而进入TIMEWAIT状态,而且这个所谓的“预期的时间”不会太长,以秒计算吧,因此给定一个超时时间是明智的。

因此,针对上面问题“如果对端死活不发送FIN,本端会一直待在FINWAIT-2状态吗?”的回答我把可能的答案罗列:

  • 收到对端的FIN之前,本端会一直保持FINWAIT-2状态(标准的要求)
  • 收到对端的FIN之前,本端会保持FINWAIT-2状态一段足够的时间,超过此时间,连接即释放(现实的要求)

我们看到,历史选择了现实而摒弃了理想。Linux任意使用2.2内核以上的发行版,看tcp的manual,其中的:

tcp_fin_timeout (integer; default: 60; since Linux 2.2)
This specifies how many seconds to wait for a final FIN packet before the socket is forcibly closed. This is
strictly a violation of the TCP specification
, but required to prevent denial-of-service attacks. In Linux 2.2,
the default value was 180.


现在FINWAIT-2为什么会有个超时时间的问题已经解释清楚了,接下来的问题是,如果FINWAIT-2的timer超时了,这个TCP连接将何去何从?


我事先还真没有了解过实现的细节,但是按照我对这个问题理解的逻辑来讲,我认为timer到期后连接应该被销毁,顺便给对端发送一个reset。我之所以这么认为,我是这么想的。

既然在预期的时间内对端没有发送FIN(是的,FIN会丢失,但是TCP也会重传,另外,网线也可能被剪断),那么说明对端是“违约”的,至少是不符合常理的,对待不遵守游戏规则的,当然也不需要规则内的措施,直接释放连接是本端的原则,而发送reset则是针对对端“违约”的告知,就是想告诉对端“你违约了,明白吗?”…

我和两位同事楼下抽根烟讨论了这个问题,一位同事持有不同意见,认为不会发送reset,事实证明他是对的,确实不会发送reset,我犯了与垃圾对话的错误。我一向秉承的就是针对我不感兴趣或者不想卷入的事情,我会保持沉默这个理念,如果TCP对端违约,按照这个理念,超时后把连接资源默默释放即可,不必再与之对话!我一直在家“教育”我的老婆和女儿,不与之交互,保持沉默是最好的策略….然而,我自己在一个系统设计问题上却犯了错误,不该!

从社会工程学的角度来看,如果你只是为了说一句“你错了”,而发送一个reset,搞不好就会被绕进去,所以不理它就是了。我们都知道婴幼儿是社会工程学领域内的高手,因为他只要一哭闹,你总是会与之交互,然后你们一来二去的,婴幼儿的目的就达到了….不多说。


好了,最后一个问题,在FINWAIT-2超时之后,连接还会进入TIMEWAIT状态吗?


我认为是不会的,连接会直接消失。但是一位同事通过代码确认了一个不同的意见,他认为在经历了FINWAIT-2之后,即FINWAIT-2的timer到期后,连接依然会进入到TIMEWAIT状态,其通过tcp_time_wait函数的调用路径可以确认,调用参数为:

tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);

我始终是持怀疑态度的,而且我也不善于撸代码,而且特别恶心的就是TCP的代码,代码总是有不确定的因此,一堆堆的if分支,不去实际run的话,也许你能讲清楚的那个分支反而是99%进不去的分支。

所以我依然选择设计实验来验证。

两台机器,一台作为Client,IP地址为192.168.44.138,另一个为侦听了22端口的Server,IP地址为192.168.44.111,好了,我在Client上配置以下的iptables规则,以阻止Server发送的FIN到达Client的TCP处理逻辑,以模拟对端永远不回复FIN导致FINWAIT-2到期的情形:

iptables -A INPUT -s 192.168.44.111 -p tcp --tcp-flags SYN,FIN,RST FIN -j DROP

接下来我把Client的fin_timeout设置短一些:

sysctl -w net.ipv4.tcp_fin_timeout=5    

最后我在Client上发起一个连接并随即Ctrl-]+q关闭,以使一个TCP连接进入FINWAIT-2状态(iptables规则会阻止对端的FIN,因此本端将进入FINWAIT-2而不是TIMEWAIT):

root@debian:/home/zhaoya# telnet 192.168.44.111 22
Trying 192.168.44.111...
Connected to 192.168.44.111.
Escape character is '^]'.
SSH-2.0-OpenSSH_7.4
^]
telnet> q
Connection closed.
root@debian:/home/zhaoya# 

迅速观察netstat:

root@debian:/home/zhaoya# netstat -anpt|grep 111
tcp        0      0 192.168.44.138:53068    192.168.44.111:22       ESTABLISHED 96417/telnet
root@debian:/home/zhaoya# netstat -anpt|grep 111
tcp        0      0 192.168.44.138:53068    192.168.44.111:22       FIN_WAIT2   -
root@debian:/home/zhaoya# netstat -anpt|grep 111
tcp        0      0 192.168.44.138:53068    192.168.44.111:22       FIN_WAIT2   -
root@debian:/home/zhaoya# netstat -anpt|grep 111
tcp        0      0 192.168.44.138:53068    192.168.44.111:22       FIN_WAIT2   -
root@debian:/home/zhaoya# netstat -anpt|grep 111
tcp        0      0 192.168.44.138:53068    192.168.44.111:22       FIN_WAIT2   -
root@debian:/home/zhaoya# netstat -anpt|grep 111
tcp        0      0 192.168.44.138:53068    192.168.44.111:22       FIN_WAIT2   -
root@debian:/home/zhaoya# netstat -anpt|grep 111
tcp        0      0 192.168.44.138:53068    192.168.44.111:22       FIN_WAIT2   -
root@debian:/home/zhaoya# netstat -anpt|grep 111
root@debian:/home/zhaoya# netstat -anpt|grep 111

大概5秒钟,连接灰飞烟灭,什么也没有剩下,连接并没有进入到TIMEWAIT,与此同时tcpdump抓包,也没有看到任何reset。实验很low,但是却说明了问题。


连接在FINWAIT-2超时后并不会进入TIMEWAIT状态,也不会发送reset,而是直接默默消失。


关于tcp_time_wait这个函数的代码,我就不撸了,TCP的代码非常恼人的。只说几个细节:
1. 只要调用tcp_time_wait,TCP连接状态就会变成TCP_TIME_WAIT;
2. 如果以TCP_FIN_WAIT2参数调用tcp_time_wait,则TCP_FIN_WAIT2作为substate处理对端的FIN;
3. 不管是TCP_FIN_WAIT2还是TCP_TIME_WAIT,均是将TCP连接从Establish哈希链表摘除,重新分配TW item链接进入哈希表。


只要写关于TCP的任何东西,代码,文档,文章,测试case脚本,我都是感慨的,我想唱着歌爆粗,我想大笑,我又想摔电脑,不一而足。TCP是令人气愤的,TCP是过时的。

  • TIMEWAIT太多的问题
  • PAWS问题
  • PAWS与NAT的问题
  • Nagle问题
  • Nagle与CORK问题
  • 同时打开,同时关闭问题
  • Reno,CUBIC的问题
  • 慢启动,慢慢慢
  • BBR the fXXking
  • keepalive问题
  • tcp repair问题
  • 滑动窗口问题
  • ….
  • shit问题

你知道TIMEWAIT持续多久吗?你真的知道吗?

很多人会回答120秒,很多人会回答2MSL,很多人不知道什么是MSL,2MSL为什么是120秒而不是360秒,我要是说这是根据光速以及地球的周长算出来的你信吗?事实上确实是和地球周长有关的。如果是在火星上,TCP的TIMEWAIT超时值一定至少半小时。

可是,对于Linux系统,上面的说法全是谎言,在Linux上,TIMEWAIT的定义是:

#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT* state, about 60 seconds */

你不信吗?试试看呗,还是刚才那个Low逼实验,这次把iptables规则去掉,Ctrl-]+q之后,观察TIMEWAIT的时间,观察期间把你的Windows系统右下角的钟表打开,看看是不是60秒后TIMEWAIT连接就消失了。

Why?为什么TCP的实现一而再地违反所谓的规范?到底有没有规范?到底什么是MUST的,什么是MAY的。


我同样不喜欢HTTP,确切的说,是HTTP 1.x,同样的原因,它太松散了。请问HTTP的头部最长有多长?标准并没有明确的规定,读到\r\n\r\n为结束,但是Web服务器的实现却规定了。不然呢?不然一个恶意的客户端可以产生100T的头部,瞬间耗尽服务器的所有资源。

所以我就很看好HTTP 2.0,它解决了这个问题。


最后说说企业招聘和面试。

懂TCP有什么了不起吗?并没有!可是TCP几乎是各家必考的内容,令人不解的是,其实很多面试官也不懂,一群不懂TCP的人招了另一群不懂TCP的人,这并不耽误所有的人持续跪舔TCP!

诚然,如今互联网大行其道,一个送外卖的公司都能上万人的规模,个个拿着高薪,过着小康生活,有车有房有烧烤,有啤酒有脂肪肝,这其中有TCP的功劳,毕竟我们如今的服务器几乎90%+都是基于TCP的,这意味着90%+以上的人必须和TCP打交道,当你去应聘互联网公司的技术岗时,你可以不懂快排,你可以不懂数据库,但你不能不知道TCP。这就是现实!

精不精通不重要,懂就行,TCP是行话,大家一说TCP就知道都在一个坑里找食的,这就像喝威士忌,你再能喝也没用,你得会说“纯饮”,“水割”,你得能扯橡木桶,这就像血色浪漫里关于高尔夫的言论,对于上流社会的人,你可以不喜欢高尔夫,但你不能不会…

我从2004年第一次知道3次握手,一直到昨天跟Google的人聊TCP BBR 2.0一个细节的数学推导,06年底参加工作到现在从业十几年了,面试过太多的公司,对TCP三次握手倒背如流,以至于从2010年开始当有人让我笔试TCP相关的题目时,我要么借口有事走人,要么就直接写下两个字“口述”,这种事我能扯一整夜,只要你买单烧烤和酒,咱就找个大排档单练。对了,我不喝啤酒,白酒,伏特加,咖啡,奶茶和果汁,我只喝烧酒,清酒,黄酒,威士忌,白兰地,特浓柠檬汁,或者纯净水。

很多次,若不是我急于用钱有求于人,我可能也会像徐晓冬那般跟面试官来个关于TCP的现场PK,然后爆料这些都是假的。唉…

IP层的东西难道不重要吗?

上个月轮到我值班,有人碰到一个问题,说是在一台机器上确认TCP的init cwnd就是10,然而一个进程发包的时候只能发出去1个包,问我这是怎么回事。我犯了一个错误,我把这个问题搁置了,我觉得这不是一个有难度,且好玩的问题,再说当时还有更加紧急的问题,所以我三言两语敷衍提问者,说什么你这是在虚拟机环境,而虚拟机涉及到不可控的调度….说的我自己都信了。

过了好几天,那人的问题还没解决,并且已经严重影响了业务,再次问我,态度已经不是很好了,如果不给一个明确地说法,感觉像要投诉我的样子。闲来无事就帮他看看吧,其实我宁可去排查100个Bridge,bonding的问题,也不想沾染TCP…你们知道我是怎么在1秒内解决问题的吗?

因为我精通IP层的逻辑啊,因为我精通iproute2的使用啊,我让他赶紧ip route ls tab all,结果发现了一条路由:

..... initcwnd 1

这不就是问题的根源吗?很多人不知道initcwnd还能针对路由来指定。不知道吧,哈哈,你也许你知道,但你知道这是为什么吗?

什么是路由?TCP是不管路由的。但是TCP的拥塞控制却完全离不开路由。我们都知道,一条路和另一条路的拥塞程度是完全不同的,IP层的路由正是基于这个拥塞程度的不同,想办法用参数告诉你,哪条路更好走一些,仅此而已。悲哀的是,端到端的TCP并没有“路”的概念!

关于select,poll,epoll我就不多说了。


TCP没有意思,真的没有意思,也许,真的是时候关注一下QUIC了,顺便说一句,BBR在TCP上的实现是一个半吊子实现,在QUIC上的实现才是完整的,虽然QUIC还不甚完美,但也不要先入为主地去捧TCP而唱衰QUIC。QUIC99次行1次不行就会被你们记录在案,而对于TCP,你们只是自然主观的屏蔽掉了你们不想看到的结果,而已。

不多说。

一个TCP FIN_WAIT2状态细节引发的感慨相关推荐

  1. 通过TCP各个状态,可以排除和定位网络或系统故障

    我们通过了解TCP各个状态,可以排除和定位网络或系统故障时大有帮助. 1.TCP状态 了解TCP之前,先了解几个命令: linux查看tcp的状态命令: 1).netstat -nat  查看TCP各 ...

  2. TCP连接状态详解及TIME_WAIT过多的解决方法

    TCP建立连接的三次握手过程,以及关闭连接的四次握手过程. TCP建立连接的三次握手过程,以及关闭连接的四次握手过程. 1.建立连接协议(三次握手) (1)客户端发送一个带SYN标志的TCP报文到服务 ...

  3. 查看 并发请求数及其TCP连接状态

    服务器上的一些统计数据: 1)统计80端口连接数 netstat -nat|grep -i "80"|wc -l 2)统计httpd协议连接数 ps -ef|grep httpd| ...

  4. shutdown函数和FIN_WAIT2状态

    玩过英雄联盟的人都不会对shutdown感到陌生,就是你连杀被终结了嘛.在网络编程中也差不多是这个意思,准确来说是从容关闭.有啥用呢?来看代码吧 [mapan@localhost TCP]$ ls c ...

  5. 查看Apache并发请求数及其TCP连接状态 --张宴

    [ 2007-7-20 18:30 | by 张宴 ]     [文章作者:张宴 本文版本:v1.1 最后修改:2007.07.27 转载请注明出处:http://blog.s135.com] 这两天 ...

  6. TCP 连接状态及相关命令学习

    在平时的开发工作中,我们都使用被封装完好的 TCP/HTTP 库去完成需求开发,很少关心底层 TCP 的连接状态,但是一旦遇到较难定位的线上事故,往往都是因为 TCP 连接参数或者使用姿势不对导致的, ...

  7. TCP FIN_WAIT2定时器

    注:本文分析基于3.10.107内核版本 之前我们一直考虑的是TCP建链过程中出现问题时,双方用户是如何感知,并处理的.在TCP建链的三次我手中主要是由超时重传定时器以及SYNACK定时器来进行链接状 ...

  8. TCP FIN_WAIT2由来

    TCP 为什么要有FIN_WAIT2, 主要是为了维护一个全双工的通道, 要弄懂这个问题, 必须要弄清楚, TCP断开链接时的四次挥手.如图所示 tcp四次挥手,由于TCP连接是全双工的,因此每个方向 ...

  9. 23-tcp协议——TIME_WAIT状态和FIN_WAIT2状态

    关于TIME_WAIT状态   TIME_WAIT是TCP状态转换中的一个非常重要的状态,TIME_WAIT状态的或多或少会极大的影响客户端与服务端的性能,在真实的应用场景中往往需要根据实际需求来对T ...

  10. 查看httpd并发请求数及其TCP连接状态

    服务器上的一些统计数据: 1)统计80端口连接数 netstat -nat|grep -i "80"|wc -l 2)统计httpd协议连接数 ps -ef|grep httpd| ...

最新文章

  1. RDKit:化合物相似性搜索(基于Python3)
  2. java mesos kubernete_Fabric8操作Kubernetes(一)
  3. 安装 PyCharm
  4. CentOS7中编译安装redis5.0
  5. 【免费毕设】asp.netERP客户管理系统的实现(源代码+lunwen)
  6. 使用loadrunner进行服务器性能测试(winsocket)
  7. 流畅的Python---list排序和保持有序序列
  8. RN开发研究入门篇(一)项目搭建
  9. iphone投屏老是显示无法连接服务器,iphone怎么投屏到电视 升级iOS11后为什么投屏会失败...
  10. Mac命令行上传代码至GitHub
  11. python爬取网站突破_python最强的代理池,突破IP的封锁爬取海量数据
  12. [Jzoj] 1285. 奶酪厂
  13. 学习使用master.dbo.spt_values表
  14. 已解决FileNotFoundError: [WinError 2] 系统找不到指定的文件。
  15. 重磅发布!2022大数据十大关键词
  16. 肺实质分割matlab实现
  17. CBE可表示计算机辅助教育,计算机辅助教育(CBE)的理论基础概述
  18. 网站中的个性及风格设计
  19. redis queue_在Redis上通过Easy Message Queue扩展微服务
  20. 超级星饭团专访陆毅:化身“霸总”上演“沙漏”式爱情

热门文章

  1. 怎么用计算机ping组播地址,windows – 使用’目标主机无法访问’从同一台计算机ping“回复”(没有到其他计算机的路由)...
  2. 你真的了解VRP操作系统吗?华为网络设备的文件系统、设备基础管理、命令行基础一次学会
  3. 802.11 ------ Beacon帧、Beacon Interval、TBTT、Listen Interval、TIM、DTIM
  4. 基于javaweb+jsp的学生档案管理系统(JavaWeb JSP MySQL Servlet SSM SpringBoot Bootstrap)
  5. STM32实现薄膜压力传感器数据采集(标准库和HAL库实现)
  6. 计算机每次启动时系统时间不更新,电脑每次开机都要重新设置时间
  7. VS2010 保护视力 背景色设置
  8. iqc工作职责和工作内容_iqc工作职责流程
  9. CTF Web出题感悟
  10. 网络技术——路由器及其配置