--- TCP 口水话 --- #### 目录 1. 概述 2. TCP 头部 3. 三次握手 4. 四次挥手 5. 重传机制 6. 滑动窗口 7. 流量控制 #### 概述 TCP 是一个可靠的、面向连接的、基于字节流、全双工的协议。 面向连接的协议要求正式发送数据之前需要通过握手建立一个逻辑连接,结束通信时也是通过有序的四次挥手来断开连接。 TCP 协议是可靠的,而 IP 是一种无连接、不可靠的协议。IP 协议尽最大可能将数据报从发送者传输给接受者,但并不保证包到达的顺序会与它们被传输的顺序一致,也不保证包是否重复,甚至都不保证包是否会到达接受者。TCP 要想在 IP 的基础上构建可靠的传输层协议,必须有一个复杂的机制来保障可靠性,主要有:对每个包提供校验和、包的序列号解决了接收数据的乱序、重复问题、超值重传机制以及流量控制、拥塞控制。 TCP 是面向字节流的协议,流的含义是没有固定的报文边界,这就需要应用程序定义自己的消息分隔符,比如 HTTP 中的 \r\n。 TCP 是全双工的协议,通信的双方在任意时刻既可以是接收数据也可以是发送数据,每个方向的数据流都独立管理序列号、滑动窗口大小、MSS 等信息。 #### TCP 头部 TCP 头部是支撑 TCP 复杂功能的基石,它包括源端口、目标端口、序列号、确认号、头部长度、校验和以及一些 SYN、ACK 标识位等等。 ![img](https://user-gold-cdn.xitu.io/2019/9/27/16d702629b61cbcc?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 下面主要说一下标识位的意思: SYN:用于发起连接同步双方的初始序列号 ACK:确认数据包 RST:强制断开连接 FIN:通知对端已经发送完数据了,准备断开连接,后续也不会再发数据了 PSH:告知对方这些数据包收到以后应该马上交给上层应用,不能缓存起来 #### 三次握手 ![img](https://user-gold-cdn.xitu.io/2019/6/13/16b518ccedac1b6e?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 三次握手的最重要的就是交换彼此的 ISN(初始序列号)。客户端会随机选择一个数字作为初始序列号。SYN 报文不携带数据,但是它占用一个序号,下次发送数据序列号要加一。之所以要消耗一个序列号是因为它需要对端确认,不占用序列号的段是不需要确认的,比如 ACK。总结来说,凡是消耗序列号的 TCP 报文段,一定需要对端确认,如果这个段没有收到确认,会一直重传直至到达指定的次数为止。 除了交换彼此的初始序列号,三次握手的另一个重要作用是交换一些辅助信息,比如最大段大小 MSS、窗口大小 Win、窗口缩放因子 WS 等。 ![img](https://user-gold-cdn.xitu.io/2019/6/13/16b518cd1664fa5d?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 当客户端发送 SYN 到对端,服务端收到后会回复 SYN+ACK,此时会将连接信息放入半连接队列,服务端同时会开启一个定时器,如果超时还未收到 ACK 就会进行 SYN+ACK 重传。一旦收到客户端的 ACK,服务端就开始尝试把它加入另一个全连接队列。 SYN 洪水攻击就是客户端伪造 IP 发送 SYN 包,服务端回复的 ACK+SYN 去到了一个未知的 IP 地址,势必会造成服务端大量连接处于 SYN_RCVD 状态。而服务器的半连接队列大小也是有限的,如果半连接队列满了,也会出现无法处理正常请求的情况。解决 SYN Flood 攻击的办法是 SYN Cookie 机制,它的原理其实很简单,就是在三次握手的最后阶段才分配连接资源。服务端在收到 SYN 包后不马上分配内存资源,而是根据这个 SYN 包计算出一个 Cookie 值,作为握手第二步的序列号回复 SYN+ACK,等对方回应 ACK 包是校验回复的 ACK 是否合法,如果合法握手成功,分配资源。 #### 四次挥手 ![img](https://user-gold-cdn.xitu.io/2019/6/26/16b911c618264239?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) FIN 同 SYN 报文一样,不管是否携带数据,FIN 段都需要消耗一个序列号,如果 FIN 段不消耗序列号,那么在客户端收到 ACK 报文时是不知道是 Data 的确认包还是 FIN 的确认包。 在实际情况下,四次挥手进程会变成三次,即第二步和第三步合并了。在客户端发送 FIN 包之后,会进入半关闭状态,表示自己不会再给对方发送数据了,但是服务端还是可以给自己发送数据的,在这种情况下,如果不及时发送 ACK,死等服务端这边发送数据,可能会造成客户端不必要的重发 FIN 包。如果服务端确定没有什么数据需要发送给客户端,当然就可以把 FIN 和 ACK 合并成一个包发送,四次挥手的过程也就变成了三次。 同理,三次握手也可以变成四次握手,只是把第二步的 SYN+ACK 拆分开来。与 FIN 不同的是,一般情况下,SYN 包都不携带数据,收到客户端的 SYN 包以后不用等待,可以立马回复 SYN+ACK,四次握手理论上可行,但是现实中并不常见。