《TCP/IP详解》之一:链接创建、断开

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

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

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

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

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

  首先是管道的创建、拆除。即很是有名的TCP三次握手。ui

  终止要通过四次握手。编码

  为何会多一次呢?从图中能够看出,创建时报文段2包含了SYN、ACK,而终止时FIN与ACK是分两条发的,why?spa

  TCP链接是双全工的,两端都能收发,向对方交付FIN仅仅只是告知对端本身再也不发送数据(仍须能收,即TCP的半关闭),而对端什么时候终止发送,是其业务逻辑决定的。终止时报文段二、报文段3的触发,源自两个不一样层次的东西,没法合并成一条(即便有ack延时确认)。设计

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

  这就是很是著名的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分钟)后,强关~~

相关文章
相关标签/搜索