• 《TCP/IP详解》之一:连接建立、断开


      《TCP/IP详解·卷一》看了三遍才算整明白个大概,一直想做个总结。

      最初对TCP的印象很简单:丢包重传、流数据。丢包重传很好理解,“流数据”是什么鬼?

      知乎上看到个极好的解释:把TCP看作用管子往对端灌水,水是数据,它们之间没有边界,且先发先到;UDP是往对端滚小球,它们之间有明确边界,且可能每个小球速度不同,先滚的不一定先到,得自己处理乱序。

      编码上也可看出,TCP的send回调带有dwNumberOfBytesTransferred参数,描述本次网络IO发送了多少字节数据,而不是给它多少发多少。很可能让发的500字节只发了300,剩200留着呢,业务层得自行处理。接收方也有个常规的粘包操作,把连成一片的网络buffer解析为单个消息包,再分发给各业务逻辑。

      那就试着复述下这个管子灌水的过程吧。

      首先是管道的建立、拆除。即非常有名的TCP三次握手。

      终止要经过四次握手。

      为什么会多一次呢?从图中可以看出,建立时报文段2包含了SYN、ACK,而终止时FIN与ACK是分两条发的,why?

      TCP连接是双全工的,两端都能收发,向对方交付FIN仅仅只是告知对端自己不再发送数据(仍须能收,即TCP的半关闭),而对端何时终止发送,是其业务逻辑决定的。终止时报文段2、报文段3的触发,源自两个不同层次的东西,无法合并成一条(即使有ack延时确认)。

      剩下的问题是,建立/终止的这些报文,任意一条都会丢失。其中最麻烦的是终止握手的最后一条ACK,它的重传依赖对端FIN的超时,所以主动发起关闭的一方不能在收到最后一个FIN时,即刻关闭连接。

      这就是非常著名的TIME_WAIT状态。主动关闭方要等2×MSL(报文段最大生成时间)后方可真正关闭连接,留有足够的时间应对最后ACK的丢失(对端重传FIN)。

      然后这个TIME_WAIT在使用上引起了些麻烦——此状态的连接,不能再接收数据(报文段直接抛弃),也不能重用(旧连接的在路上的数据可能会被当成新连接的)。

      在服务器端就要注意,避免短时间内大量关闭socket,产生的众多TIME_WAIT连接可能会耗尽系统资源,致使新连接无法建立。

      以为这就完了吗?

      Quiet Time。

      平静时间:TCP在重启后的MSL秒内不能建立任务连接。

      如若没有Quiet Time,TIME_WAIT的2×MSL出现故障怎么办?比如服务器中途宕机很快重启,并按流程立即使用了之前同样的一对socket。假设故障发生时那对旧socket恰好有报文段在网络上,那重启后的新连接就完全无法区分此条报文段了,会被当作正常数据接收。

      

      在分析断开为何要四次时介绍了TCP半关闭,相应的也有TCP半打开。

      比如客户端忽然断电停机,由于没来得及跟服务器有交互,所以服务器是不知道对端已关闭的。只要不在此连接上传送数据,就永远不会知晓另一方已然完蛋。

      TCP四大定时器(重传、坚持、保活、2MSL)之一的保活定时器要解决的问题。

      一条连接在给定时间内没任何交互,服务器会向客户端发送探查报文,若超时后仍未收到回应,或收到RST复位(客户端已重启),即终止该连接。

     

      当然还有“同时打开/同时关闭”的问题。与上述常规打开/关闭相比,只是状态迁移有所不同,对照示意图一看即明。

      这里再介绍下连接建立过程中的“呼入请求队列”。

      三次握手成功是个及时操作,系统不可能紧盯着它等处理。那服务器繁忙时,有连接到了怎么办?很简单,排队,等有空了挨个处理。

      “呼入请求队列”便是这个。需要注意的:客户端connect成功,仅仅只表示被放入了svr的呼入请求队列(此时client仍能发数据,收的数据被放在tcp缓存了),还未交接给应用层。svr端Accept时才会将队列中的连接取出,进而处理。

      PS:如果不取或取慢了,client会因tcp发送缓冲满而一直send失败。所以怎么调Accept也是要考虑设计的

      PPS:“呼入请求队列”有长度限制(积压值 backlog),超过后再来的连接请求就一直失败了。

     

      业务层上,关闭比建立更难些,难点在于:发送数据的完整性(极可能你的用户buffer/tcp缓冲中都残留有正常的逻辑数据),以及关闭的及时性。

      针对TCP层的发送缓冲,有“优雅关闭、强制关闭”两种。优雅关闭是指,如果发送缓存中还有数据未发出则其发出去,并且收到所有数据的ACK之后,发送FIN包,开始关闭过程;强制关闭是指,如果缓存中还有数据,则丢弃这些数据,然后发送RST包,直接重置TCP连接。

      针对用户层的发送缓冲,正统做法是:将closeflag置true,业务层的buffer不再接数据了,继续调tcpSend直至buffer数据被发完,随即“优雅关闭”——TCP缓冲发完后FIN,客户端收到后回FIN(四次握手关闭连接),svr收到FIN,进而closesocket。

      客户端异常就收不到回的FIN了,所以得要个“优雅关闭列表”,数秒后还不关就强关。

      还有土豪做法,先将连接设置成无效的,业务层不收也不发了,等足够长时间(3分钟)后,强关~~

      

      

      

      

  • 相关阅读:
    C#程序之Main()方法
    JavaScript引用类型之RegExp类型(正则表达式)
    正则表达式之量词(重复出现)?、*、+
    正则表达式一元字符与字符组
    正则表达式简介
    yii自动登录
    Yii学习
    改变Yii2的默认路由
    extract()函数
    html5标签figure、figcaption
  • 原文地址:https://www.cnblogs.com/3workman/p/5468642.html
Copyright © 2020-2023  润新知