当前位置:   article > 正文

计算机网络协议第九章,TCP连接的建立与终止_并以 a 计算机为主动提出连接终止的一端,绘图说明 tcp 终止连接时各步骤的信

并以 a 计算机为主动提出连接终止的一端,绘图说明 tcp 终止连接时各步骤的信

      TCP协议作为有状态的服务,本章围绕这一主题进行详细讲解。本章主要分解连接建立和终止两个部分阐述TCP的状态,并且对TCP连接建立和终止的一些常见问题进行分析。


TCP的状态机

下面我们看一下TCP状态机的图例:


一共分为两个部分,第一部分是关于连接建立的5个状态,第二部分是关于连接终止的7个状态。
连接建立状态是ESTABLISHED状态及其以上半部分,连接终止则是其下半部分。
首先初始状态为CLOSED状态,通过三步握手成功后转换为ESTABLISHED状态。最好通过4步链接终止最终再次转换到CLOSED状态,这就是网络连接状态机的闭环。
下面通过连接的建立和拆除分解的方式来理解TCP状态机。

网络连接的建立


左侧一方为连接的主动方,通常是一个client角色,右侧则是连接的接受方,通常是一个server 角色。
步骤一、主动方通过connect 调用会发送一个SYN的TCP分节,此时socket 状态从CLOSED状态转换为SYN_SENT状态。
步骤二、接受方通过listen监听到SYN分节后,此时socket状态从CLOSED状态转换为SYN_RECV状态。并且发送SYN,ACK分节。
步骤三、主动方收到接受方的SYN,ACK分节后socket状态转换为ESTABLISHED, 并且发送ACK分节。
步骤四、接受方收到ACK分节后socket状态切换为ESTABLISHED状态。

连接双方都是经过一发一收两个步骤,最终都进入ESTABLISHED状态。看起来这个TCP连接建立也非常简单,但是里面也蕴含很多设计的思想。
在发送SYN分节时,需要初始化seq(Sequence Number简写)的值,如果细心的同学通过wireshark等抓包工具查看的时候发现seq=0,但是实际上seq是不为0的,因为抓包工具为了方便使用者分析而采用了相对seq。一般内核协议栈会根据时间的因子做一个随机的seq值,保证每次连接时尽可能不同。
接受方发送了SYN,ACK分节中的seq也是需要初始化一个值,是一个道理,而ACK Number则是需要在上一个SYN分节的seq上加1。
发送方收到SYN,ACK分节后,ACK Number也是需要加1,道理是同样的,因为当TCP的Data为0时也需要加1处理,如果TCP的Data有10个字节,ACK Number可以表示有多少字节被收到,ACK Number和Seq Number是相辅相成的,ACK Number可以告知发送方多少数据被接收,它被用于重传机制。Seq Number的保证每次发送时的序号不同,这样就能够保证发送报文的顺序。

下面看下连接建立后,发送1个字节的数据的相互流程,就比较容易理解Seq Number和Ack Number了


网络连接的终止


左侧我们称为连接主动关闭方,右侧为被动关闭方。通过close调用就可以发送FIN分节。
步骤一、主动方发送FIN分节后,socket状态从ESTABLISHED状态转换位FIN_WAIT_1。
步骤二、被动方收到FIN分节后,进入到CLOSE_WAIT状态并且发送ACK分节确认。
步骤三、被动方调用close函数,发送FIN分节,socket转换切换为LAST_ACK状态
步骤四、主动方收到步骤二发来的ACK分节后进入FIN_WAIT_2状态。
步骤五、主动方收到步骤三发来的FIN分节后进入TIME_WAIT状态,并且回复ACK分节。
步骤六、被动方收到ACK分节后,socket状态重新回到CLOSED状态。

连接的关闭流程确实比建立复杂一些,可以看到被动关闭方最终进入到CLOSED状态,而主动关闭方却进入到TIME_WAIT状态。主要看第一张图,当TIME_WAIT状态经过2个MSL周期后后最终进入到CLOSED状态,因此形成状态机的闭环。

TCP连接建立和终止扩展

连接同时打开

在第一张图的如下图所示部分就是同时打开的情况。

当主动方发生SYN分节后进入到SYN-SENT状态后,正常而言应该是等待SYN,ACK分节,但是等到的是SYN分节,这就以为则连接的对方同时发生SYN分节,这就叫同时打开,此时应该回复ACK分节并且进入到SYN-RECEIVED状态,等待ACK分节,而对方的情况也是如此,因此都会进入到SYN-RECEIVED状态等待ACK,如此就会出现连接过程中四个分节,2个SYN和2个ACK,当每方都收到对方的ACK分节后同时进入到ESTABLISHED状态。

这里讲一个关于同时打开的小话题,如果用TCP链接本机的某一个服务端口的时候,某些情况下会出现“鬼打墙”的现象,就是自己发送的数据又被自己给收到了,出现这个问题的原因就是因为出现自连接,而自连接就是一直同时打开的现象,其实很好复现这个问题,字节建立一个连接,然后又使用同一个fd连接自己监听的端口。 当然有时不是故意为之也会出现,比如你像连接本机的1234端口,而这个12345端口的服务没有起来,而正好系统把它分给给你创建的fd,当你去连接这个端口的时候就变成自连接了。

连接同时关闭


上节没有将CLOSING这个状态,这个状态只会出现在同时关闭的时候,不少很常见,当一方发送FIN分节进入到FIN_WAIT_1状态是,正常情况下应该等等ACK分节,可是等待的却是FIN分节,这就意味着同时关闭,然此时应该进入到CLOSING状态并且等带对方的ACK,如果收到ACK则进入到TIME_WAIT状态。

SYN超时和SYN FLOOD攻击

SYN超时是如此,当发送SYN一段时间没有回应时,会不间断的发送SYN包以防止丢包,而发送时间间隔会不停的地址,一般直观感受是connect等待30秒(根据内核而定)左右后会提示连接超时。也就是TCP内核会尝试多次,如果依然没有回应才会超时。

SYN FLOOD 攻击是一个比较常见的DOS攻击手段,它的手段是发送SYN分节报文后,不在响应SYN,ACK分节,让对方一直无法完成连接的建立,linux内核有一个队列保持这种连接,并且队列有一定大小,通过内核参数tcp_syncookies而定,如果内核的队列被这种SYN报文多次攻击后,会占满内核的队列导致正常的连接无法等到响应。形成DOS攻击。

解决这个方法有三个参数可以调配, 第一个是: tcp_synack_retries 可以用他来减少重试次数;第二个是: tcp_max_syn_backlog ,可以增大 SYN 连接数;第三个是: tcp_abort_on_overflow 处理不过来干脆就直接拒绝连接了。一般大公司会有专门保持TCP连接的机器,他们修改内核并有自己保护的策略,能够扛住百万级甚至更高的SYN FLOOD攻击。

FIN_WAIT_1状态和半关闭

写个HTTP服务的同学可能会遇到HTTP的QPS效率出现异常而CPU和内存还有很多空间的情况,然后才发现原来80端口有很多socket处于FIN_WAIT_1状态,出现这个状态是因为HTTP服务端关闭了连接,但是客户端没有调用close,发送FIN分节,导致服务socket长期处于FIN_WAIT_1状态导致fd句柄占用,从而导致服务响应性能下降。这个现象就称为半关闭状态。

那网络内核就一直不清理FIN_WAIT_1状态吗,也不是, 而是要等很长一段时间才能清理,可以将TCP的心跳包活时常设置短一些,这样能够加快对FIN_WAIT_1状态的回收。

TIME_WAIT状态和MSL

先解释MSL,MSL的意思为最大传输周期,也就是IP报文在网络中存活的最大时常,它在Linux中被设置为30秒,可以认为MSL=30秒。

什么是TIME_WAIT状态,这个状态的目的是什么? TIME_WAIT状态是主动断开连接方最终进入的状态,为什么要进入到TIME_WAIT状态呢,因为TCP设计者认为如果没有这个状态,一个连接断开后,这个端口如果再次被使用并且向上一次连接目的地址发送报文,那么可能上一个连接发送的报文在网络上延时后再次到达目的地址,这样目的地址会同时收到上一次连接和这一次连接的数据,有可能无法区分而带来问题。因此TCP设计者认为如果进入到TIME_WAIT后,必须等到2*MSL时间端口才能被复用,这样才会比较安全。
还记得初始化Seq Number吧,上面提到要使用一个随机值来初始化Seq Number,它也是一种防止这种现象的设计,当然也不一定完全有效,但是大多数时候是有用的。

HTTP服务是短连接的情况,如果Server端主动端开连接就会产生TIME_WAIT状态,然后TIME_WAIT状态也是占用系统资源的,这也是为什么大牛设计系统时都是让客户端主动端口连接的原因了,当然也不是所有场景都能让客户端先端开连接的,系统提供tcp_max_tw_buckets参数来控制TIME_WAIT的最大数量。

当客户端的场景下,如果重启进程后会出现上一个连接TIME_WAIT状态,当时有想复用这个端口,那么socket提供SO_REUSE_ADDRESS参数来复用TIME_WAIT状态的端口。

close于shutdown调用

在编写TCP协议会碰到这样一个问题,就是发送完数据后关闭连接,对方并没有将数据全部收全,原因在于调用close函数,以为你当自己的接受缓存中有数据可读时,但是没有全部读到应用层而直接调用close函数,会发送RST分节,如果发送RST分节,这样写缓冲的数据将会被丢弃,因此对方是无法收全你发送的数据的,正确的做法是调用shutdown函数,然后使用recv函数将接受缓冲队列的数据收全,当对方收到FIN分节后,也好发送ACK分节和FIN分节,如此shutdown调用方式也好收到recv == 0的时间,这时再调用close方法关闭fd。


小结

本章主要讲解TCP的状态机,并且通过讲解TCP连接建立和终止的坑来加深对TCP协议的理解,掌握如何避免掉进这些坑中。TCP的因为有状态的服务会带来一定性能问题,在媒体传输中不是被首选使用,后续有机会再讲下SCTP和T/TCP这两种协议。

参考

《TCP/IP详解-协议》卷一      W.Richard Stevens

修订

初稿                                       2015-3-27               Simon


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/158788
推荐阅读
相关标签
  

闽ICP备14008679号