TCP 报文格式
三次握手
三次握手,其实就是指建立一个 TCP 连接时,需要客户端和服务端发送三个数据包,主要作用就是为了确认双方的接受能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备;
建立连接的步骤
- 第一次握手
- 客户端向服务端发送一个数据包:
SYN=1
代表要发起新的连接,并初始化序列号(随机生成)seq=x
,此时客户端处于SYN_SENT
状态; SYN=1
的报文段不能携带数据,但要消耗掉一个序号;
- 客户端向服务端发送一个数据包:
- 第二次握手
- 服务端接收到客户端发送的数据包之后:回应发出
SYN=1
服务端也发起新的连接,发送ACK=1
标记ack
确认序列号是有效的,并携带确认序列号ack=x+1
,初始化服务端的初始化序列号(随机生成)seq=y
;
- 服务端接收到客户端发送的数据包之后:回应发出
- 第三次握手
- 客户端收到了服务端发送的数据包之后:回应发出
ACK=1
标记ack
确认序列号是有效的,并携带确认序列号ack=y+1
,初始化客户端初始序列号seq=x+1
(初始为seq=x
,第二个报文段所以要+1); - 此时双方已经建立了连接;
- 客户端收到了服务端发送的数据包之后:回应发出
问题
- 为什么 http 建立连接需要三次握手?
- 三次握手的目的:就是为了确认服务端和客户端双方的接受能力和发送能力是否正常;
- 第一次握手:客户端发送网络数据包,服务端收到了 —-服务端得出结论:客户端的发送能力、服务端的接受能力正常;
- 第二次握手:服务端发送网络数据包,客户端收到了 —-客户端得出结论:客户端和服务端的发送、接受能力正常,服务端不能确定客户端的接受能力;
- 第三次握手:客户端发送网络数据包,服务端收到了 —-服务端得出结论:客户端和服务端的发送、接受能力正常;
- 因此需要三次握手才能确认双方的接受和发送能力是否正常;
- 三次握手的目的:就是为了确认服务端和客户端双方的接受能力和发送能力是否正常;
- 假如 http 建立连接是两次握手会发生什么?
- 如果是两次握手,B 收到 A 的 SYN 包后状态即为:ESTABLISHED,此时 B 再给 A 发送一个
SYN+ACK
包,由于此时 B 已经为 ESTABLISHED,所以可以向 A 发送数据,如果数据包先于SYN+ACK
包到达 A ,A 就尴尬了,A 没有收到 B 的 ISN ,没法判断这些数据包是否能用(< ISN 该直接丢弃),一个优雅牛逼的协议是没法容忍这些的; - 如果是三次握手,B 收到 A 的 SYN 包后状态即为:SYN_RECV,是没法发送包的;
- 但有一点是相同的,无论是 SYN_RECV 或者 ESTABLISHED,都是一种等待状态,都需要发送数据包确认对方是否还在(SYN_RECV 会出现 SYN flood);
- 如果是两次握手,B 收到 A 的 SYN 包后状态即为:ESTABLISHED,此时 B 再给 A 发送一个
- 什么是半连接队列?
- 服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立连接,服务器会把此状态下的请求放到一个半连接队列里面;
- 当然还有一个全连接队列,就是已经完成三次握手建立连接的放入到全连接队列;如果队列满了就有可能出现丢包现象;
- 注意:
- 服务器发送完 SYN-ACK 包,如果没有收到客户端确认,会进行首次重传,等待一段时间仍未收到确认,会继续重传,超过重传次数之后,在半连接队列中清除该连接;
- 每次重传的时间不一定相同,一般是指数增长;
- ISN(Initial Sequence Number)初始化序列号是固定的吗?
- ISN 随时间变化,这样 ISN 在网络延迟中,即使又被传送,也不会导致某个连接对它有错误的解释;
- 三次握手的重要功能是客户端和服务端相互交换 ISN,以便然给对方知道接下来接收数据的时候如何组装数据,如果 ISN 固定的,攻击者很容易猜出来后续的确认号;
- 三次握手过程中可以携带数据吗?
- 第三次握手的时候,可以携带,前两次握手不能携带数据;
- 如果前两次握手能够携带数据,那么一旦有人想攻击服务器,那么他只需要在第一次握手中的 SYN 报文中放大量数据,那么服务器势必会消耗更多的时间和内存空间去处理这些数据,增大了服务器被攻击的风险。而对于第三次握手,此时客户端已经建立了连接,已经能够确认服务器的接收、发送能力正常,这个时候相对安全了,可以携带数据。
- 如果第三次握手丢失了,客户端服务端会如何处理?
- Server 端
- 第三次的 ACK 在网络中丢失,那么 Server 端该 TCP 连接的状态为 SYN_RECV,并且会根据 TCP 的超时重传机制,会等待 3 秒、6 秒、12 秒后重新发送 SYN+ACK 包,以便 Client 重新发送 ACK 包;
- Server 重发 SYN+ACK 包的次数,可以通过设置 /proc/sys/net/ipv4/tcp_synack_retries 修改,默认值为 5;
- 如果重发指定次数之后,仍然未收到 client 的 ACK 应答,那么一段时间后,Server 自动关闭这个连接;
- Client 端
- client 在接收到 SYN+ACK 包,它的 TCP 连接状态就为 established (已连接),表示该连接已经建立;
- 第三次握手中的 ACK 包丢失的情况下,Client 向 server 端发送数据,Server 端将以 RST 包响应,方能感知到 Server 的错误。
- Server 端
- SYN 攻击是什么?
- Clint 在一段时间内伪造大量的不存在的 IP,不断的像服务器发送 SYN,服务器回复确认包;
- 由于源地址不存在,则服务端不断重发直至超时;
- 伪造的 SYN 包会长时间占用未连接队列,导致正常的 SYN 请求因队列满而被丢弃,从而引起网络堵塞甚至系统瘫痪;
- 如果已经建立了连接,但是客户端突然出现故障了怎么办?
- TCP 还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源;
- 服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为 2 小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔 75 秒钟发送一次;
- 若一连发送 10 个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接;
四次挥手
四次挥手(waves four times),别名连接终止协议,网络数据通信术语,其性质为终止协议;
断开连接的步骤
- 第一次挥手:
- 客户端进程发出连接 释放报文,并且停止发送数据;
- 发送 FIN=1 要结束连接,初始化序列号为 seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入 FIN-WAIT-1(终止等待1)状态;
- TCP 规定,FIN 报文段即使不携带数据,也要消耗一个序号;
- 第二次挥手:
- 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号 seq=v,此时,服务端就进入了 CLOSE-WAIT(关闭等待)状态;
- TCP 服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个 CLOSE-WAIT 状态持续的时间;
- 客户端收到服务器的确认请求后,此时,客户端就进入 FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据);
- 第三次挥手:服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了 LAST-ACK(最后确认)状态,等待客户端的确认;
- 第四次挥手:客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了 TIME-WAIT(时间等待)状态,注意此时 TCP 连接还没有释放,必须经过2MSL(最长报文段寿命)的时间后,才进入 CLOSED 状态;
问题
- 四次挥手释放连接时,等待 2MSL 的意义?
- 必须假想网络是不可靠的,有可能最后一个 ACK 丢失,所以 TIME_WAIT 状态就是用来重发可能丢失的 ACK 报文;
- Server 如果没有收到 ACK,将不断重复发送 FIN 片段。所以 Client 不能立即关闭,它必须确认 Server 接收到了该 ACK;
- Client 会在发送出 ACK 之后进入到 TIME_WAIT 状态,如果直到 2MSL,Client 都没有再次收到 FIN,那么Client 推断 ACK 已经被成功接收,则结束 TCP 连接;
- 挥手为什么要四次?
- 关闭连接时,当 Server 端收到 FIN 报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个 ACK 报文,告诉 Client 端,”你发的 FIN 报文我收到了”;
- 只有等到我 Server 端所有的报文都发送完了,我才能发送 FIN 报文,因此不能一起发送。故需要四步;
第 4️⃣ 座大山:手写 promise
上一篇