错误现象
丢包
- 网络问题:网络不稳定可能导致丢包,例如信号弱或干扰强。带宽限制可能导致路由器或交换机丢弃包,尤其是在高流量时段。网络拥塞时,多个数据流竞争有限的资源,也可能导致丢包。
- 缓冲区溢出:TCP使用缓冲区暂存数据包。如果缓冲区满了,新的数据包就会被丢弃。这通常发生在高速发送端和低速接收端之间的通信中。
- 程序处理错误:软件错误,如编程错误或资源管理不善,也可能导致数据包丢失。
损坏
- 传输链路上的噪声:电磁干扰等噪声可能导致比特级的错误,从而损坏数据包。
- 设备故障:路由器、交换机或其他网络设备的硬件故障也可能导致数据包损坏。
延迟
- 网络拥塞:当网络中的数据包数量超过网络能够处理的能力时,会导致延迟。
- 路由选择:数据包可能因为路由算法选择了较长的路径而导致延迟。
- 乱序:由于网络中的不同延迟路径,数据包可能会以不同的顺序到达目的地。
- 历史报文:旧的TCP连接的数据包可能在新的连接中被错误地接收。
连接建立阶段
-
三次握手:三次握手的目的是确保双方都具有接收和发送的能力,防止乱序报文影响,以及确保双方的初始序列号能被可靠地同步。如果只有两次握手,可能会因为网络时延和超时重发导致资源浪费。但是如果有第三次握手,服务端可以等待第三次握手的结果再判断进入什么状态。
-
初始化序列号随机:初始化的序列号需要随机,以避免历史报文的影响和防止黑客冒充TCP报文。如果碰到序列号不在接收方的接收窗口的直接丢弃。
-
序列号回环:TCP的SEQ号是有限的,一共32位,SEQ开始是递增,溢出之后从0开始再次依次递增。因为存在序列号回环,就仍然存在历史报文被错误接收的现象。此时需要开启TCP的时间戳功能,从而启用PAWS机制,如果收到的包时间戳不是递增的就丢弃。
-
报文丢失:如果是握手阶段的报文丢失,那么由发送方进行超时重传,每次发送触发的RTO翻倍,直到达到最大重传次数断开。如果是第二、三次握手,双方都会认为自己没有发送成功,双方都会进行超时重传,一直没回应就断开。
-
连接过多:在高压环境下,TCP通过半连接队列(SYN队列)和全连接队列(accept队列)来应对。如果accept队列满,可以设置服务端发来的ACK还是回复RST断开连接,或者调大accept队列大小。如果SYN队列满,可以启用syncookies功能,或者调大SYN队列大小。
-
SYN攻击:SYN攻击是攻击者伪造IP地址发送SYN请求,服务端收到大量请求但是发送的SYN+ACK没有回应,导致半连接队列占满,后续的SYN报文就会丢弃。防范措施包括调大接收数据包的队列的大小,减少SYN+ACK重传次数,增大SYN队列大小,以及启用syncookies功能。
数据传输阶段
-
TCP分段:TCP分段的目的是尽量避免IP层的分片。因为IP层没有重发机制,如果在TCP层不分段而有IP层分片,如果丢了其中的一个IP分片,在接收端由于没有收到完整的TCP报文(被IP层割断),不会发送ACK报文,发送端就会触发超时重传,重发整个TCP报文。因此在连接的开始阶段,双方就要协商MSS大小,设定为传输路径上的最小值PMTU(Path MTU)。
-
粘包:粘包是因为TCP是面向字节流的协议,因此会存在两个消息分到同一个TCP报文中,无法区分边界。解决粘包的方法包括固定用户消息的长度,使用特殊字符作为边界,如回车、换行,以及自定义消息结构,结构体存储数据和本段数据长度。
-
乱序/丢包/重复:TCP会先将乱序的包放入缓冲区的乱序队列中,如果后续收到了这个报文的前一个报文,TCP就从缓冲区中取出这个乱序报文,然后按照正确的顺序处理这些报文。如果长时间没有收到,就会认为是丢包了,接收方发送ACK/SACK,发送方会进行重传。当接收端收到重复的数据包时,会发送DSACK给发送端。