TCP连接的建立与释放

TCP 连接的建立与释放

TCP 是面向连接的,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。在TCP/IP协议中,TCP协议提供可靠的连接服务,连接是通过三次握手进行初始化的。

TCP 连接的三个阶段:

  1. 连接建立。
  2. 数据传送。
  3. 连接释放。

TCP 连接建立过程中要解决的问题:

  1. 每一方能够确知对方的存在。
  2. 允许双方协商参数。如:Sequence Number初始值,最大窗口值,是否使用窗口扩大选项,是否使用时间戳选项,服务质量…
  3. 能够对运输实体资源进行分配。如:缓存大小,连接表中的项目…

TCP三次握手

建立TCP连接-三次握手

  1. 最初两端的 TCP 都处在 CLOSED状态。
  2. B的 TCP 服务器进程创建传输控制块 TCB,服务器进程进入LISTEN(收听)状态,等待客户的连接请求。
    传输控制块:Transmission Control Block,TCB,存储连接中的信息。如:TCP 连接表,到发送和接收缓存的指针,到重传队列的指针,当前发送和接收序号…
  3. A的 TCP 客户进程创建传输控制块 TCB ,向B发出连接请求报文段。这时,首部中同步位SYN=1,初始序号seq=x。SYN报文段不携带数据,但要消耗一个序号。TCP 客户进程进入SYN-SENT(同步已发送)状态。
  4. B收到连接请求报文段,如果同意建立连接,则向A发送确认。确认报文段中,SYNACK都为1,确认号ack=x+1,并选择自己的初始序号seq=y。此报文段同样不携带数据,但要消耗一个序号。TCP服务器进程进入SYN-RCVD(同步接收)状态。
  5. TCP 客户进程收到B的确认后,向B发出确认。确认报文段的ACK=1,确认号ack=y+1,自己的seq=x+1。ACK报文段可携带数据,不携带数据则不消耗序号。TCP 连接已建立,A进入ESTABLISHED(已连接)状态。
  6. B收到A的确认,也进入ESTABLISHED状态。

A 收到 B 的确认,为什么还要再次向 B 发送确认?
为了防止已失效的连接请求报文段突然又传送到了服务端,而产生错误。

举个栗子:

A 向 B 发出连接请求报文段,如果 client 发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达 server 。本来这是一个早已失效的报文段,但 server 收到此失效的连接请求报文段后,就误认为是 client 再次发出的一个新的连接请求。于是就向 client 发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要 server 发出确认,新的连接就建立了。由于现在 client 并没有请求建立连接,因此不会理睬 server 的确认,也不会向 server 发送数据。但 server 却以为新的运输连接已经建立,并一直等待 client 发来数据。这样,server 的很多资源就白白浪费掉了。采用『三次握手』的办法可以防止上述现象发生。例如刚才那种情况, client 不会向 server 的确认发出确认。server 由于收不到确认,就知道 client 并没有要求建立连接。
—— 谢希仁的《计算机网络》

释放TCP连接-四次挥手

四次挥手

  1. 开始时,AB都处在ESTABLISHED状态。
  2. A的应用进程向 TCP 发出连接释放连接报文段,停止发送数据,关闭 TCP 连接。A把报文段头部中FIN置为1,序号seq=u,u是已传送的数据的最后一个字节序号加1。A进入FIN-WAIT-1状态,等待B的确认。FIN报文段即使不携带数据,也要消耗一个序号。
  3. B收到连接释放报文段向A发出确认,确认号ack=u+1,B自己的序号是v,v是已传送过的数据的最后一个字节加1。B进入CLOSE-WAIT状态。TCP 服务器进程通知高层应用进程,从AB的连接被释放。TCP 进入half-close状态。
  4. A收到B的确认,进入FIN-WAIT-2状态,等待B发出连接释放报文段。
  5. B应用进程通知 TCP 释放连接,报文段首部FIN=1,序号为w(半关闭状态时B可能又发送了一些数据),B重复上次发送的确认号ack=u+1。B进入LAST-ACK(最后确认)状态,等待A的确认。
  6. A收到B的连接释放报文段,向B发出确认。确认报文段中ACK=1,确认号ack=w+1,A自己的序号seq=u+1(发送的FIN报文段使用一个序号)。A进入TIME-WAIT(时间等待)状态。
  7. 经过时间等待计时器(TIME-WAIT timer)设置的时间2 MSL 后,A进入CLOSED状态。
    最长报文段寿命:Maximum Segment Lifetime,MSL,TCP 允许根据不同情况调整此值,RFC 793建议时间2分钟。
  8. B收到A的确认,进入CLOSED状态,撤销传输控制块 TCB,TCP 连接释放成功。

对于4次挥手,其实是2次,因为 TCP 是全双工的。所以,发送方和接收方都需要FINACK。只不过,有一方是被动的,所以看上去就成了所谓的4次挥手。如果两边同时断连接,那就会就进入到CLOSING状态,然后到达TIME_WAIT状态。TCP两端同时断开连接
@(TCP两端同时断开连接)

A 为什么在 TIME-WAIT 状态等待2 MSL 时间?
保证A发送的最后一个ACK报文段能够到达B。若此报文段丢失,处在LAST-ACK状态的B收不到FIN+ACK报文段的确认,B将超时重传FIN+ACK报文段,A可在2 MSL 时间内收到重传的FIN+ACK报文段。A重传确认,重新启动2MSL计时器,保证AB顺利进入CLOSED状态,防止“已失效连接请求报文段”。A等待2 MSL,可使本连接持续时间内所产生的所有报文段全部从网络中消失。**

什么是时间保活计时器?为什么设置时间保活计时器?
时间保活计时器(keepalive timer):服务器每收到一次数据,就重新设置保活计时器,时间2小时。超时后还未收到客户数据,服务器发送探测报文段,以后每隔75分钟发送一次。连续发送10个探测报文段客户仍无响应,服务器关闭 TCP 连接。
设置保活计时器的原因:处在 TCP 连接状态时,若A出现故障,防止B一直等待下去。

TCP有限状态机
方框:TCP状态。
箭头:状态变迁。
箭头旁边的字:1.引起状态变迁的原因。2.发生状态变迁后出现的动作。
粗实线箭头:客户进程的正常变迁。
粗虚线箭头:服务器进程的正常变迁。
细线箭头:异常变迁。

另外…

  1. 关于建连接时SYN超时。试想一下,如果 serve r端接到了 clien 发的SYN后回了SYN-ACK后 client 掉线了,server 端没有收到 client 回来的ACK,那么,这个连接处于一个中间状态,即没成功,也没失败。于是,server 端如果在一定时间内没有收到的 TCP 会重发SYN-ACK。在 Linux 下,默认重试次数为5次,重试的间隔时间从1s开始每次都翻售,5次的重试时间间隔为1s, 2s, 4s, 8s, 16s,总共31s,第5次发出后还要等32s都知道第5次也超时了,所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 2^6 -1 = 63s,TCP 才会把断开这个连接。
  2. 关于SYN Flood 攻击。一些恶意的人就为此制造了 SYN Flood攻击——给服务器发了一个SYN后,就下线了,于是服务器需要默认等63s才会断开连接,这样,攻击者就可以把服务器的SYN连接的队列耗尽,让正常的连接请求不能处理。于是,Linux 下给了一个叫 tcp_syncookies 的参数来应对这个事——当SYN队列满了后,TCP 会通过源地址端口、目标地址端口和时间戳打造出一个特别的 Sequence Number 发回去(又叫cookie),如果是攻击者则不会有响应,如果是正常连接,则会把这个 SYN Cookie发回来,然后服务端可以通过 cookie 建连接(即使你不在SYN队列中)。请注意,请先千万别用 tcp_syncookies来处理正常的大负载的连接的情况。因为,SYN Cookies是妥协版的 TCP 协议,并不严谨。对于正常的请求,你应该调整三个 TCP 参数可供你选择:
    1. tcp_synack_retries 可以用他来减少重试次数;
    2. tcp_max_syn_backlog,可以增大SYN连接数;
    3. tcp_abort_on_overflow 处理不过来干脆就直接拒绝连接了。
  3. 关于ISN的初始化。ISN是不能 hard code(硬编码),不然会出问题的——比如:如果连接建好后始终用1来做ISN,如果 client 发了30个 segment 过去,但是网络断了,于是 client 重连,又用了1做ISN,但是之前连接的那些包到了,于是就被当成了新连接的包,此时,client 的Sequence Number 可能是3,而 Server 端认为client 端的这个号是30了。全乱了。RFC793中说,ISN会和一个假的时钟绑在一起,这个时钟会在每4微秒对ISN做加一操作,直到超过2^32,又从0开始。这样,一个ISN的周期大约是4.55个小时。因为,我们假设我们的 TCP Segment 在网络上的存活时间不会超过Maximum Segment Lifetime(缩写为MSL - Wikipedia语条),所以,只要MSL的值小于4.55小时,那么,我们就不会重用到ISN
  4. 关于MSLTIME_WAIT。通过上面的ISN的描述,相信你也知道MSL是怎么来的了。我们注意到,在 TCP 的状态图中,从TIME_WAIT状态到CLOSED状态,有一个超时设置,这个超时设置是 2*MSL(RFC793定义了MSL为2分钟,Linux 设置成了30s)为什么要这有TIME_WAIT?为什么不直接给转成CLOSED状态呢?主要有两个原因:
    1. TIME_WAIT确保有足够的时间让对端收到了ACK,如果被动关闭的那方没有收到ACK,就会触发被动端重发FIN,一来一去正好2个MSL;
    2. 有足够的时间让这个连接不会跟后面的连接混在一起(你要知道,有些自做主张的路由器会缓存IP数据包,如果连接被重用了,那么这些延迟收到的包就有可能会跟新连接混在一起)。你可以看看这篇文章《TIME_WAIT and its design implications for protocols and scalable client server systems》

参考链接:
http://www.imooc.com/article/17411 作者:江户川秋风
http://www.jellythink.com/archives/705 作者:果冻想
http://www.cnblogs.com/lshs/p/6038458.html 作者:lshs