🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] # 协议头 ![](https://img.kancloud.cn/fe/49/fe49e43a56c783df03da2fb2a222b4fc_800x324.png) * 至少占用24个字节 * source port:客户端的端口 * destination port:服务端口 * 端口最大值是2^16-1 * Sequence Number:顺序号,4个字节的整型,网络包分块传输时走的路由可能不一致,先发的包可能后到 * Acknowledgment Number:应答号, <br> <br> # 状态机 TCP 的状态机是很复杂的,并且与建立断开连接时的握手息息相关,接下来就来详细描述下两种握手。 <br> TCP有6种标示: * SYN(建立联机) * ACK(确认) * PSH(传送) * FIN(结束) * RST(重置) * URG(紧急) <br> ![](https://box.kancloud.cn/b2703a0278cde9c260ffe48a5fe6f90e_1273x960.png) <br> ## 建立连接三次握手 ![](https://box.kancloud.cn/e2bae0f3db470793491318a14be3b79f_745x381.png) 首先假设主动发起请求的一端称为客户端,被动连接的一端称为服务端。不管是客户端还是服务端,TCP 连接建立完后都能发送和接收数据,所以 TCP 是一个全双工的协议。 起初,两端都为 CLOSED 状态。在通信开始前,双方都会创建 TCB。 服务器创建完 TCB 后便进入 LISTEN 状态,此时开始等待客户端发送数据。 **第一次握手** 客户端向服务器发出连接请求报文,这时报文首部中的同部位 **SYN=1**,同时随机生成初始序列号 **seq=x**,此时,TCP客户端进程进入了 **SYN-SENT**(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。这个三次握手中的开始。表示客户端想要和服务端建立连接。 **第二次握手** TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 **ACK=1**,**SYN=1**,确认号是 **ack=x+1**,同时也要为自己随机初始化一个序列号 seq=y,此时,TCP服务器进程进入了 SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。这个报文带有SYN(建立连接)和ACK(确认)标志,询问客户端 是否准备好。 **第三次握手** TCP客户进程收到确认后,还要向服务器给出确认。确认报文的 **ACK=1**, **ack=y+1**,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。 TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。这里客户端表示我已经准备好。 <br> ### 每一次握手的作用 第一次握手:Client 什么都不能确认;Server 确认了对方发送正常 第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己接收正常,对方发送正常 第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送接收正常 所以三次握手就能确认双发收发功能都正常,缺一不可。 <br> ### 为什么要三次握手 举例:已失效的连接请求报文段。    client发送了第一个连接的请求报文,但是由于网络不好,这个请求没有立即到达服务端,而是在某个网络节点中滞留了,直到某个时间才到达server,本来这已经是一个失效 的报文,但是server端接收到这个请求报文后,还是会想client发出确认的报文,表示同意连接。假如不采用三次握手,那么只要server发出确认,新的建立就连接了,但其实这个 请求是失效的请求,client是不会理睬server的确认信息,也不会向服务端发送确认的请求,但是server认为新的连接已经建立起来了,并一直等待client发来数据,这样,server的 很多资源就没白白浪费掉了,采用三次握手就是为了防止这种情况的发生,server会因为收不到确认的报文,就知道client并没有建立连接。这就是三次握手的作用。 ## 断开链接四次握手 ![](https://box.kancloud.cn/cfe752dc7fe8dbca061f3f59c60184bc_1844x1094.png) TCP 是全双工的,在断开连接时两端都需要发送 FIN 和 ACK。 **第一次握手** TCP发送一个 **FIN(结束)**,用来关闭客户到服务端的连接。     客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,**FIN=1**,其序列号为**seq=u**(等于前面已经传送过来的数据的最后一个字节的序号加1), 此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。 <br> <br> **第二次握手** 服务端收到这个FIN,他发回一个**ACK(确认)**,确认收到序号为收到序号+1,和SYN一样,一个FIN将占用一个序号。 <br>     服务器收到连接释放报文,发出确认报文,**ACK=1,ack=u+1**,并且带上自己的序列号 **seq=v**,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器 通知高层的应用进程,客户端向服务器的方向就释放了,**这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受**。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。 <br> 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。 <br> <br> **第三次握手** 服务端发送一个 **FIN(结束)** 到客户端,服务端关闭客户端的连接。      服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。 <br> <br> **第四次握手**  客户端发送 **ACK(确认)** 报文确认,并将确认的序号+1,这样关闭完成。 客户端收到服务器的连接释放报文后,必须发出确认,**ACK=1,ack=w+1**,而自己的序列号是 seq=u+1,此时,客户端就进入了 TIME-WAIT(时间等待)状态。注意此时 TCP 连接还没有释放,必须经过2∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。 **为什么 A 要进入 TIME-WAIT 状态,等待 2MSL 时间后才进入 CLOSED 状态?** 为了保证 B 能收到 A 的确认应答。若 A 发完确认应答后直接进入 CLOSED 状态,如果确认应答因为网络问题一直没有到达,那么会造成 B 不能正常关闭。 ### 为什么要四次挥手 **为了确保数据能够完成传输。** 关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。可能有人会有疑问,tcp我握手的时候为何ACK(确认)和SYN(建立连接)是一起发送。挥手的时候为什么是分开的时候发送呢. <br> 因为当 Server 端收到 Client 端的 SYN 连接请求报文后,可以直接发送 SYN+ACK 报文。其中 ACK 报文是用来应答的,SYN 报文是用来同步的。但是关闭连接时,当 Server 端收到 FIN 报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个 ACK 报文,告诉 Client 端,"你发的FIN报文我收到了"。只有等到我 Server 端所有的报文都发送完了,我才能发送 FIN 报文,因此不能一起发送。故需要四步握手。 <br> <br> # TCP 协议如何保证可靠传输 1. 应用数据被分割成 TCP 认为最适合发送的数据块。 2. TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。 3. **校验和:** TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。 4. TCP 的接收端会丢弃重复的数据。 5. **流量控制:** TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制) 6. **拥塞控制:** 当网络拥塞时,减少数据的发送。 7. **停止等待协议** 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。 **超时重传:** 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。 <br> ## 停止等待协议 * 停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组; * 在停止等待协议中,若接收方收到重复分组,就丢弃该分组,但同时还要发送确认; **1) 无差错情况:** ![](https://user-gold-cdn.xitu.io/2018/8/16/16541fa8c3816a90?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 发送方发送分组,接收方在规定时间内收到,并且回复确认.发送方再次发送。 **2) 出现差错情况(超时重传):** ![](https://user-gold-cdn.xitu.io/2018/8/16/16541faefdf249ab?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重转时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为 **自动重传请求 ARQ** 。另外在停止等待协议中若收到重复分组,就丢弃该分组,但同时还要发送确认。**连续 ARQ 协议** 可提高信道利用率。发送维持一个发送窗口,凡位于发送窗口内的分组可连续发送出去,而不需要等待对方确认。接收方一般采用累积确认,对按序到达的最后一个分组发送确认,表明到这个分组位置的所有分组都已经正确收到了。 **3) 确认丢失和确认迟到** * **确认丢失**:确认消息在传输过程丢失 ![](https://user-gold-cdn.xitu.io/2018/8/16/16541fb6941a7165?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 当A发送M1消息,B收到后,B向A发送了一个M1确认消息,但却在传输过程中丢失。而A并不知道,在超时计时过后,A重传M1消息,B再次收到该消息后采取以下两点措施: 1. 丢弃这个重复的M1消息,不向上层交付。 2. 向A发送确认消息。(不会认为已经发送过了,就不再发送。A能重传,就证明B的确认消息丢失)。 * **确认迟到** :确认消息在传输过程中迟到 ![](https://user-gold-cdn.xitu.io/2018/8/16/16541fdd85929e6b?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) A发送M1消息,B收到并发送确认。在超时时间内没有收到确认消息,A重传M1消息,B仍然收到并继续发送确认消息(B收到了2份M1)。此时A收到了B第二次发送的确认消息。接着发送其他数据。过了一会,A收到了B第一次发送的对M1的确认消息(A也收到了2份确认消息)。处理如下: 1. A收到重复的确认后,直接丢弃。 2. B收到重复的M1后,也直接丢弃重复的M1。 ## 自动重传请求 ARQ 协议 停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重转时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为自动重传请求ARQ。 **优点:** 简单 **缺点:** 信道利用率低 ## 连续ARQ协议 连续 ARQ 协议可提高信道利用率。发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明到这个分组为止的所有分组都已经正确收到了。 **优点:** 信道利用率高,容易实现,即使确认丢失,也不必重传。 **缺点:** 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5条 消息,中间第三条丢失(3号),这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落,而只好把后三个全部重传一次。这也叫 Go-Back-N(回退 N),表示需要退回来重传已经发送过的 N 个消息。 ## 滑动窗口 * TCP 利用滑动窗口实现流量控制的机制。 * 滑动窗口(Sliding window)是一种流量控制技术。早期的网络通信中,通信双方不会考虑网络的拥挤情况直接发送数据。由于大家不知道网络拥塞状况,同时发送数据,导致中间节点阻塞掉包,谁也发不了数据,所以就有了滑动窗口机制来解决此问题。 * TCP 中采用滑动窗口来进行传输控制,滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为 0 时,发送方一般不能再发送数据报,但有两种情况除外,一种情况是可以发送紧急数据,例如,允许用户终止在远端机上的运行进程。另一种情况是发送方可以发送一个 1 字节的数据报来通知接收方重新声明它希望接收的下一字节及发送方的滑动窗口大小。 ## 流量控制 * TCP 利用滑动窗口实现流量控制。 * 流量控制是为了控制发送方发送速率,保证接收方来得及接收。 * 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。 ## 拥塞控制 在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。 为了进行拥塞控制,TCP 发送方要维持一个 **拥塞窗口(cwnd)** 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。 TCP的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免** 、**快重传** 和 **快恢复**。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。 * **慢开始:** 慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd初始值为1,每经过一个传播轮次,cwnd加倍。 ![](https://user-gold-cdn.xitu.io/2018/8/10/1652348ada2c8fd0?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) * **拥塞避免:** 拥塞避免算法的思路是让拥塞窗口cwnd缓慢增大,即每经过一个往返时间RTT就把发送放的cwnd加1. * **快重传与快恢复:** 在 TCP/IP 中,快速重传和恢复(fast retransmit and recovery,FRR)是一种拥塞控制算法,它能快速恢复丢失的数据包。没有 FRR,如果数据包丢失了,TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR,如果接收机接收到一个不按顺序的数据段,它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认,它会假定确认件指出的数据段丢失了,并立即重传这些丢失的数据段。有了 FRR,就不会因为重传时要求的暂停被耽误。  当有单独的数据包丢失时,快速重传和恢复(FRR)能最有效地工作。当有多个数据信息包在某一段很短的时间内丢失时,它则不能很有效地工作。 # 参考资料 [前端面试之道 - 掘金小册](https://juejin.im/book/5bdc715fe51d454e755f75ef/section/5bdc729af265da615a414603) [搞定计算机网络面试,看这篇就够了(补充版)](https://juejin.im/post/5b7be0b2e51d4538db34a51e#heading-18) [【TCP协议】(2)---TCP三次握手和四次挥手](https://www.cnblogs.com/qdhxhz/p/8470997.html)