简介
TCP 是一个可靠的(reliable)、面向连接的(connection-oriented)、基于字节流(byte-stream)、全双工(full-duplex)的协议。发送端在发送数据以后启动一个定时器,如果超时没有收到对端确认会进行重传,接收端利用序列号对收到的包进行排序、丢弃重复数据,TCP 还提供了流量控制、拥塞控制等机制保证了稳定性。
TCP报文格式
注意TCP 的报文里是没有源 ip 和目标 ip 的,因为那是 IP 层协议的事情,TCP 层只有源端口和目标端口。
- 序列号:指的是本报文段第一个字节的序列号。可以利用序列号来解决网络包乱序、重复的问题,以保证数据包以正确的顺序组装传递给上层应用。
- 确认号:告知对方下一个期望接收的序列号,小于此确认号的所有字节都已经收到。用来解决不丢包的问题。(Ack=Seq+1)
- 校验和:由发送端计算和存储,由接收端校验。解决数据正确性问题
- 窗口大小:窗口大小。接收端期望接收的字节数,用于流量控制的。
- TCP Flags:也就是包的类型,通常所说的 SYN、ACK、FIN、RST 其实只是把 flags 对应的 bit 位置为 1 而已。
最常见的有下面这几个:
- SYN(Synchronize):用于发起连接数据包同步双方的初始序列号
- ACK(Acknowledge):确认数据包
- RST(Reset):这个标记用来强制断开连接,通常是之前建立的连接已经不在了、包不合法、或者实在无能为力处理
- FIN(Finish):通知对方我发完了所有数据,准备断开连接,后面我不会再发数据包给你了。
- PSH(Push):告知对方这些数据包收到以后应该马上交给上层应用,不能缓存起来
对于TCP协议,要成功建立一个新的链接,需要保证新链接四个要素组合体的唯一性:客户端的IP、客户端的port、服务器端的IP、服务器端的port。也就是说,服务端的同一个IP和port,可以和同一个客户端的多个不同端口成功建立多个TCP链接(与多个不同的客户端当然也可以)
三次握手
- 客户端发送SYN,表明要向服务器建立连接。同时带上序列号Seq=ISN(c)
- 服务器返回ACK(序号为客户端序列号+1)作为确认。同时发送SYN作为应答Seq=ISN(s)
- 客户端发送ACK确认收到回复(序列号为服务端序列号+1)
为什么是三次握手而不是二次或者四次?
- 第一次: A->B,B确定A有发消息的能力。
- 第二次: B->A,A确定B有收消息(通过第一次)的能力,并且有发消息的能力。
- 第三次: A->B,B确定A有收消息的能力。
简单来说就是双方各一次握手,各一次确认,其中一次握手和确认合并在一起,双方都确认对方有收 发消息的能力(二次握手达不到目的,四次又多余)
为了实现可靠数据传输,三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤如果只是两次握手,至多只有连接发起方的起始序列号能被确认,另一方选择的序列号则得不到确认.
四次挥手
- 客户端调用 close 方法,执行「主动关闭」,会发送一个 FIN 报文给服务端,从这以后客户端不能再发送数据给服务端了,客户端进入FIN-WAIT-1「半关闭(half-close)状态」,FIN 报文其实就是将 FIN 标志位设置为 1。
- 服务端收到 FIN 包以后回复确认 ACK 报文给客户端,服务端进入 CLOSE_WAIT,客户端收到 ACK 以后进入FIN-WAIT-2状态。
- 服务端也没有数据要发送了,发送 FIN 报文给客户端,然后进入LAST-ACK 状态,等待客户端的 ACK。
- 客户端收到服务端的 FIN 报文以后,回复 ACK 报文用来确认第三步里的 FIN 报文,进入TIME_WAIT状态,等待 2 个 MSL 以后进入 CLOSED状态。服务端收到 ACK 以后也进入CLOSED状态。
为什么挥手要四次,变为三次可以吗?
当然可以,因为有延迟确认的存在,把第二步的 ACK 经常会跟随第三步的 FIN 包一起捎带会对端。如果不及时发送 ACK 包,死等服务端这边发送数据,可能会造成客户端不必要的重发 FIN 包。如果服务端确定没有什么数据需要发给客户端,那么当然是可以把 FIN 和 ACK 合并成一个包,四次挥手的过程就成了三次。
为什么时间是两个 MSL(报文最大生存时间)?
- 1 个 MSL 确保四次挥手中主动关闭方最后的 ACK 报文最终能达到对端
- 1 个 MSL 确保对端没有收到 ACK 重传的 FIN 报文可以到达
2MS = 去向 ACK 消息最大存活时间(MSL) + 来向 FIN 消息的最大存活时间(MSL)
TIME_WAIT 状态存在的意义是可靠的实现 TCP 全双工的连接终止(处理最后 ACK 丢失的情况,服务端重发FIN到客户端重发ACK的时间和)以及避免当前关闭连接与后续连接混淆(让旧连接的包在网络中消逝,已经足够让一个方向上的包最多存活 MSL 秒就被丢弃。四元素不变经过连接 释放 连接 -> 保证了第二次创建新的TCP连接以后,老连接姗姗来迟的包已经在网络中被丢弃消逝,不会干扰新的连接。)