每日一问20——TCP

简介

传输控制协议TCP(Transmission Control Protocol)[RFC 768]、

TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。

TCP的连接与释放

tcp状态机

TCP 所谓的“连接”,只是通信双方维护一个“连接状态”,让它看上去好像有连接一样,其实 TCP 连接是虚拟的连接,不是电路连接。首先看下 TCP 的状态机,状态机是 TCP 连接与释放的全过程。如下图所示:

20141130143210237.png
  • CLOSED:表示初始状态。对服务端和客户端双方都一样。
  • LISTEN:表示监听状态。服务端调用了 listen 函数使其处于监听状态,此时可以开始 accept (接收)客户端的连接。
  • SYN_SENT:表示客户端已经发送了 SYN 报文段,则会处于该状态。当客户端调用 * connect 函数发起连接请求时,首先发 SYN 给服务端,然后自己进入 SYN_SENT 状态,并等待服务端发送 ACK+SYN 作为请求应答。
  • SYN_RCVD:表示服务端收到客户端发送 SYN 报文段。服务端收到这个报文段后,进入 SYN_RCVD 状态,然后发送 ACK+SYN 给客户端。
  • ESTABLISHED:表示 TCP 连接已经成功建立,通信双方可以开始传输数据。服务端发送完 ACK+SYN 并收到来自客户端的 ACK 后进入该状态,客户端收到来自服务器的 SYN+ACK 并发送 ACK 后也进入该状态。
  • FIN_WAIT_1:表示主动关闭连接。无论哪方调用 close 函数发送 FIN 报文都会进入这个这个状态。
  • FIN_WAIT_2:表示被动关闭方同意关闭连接。主动关闭连接方收到被动关闭方返回的 ACK 后,会进入该状态。
  • TIME_WAIT:表示收到对方的 FIN 报文并发送了 ACK 报文,就等 2MSL 后即可回到 CLOSED 状态了。如果 FIN_WAIT_1 状态下,收到对方同时带 FIN 标志和 ACK 标志的报文时,可以直接进入 TIME_WAIT 状态,而无须经过 FIN_WAIT_2 状态。
  • CLOSING:表示双方同时关闭连接。如果双方几乎同时调用 close 函数,那么会出现双方同时发送 FIN 报文的情况,就会出现 CLOSING 状态,表示双方都在关闭连接。
  • CLOSE_WAIT:表示被动关闭方等待关闭。当收到对方调用 close 函数发送的 FIN 报文时,回应对方 ACK 报文,此时进入 CLOSE_WAIT 状态。
  • LAST_ACK:表示被动关闭方发送 FIN 报文后,等待对方的 ACK 报文状态,当收到 ACK 后进入CLOSED状态。
tcp连接与释放

TCP 是面向连接的、可靠的字节流协议。因此,在传输数据之前通信双方必须建立一个 TCP 连接,建立 TCP 连接需要在服务器和客户端之间进行三次握手。通信双方数据传输完毕之后进行连接释放,释放连接需要在通信双方之间进行四次挥手。

三次握手

TCP 连接的正常建立过程通信双方需要三次握手,其过程如下图所示:


20141130145008214.png

TCP 协议提供可靠的连接服务,采用有保障的三次握手方式来创建一个 TCP 连接。三次握手的具体过程如下:

1、客户端进程向服务端发出连接请求,请求报文的报文段首部中的控制位标志 SYN=1(有关 TCP 控制位信息参考《TCP 协议》),由于是首次请求建立连接,因此,控制位标志 ACK=0,该报文段包含计算机随机生成的初始序号 seq=x。发送请求连接的 TCP 报文段,此时客户端进程进入 SYN_SENT 状态,这是 TCP 连接的第一次握手。

2、服务端收到客户端发来的请求报文后,若同意建立连接,则向客户端发送确认。确认报文中的控制位 SYN=1,ACK=1,确认应答号 ack=x+1(即在接收到序列号值基础上加 1 ),并且发送自己的一个初始序列号 seq=y(即请求与客户端连接)。此时,服务端进入SYN_RCVD状态,这是TCP连接的第二次握手。

3、客户端进程收到服务端进程的确认报文后,还要向服务端发出确认信息。确认报文段的控制位 ACK=1,确认应答号 ack=y+1(即在接收到序列号值基础上加 1 ),此时,客户端进入 ESTABLISHED 状态。服务器收到来自客户端的确认应答信息也进入 ESTABLISHED 状态。这是TCP连接的第三次握手。此时,TCP 连接成功建立。

四次挥手

由于 TCP 连接是全双工的,因此每个方向都必须单独进行关闭。原则是主动关闭的一方发送一个 FIN 报文来表示终止这个方向的连接,收到一个 FIN 意味着这个方向不再有数据流动,但另一个方向仍能继续发送数据,直到另一个方向也发送 FIN 报文。TCP 连接释放的过程如下图所示:


20141130151440281.png

以下是释放连接的四次挥手过程:

1、客户端进程主动向服务端发出连接释放请求报文段,并停止发送数据,主动关闭 TCP 连接。释放连接报文段中控制位 FIN=1,序列号为 seq=i,发送该报文段之后客户端进入FIN_WAIT_1(终止等待1)状态,等待服务器的确认。这是 TCP 连接释放的第一次挥手。

2、服务器收到连接释放请求报文段后即发出确认释放连接的报文段,该报文段中控制位 ACK=1,确认应答号为 ack=i+1,然后服务器进入CLOSE_WAIT(关闭等待)状态。此时 TCP 处于半关闭状态,即客户端已经不向服务器发送数据,但服务器仍可向客户端发送数据。这是TCP连接释放的第二次挥手。

3、客户端收到服务器的确认信息后,就进入了FIN_WAIT_2(终止等待2)状态,等待服务器发出连接释放请求报文段,若没有数据需要传输,服务器被动向客户端发出链接释放请求报文段中,报文段中控制位 FIN=1,序列号 seq=j,此时服务器进入LAST_ACK(最后确认)状态,等待客户端的确认应答。这是 TCP 连接释放的第三次挥手。

4、客户端收到服务器的连接释放请求后,必须对此发出确认。确认报文段中控制位 ACK=1,确认应答号 ack=j+1,客户端发出确认应答信息之后后进入TIME_WAIT(时间等待)状态。在这段时间内 TCP连接并没有释放,必须等待 2MSL 时间后,客户端才进入 CLOSED 状态。服务器收到了客户端的确认应答后,就进入了 CLOSED 状态。直到客户端和服务器都进入 CLOSED 状态后,连接就完全释放了,这是TCP连接释放的第四次挥手。

从上面可以知道,tcp的3次握手,4次挥手保证了数据的可靠性。而udp为非面向连接协议并不能保证数据的可靠性。所以说tcp是安全可靠的,而udp是不可靠的。当然tcp保证数据可靠的原因不光是面向连接。

tcp报文结构

TCP 报文 (Segment),包括首部和数据部分。
下图是把 TCP 报文中的首部放大来看。


CFC6314E4B2FD039C450821D946E93E2.png

源端口 source port
目的端口 destination port
序号 sequence number
确认号 acknowledgment number
数据偏移 offset
保留 reserved
标志位 tcp flags
窗口大小 window size
检验和 checksum
紧急指针 urgent pointer
选项 tcp options

确认号

占 4 个字节。
表示期望收到对方下一个报文段的序号值。
TCP 的可靠性,是建立在「每一个数据报文都需要确认收到」的基础之上的。
就是说,通讯的任何一方在收到对方的一个报文之后,都要发送一个相对应的「确认报文」,来表达确认收到。
那么,确认报文,就会包含确认号。

窗口大小 Window Size

该字段明确指出了现在允许对方发送的数据量,它告诉对方本端的 TCP 接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。
窗口大小的值是指,从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量。
例如,假如确认号是 701 ,窗口字段是 1000。这就表明,从 701 号算起,发送此报文段的一方还有接收 1000 (字节序号是 701 ~ 1700) 个字节的数据的接收缓存空间。

校验和

由发送端填充,接收端对 TCP 报文段执行 CRC 算法,以检验 TCP 报文段在传输过程中是否损坏,如果损坏这丢弃。
检验范围包括首部和数据两部分,这也是 TCP 可靠传输的一个重要保障。

TCP可靠性实现

除了依靠连接过程的三次握手,tcp还有机制来保证传输的数据,无差错、不丢失、不重复、并且按序到达

1.超时重传机制

TCP 报文段在传输的过程中,下面的情况都是有可能发生的:
数据包中途丢失;
数据包顺利到达,但对方发送的 ACK 报文中途丢失;
数据包顺利到达,但对方异常未响应 ACK 或被对方丢弃;
当出现这些异常情况时,TCP 就会超时重传。
TCP 每发送一个报文段,就对这个报文段设置一次计时器。只要计时器设置的重传时间到了,但还没有收到确认,就重传这一报文段,这个就叫做「超时重传」。

2.滑动窗口 Sliding Window

TCP 头里有一个字段叫 Window,叫 Advertised-Window,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。
滑动窗口分为「接收窗口」和「发送窗口」
因为 TCP 协议是全双工的,会话的双方都可以同时接收和发送,那么就需要各自维护一个「发送窗口」和「接收窗口」。

发送窗口

大小取决于对端通告的接受窗口。
只有收到对端对于本端发送窗口内字节的 ACK 确认,才会移动发送窗口的左边界。

FCA43D210DF50C93E428DFD04FBBBF32.png

对于发送窗口,在缓存内的数据有四种状态:
#1 已发送,并得到接收方 ACK 确认;
#2 已发送,但还未收到接收方 ACK;
#3 未发送,但接收方允许发送,接收方还有空间
#4 未发送,且接收方不允许发送,接收方没有空间
如果下一刻,收到了接收方对于 32-36 字节序的数据包的 ACK 确认,那么发送方的窗口就会发生「滑动」。
并且发送下一个 46-51 字节序的数据包。

4C22A2B58DB2F0B885A0DC50057D2768.png
接收窗口

大小取决于应用、系统、硬件的限制。

F4B7AEDE41EE179676E79DEF2601D4A4.png

相对于发送窗口,接受窗口在缓存内的数据只有三种状态:

  • 已接收已确认;
  • 未接收,准备接收;
  • 未接收,并未准备接收;
    下一刻接收到来自发送端的 32-36 数据包,然后回送 ACK 确认报,并且移动接收窗口。
95A36446FAD21CC3DD086FA683942FFA.png

另外接收端相对于发送端还有不同的一点,只有前面所有的段都确认的情况下才会移动左边界,
在前面还有字节未接收但收到后面字节的情况下,窗口不会移动,并不对后续字节确认,以此确保对端会对这些数据重传。
假如 32-36 字节不是一个报文段的,而是每个字节一个报文段的话,那么就会分成了 5 个报文段。
在实际的网络环境中,不能确保是按序收到的,其中会有一些早达到,一些迟到达。

686E3FC14C2DEF657C61ECBC16C9C954.png

如图中的 34、35 字节序,先收到了,接收窗口也不会移动。
因为有可能 32、33 字节序会出现丢包或者超时,这时就需要发送端重发报文段了。

3.检验和

TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输 过程中的任何变化。如果收到段的检验和有差错, T P将丢弃这个报文段和不确认收到此报文段(希望发端超时并重发)。

4.拥塞控制

拥塞控制:防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提:网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机、路由器,以及与降低网络传输性能有关的所有因素

几种拥塞控制方法
慢开始( slow-start )、拥塞避免( congestion avoidance )、快重传( fast retransmit )和快恢复( fast recovery )。
具体算法参考:这里

tcp相关疑问

问:为什么在 TCP 协议里,建立连接是三次握手,而关闭连接却是四次握手?

因为当处于 LISTEN 状态的服务器端收到来自客户端的 SYN 报文(客户端希望新建一个TCP连接)时,它可以把 ACK (确认应答)和 SYN (同步序号)放在同一个报文里来发送给客户端。但在关闭 TCP 连接时,当收到对方的 FIN 报文时,对方仅仅表示对方已经没有数据发送给你了,但是你自己可能还有数据需要发送给对方,则等你发送完剩余的数据给对方之后,再发送 FIN 报文给对方来表示你数据已经发送完毕,并请求关闭连接,所以通常情况下,这里的 ACK 报文和 FIN 报文都是分开发送的。

问:为什么一定要进行三次握手?

当客户端向服务器端发送一个连接请求时,由于某种原因长时间驻留在网络节点中,无法达到服务器端,由于 TCP 的超时重传机制,当客户端在特定的时间内没有收到服务器端的确认应答信息,则会重新向服务器端发送连接请求,且该链接请求得到服务器端的响应并正常建立连接,进而传输数据,当数据传输完毕,并释放了此次 TCP 连接。若此时第一次发送的连接请求报文段延迟了一段时间后,到达了服务器端,本来这是一个早已失效的报文段,但是服务器端收到该链接请求后误以为客户端又发出了一次新的连接请求,于是服务器端向客户端发出确认应答报文段,并同意建立连接。如果没有采用三次握手建立连接,由于服务器端发送了确认应答信息,则表示新的连接已成功建立,但是客户端此时并没有向服务器端发出任何连接请求,因此客户端忽略服务器端的确认应答报文,更不会向服务器端传输数据。而服务器端却认为新的连接已经建立了,并在一直等待客户端发送数据,这样服务器端一直处于等待接收数据,直到超出计数器的设定值,则认为客户端出现异常,并且关闭这个连接。在这个等待的过程中,浪费服务器的资源。如果采用三次握手,客户端就不会向服务端发出确认应答信息,服务器端由于没有收到客户端的确认应答信息,从而判定客户端并没有请求建立连接,从而不建立该连接。

问:为什么需要在 TIME_WAIT 状态必须等待 2MSL 时间,而不直接给进入 CLOSED 状态?

TIME_WAIT 确保有足够的时间让对端收到了ACK,如果被动关闭的那方没有收到 ACK,就会触发被动端重发 FIN。因为最后一次确认应答 ACK 报文段很有可能丢失,因而使被动关闭方处于在LAST_ACK 状态的,此时被动关闭方会重发这个 FIN+ACK 报文段,在这等待的 2MSL 时间内主动关闭方重新收到这个被动关闭方重发的 FIN+ACK 报文段,因此,主动关闭方会重新发送确认应答信息,从而重新启动 2MSL 计时器,直到通信双方都进入 CLOSED 状态。如果主动关闭方在 TIME_WAIT 状态不等待一段时间就直接释放连接并进入 CLOSED 状态,那么主动关闭方无法收到来自被动关闭方重发的 FIN+ACK 报文段,也就不会再发送一次确认 ACK 报文段,因此被动关闭方就无法正常进入CLOSED 状态。
有足够的时间让这个连接不会跟后面的连接混在一起。防止已失效的请求连接出现在本连接中。在连接处于 2MSL 等待时,任何迟到的报文段将被丢弃,因为处于 2MSL等待的、由该插口(插口是IP和端口对的意思,socket)定义的连接在这段时间内将不能被再用,这样就可以使下一个新的连接中不会出现这种旧的连接之前延迟的报文段。

相关文章

TCP/IP详解学习笔记
TCP、UDP详解
TCP的建立与释放
理解UDP 和 TCP

推荐阅读更多精彩内容