433
社区成员




接收方在成功接收到数据后,会向发送方发送一个确认信号(ACK),告知发送方数据已正确接收。通过ACK机制,发送方可以确认数据是否成功到达接收方,从而决定是否需要重传。
重传机制是TCP确保数据可靠传输的核心。当发送方未收到ACK或检测到数据丢失时,会触发重传。主要包括:
TCP通过序号机制为每个数据段分配唯一的序列号(Sequence Number),接收方根据序号对数据段进行排序和重组。序号机制确保数据的有序性和完整性,避免数据丢失或乱序。
窗口机制用于控制发送方的数据发送速率,避免接收方缓冲区溢出。主要包括:
流量机制通过控制数据发送速率,避免网络拥塞和接收方过载。主要包括:
带宽机制用于优化网络资源利用率,确保数据传输的高效性。主要包括:
tcp 面向字节流
TCP将数据视为连续的字节流,而不是独立的数据包。发送方和接收方之间建立连接后,数据以字节流的形式传输,接收方按顺序接收并重组数据。
类似打电话
udp 面向报文
UDP将数据视为独立的数据报(报文),每个数据报都有明确的边界。发送方和接收方之间无需建立连接,数据报直接发送。
类似发短信,每个信息之间是独立的
特性 | TCP | UDP |
---|---|---|
头部长度 | 20字节(不含选项) | 8字节 |
可靠性 | 可靠传输(确认、重传、排序机制) | 不可靠传输(无确认、重传机制) |
连接性 | 面向连接(需三次握手建立连接) | 无连接(直接发送数据报) |
数据边界 | 无边界(面向字节流) | 有边界(面向报文) |
控制位 | 有(SYN、ACK、FIN等) | 无 |
流量控制 | 有(窗口机制) | 无 |
拥塞控制 | 有(慢启动、拥塞避免等) | 无 |
校验和 | 强制 | 可选 |
适用场景 | 文件传输、网页浏览、电子邮件等 | 视频流、语音通话、在线游戏等 |
可靠指的是可以正常收到而且是顺序收到,ARQ协议就是干这个的
ARQ协议通过确认(ACK)和重传机制,确保数据在传输过程中不丢失、不重复、不乱序
(1)停等ARQ(Stop-and-Wait ARQ)
发送方每发送一个数据帧后,等待接收方的ACK。如果收到ACK,则发送下一个帧;如果超时未收到ACK,则重传当前帧
(2)回退N帧ARQ(Go-Back-N ARQ)
发送方可以连续发送多个数据帧,无需等待ACK。如果某个帧丢失或损坏,发送方从该帧开始重传所有后续帧。
(3)选择性重传ARQ(Selective Repeat ARQ)
发送方可以连续发送多个数据帧,接收方对每个帧单独确认。如果某个帧丢失或损坏,发送方仅重传该帧,而不影响其他帧。
序号机制
为每个数据帧分配唯一的序号,用于标识帧的顺序和检测丢失帧。
超时机制
发送方为每个帧设置超时计时器,超时未收到ACK则触发重传。
滑动窗口
通过滑动窗口控制发送方和接收方的缓冲区大小,优化数据传输效率。
RTO(Retransmission Timeout):重传超时时间,动态计算,基于RTT(Round-Trip Time,往返时间)。
RTT测量:通过时间戳或ACK的到达时间计算RTT,并更新RTO。
对发送方发送速率的控制,称之为流量控制(发的过快就放缓存,再发的多就TM丢了,大量的丢包会极大着浪费网络资源,要保持接收双方动态稳定)
接收窗口的大小不是固定的,而是根据某种算法动态调整,以适应网络环境和发送窗口的变化。
3. 接收窗口的优化
接收窗口并非越大越好。当窗口达到一定值后,继续增大不会显著减少丢包率,反而会增加内存消耗。因此,接收窗口的大小需要根据网络环境和发送窗口动态调整。
4. 发送窗口与接收窗口的关系
发送窗口和接收窗口不一定相等。接收方在发送确认报文时会告知发送方其接收窗口大小,发送方据此调整发送窗口。
由于接收方在处理缓存区数据的同时发送确认报文,因此一般情况下,接收窗口 ≥ 发送窗口。
初始阶段:拥塞窗口(cwnd)从1开始,指数增长(每轮次翻倍)。
触发条件:连接建立或检测到网络超时(超时视为严重拥塞)。
阈值作用:初始慢开始阈值(ssthresh)决定何时切换至拥塞避免阶段
线性增长:当cwnd ≥ ssthresh时,进入“加法增大”阶段,每轮次cwnd加1。
目标:避免因窗口过快膨胀引发网络拥塞。
触发条件:收到3个重复ACK(轻度拥塞信号)。
行为:
TCP Reno:将cwnd减半(图中从24降至12),更新ssthresh为新值(12),并直接进入拥塞避免(而非重新慢启动)。
TCP Tahoe(已废弃):直接重置cwnd=1并重新慢开始,效率较低。
在网络通畅的情况下,KCP以10%-20%带宽浪费的代价,换取了比TCP快30%-40%的传输速度。
流量不一定最多
KCP的传输速率可能低于TCP,但延迟更低,适合实时性要求高的场景。
RTO翻倍 vs 不翻倍
选择性重传 vs 全部重传
快速重传
fastresend = 2
延迟ACK vs 非延迟ACK
UNA vs ACK+UNA
比如:
发送端发送序列号1、2、3、4、5。
接收端收到1、2、4、5,丢失3。
接收端发送UNA=3(表示1、2已接收),并发送ACK=4、ACK=5(确认4、5已接收)。
发送端根据UNA=3知道3丢失,立即重传3,而无需重传4、5。
KCP通过牺牲少量带宽,显著提升了传输速度和实时性,特别适合高延迟、高丢包网络环境(如实时音视频、竞技类网游)。
kcp_create
**conv
:会话ID,用于标识KCP连接。user
:用户自定义数据,通常为NULL。ikcpcb *kcp = ikcp_create(conv, user);
kcp_update
功能:更新KCP状态,触发内部定时任务(如超时重传、窗口更新等)。
参数:
kcp:KCP对象指针。
current:当前时间戳(毫秒)。
示例:
ikcp_update(kcp, current);
kcp_send
功能:发送数据。
参数:
kcp:KCP对象指针。
buffer:待发送的数据缓冲区。
len:数据长度。
返回值:成功返回0,失败返回负值。
示例:
int ret = ikcp_send(kcp, buffer, len);
kcp_input
功能:接收数据并处理(如解析KCP协议头、更新ACK等)。
参数:
kcp:KCP对象指针。
data:接收到的数据缓冲区。
size:数据长度。
返回值:成功返回0,失败返回负值。
示例:
int ret = ikcp_input(kcp, data, size);
kcp_recv
功能:从KCP接收缓冲区中读取应用层数据。
参数:
kcp:KCP对象指针。
buffer:接收数据的缓冲区。
len:缓冲区长度。
返回值:实际读取的数据长度。
示例:
int len = ikcp_recv(kcp, buffer, sizeof(buffer));
初始化KCP
ikcpcb *kcp = ikcp_create(conv, user);
ikcp_wndsize(kcp, 128, 128); // 设置窗口大小
ikcp_nodelay(kcp, 1, 10, 2, 1); // 启用快速模式
发送数据
char buffer[1024];
int len = sprintf(buffer, "Hello, KCP!");
ikcp_send(kcp, buffer, len);
接收数据
char buffer[1024];
int len = ikcp_recv(kcp, buffer, sizeof(buffer));
if (len > 0) {
printf("Received: %s\n", buffer);
}
定时更新
while (1) {
sleep_ms(10);
ikcp_update(kcp, current_ms());
}
集成
```c++
#include "ikcp.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
// 回调函数:发送 KCP 数据包
int output(const char *buf, int len, ikcpcb *kcp, void user) {
int sockfd = (int)user;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
sendto(sockfd, buf, len, 0, (struct sockaddr)&addr, sizeof(addr));
return 0;
}
int main() {
// 创建 UDP 套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket");
exit(1);
}
// 初始化 KCP 对象
ikcpcb *kcp = ikcp_create(0x11223344, &sockfd);
ikcp_wndsize(kcp, 128, 128); // 设置窗口大小
ikcp_nodelay(kcp, 1, 10, 2, 1); // 启用快速模式
ikcp_setoutput(kcp, output); // 设置回调函数
// 发送数据
char send_buffer[1024];
int send_len = sprintf(send_buffer, "Hello, KCP!");
ikcp_send(kcp, send_buffer, send_len);
// 接收数据
char recv_buffer[1024];
while (1) {
// 接收网络数据
struct sockaddr_in addr;
socklen_t addr_len = sizeof(addr);
int recv_len = recvfrom(sockfd, recv_buffer, sizeof(recv_buffer), 0, (struct sockaddr*)&addr, &addr_len);
if (recv_len > 0) {
// 处理 KCP 协议包
ikcp_input(kcp, recv_buffer, recv_len);
// 读取应用数据
int len = ikcp_recv(kcp, recv_buffer, sizeof(recv_buffer));
if (len > 0) {
printf("Received: %s\n", recv_buffer);
}
}
// 定时更新 KCP 状态
ikcp_update(kcp, current_ms());
usleep(10000); // 10ms
}
// 释放 KCP 对象
ikcp_release(kcp);
close(sockfd);
return 0;
}
## 4. QUIC
### 1. QUIC实现可靠传输的核心机制
1. Packet Number严格递增
每个数据包分配唯一且严格递增的序列号(Packet Number),即使重传数据包也会使用新序列号。
- 优势:
消除TCP重传歧义问题,精确计算RTT(往返时间),优化超时重传策略。
支持乱序确认,避免因单个数据包丢失阻塞后续传输(解决队头阻塞)。
2. Frame Header设计
每个数据包包含多个Frame,通过以下字段保证数据顺序和可靠性:
Stream ID:标识不同的并发数据流(类似HTTP/2的Stream)。
Offset:标记数据在流中的偏移量(类似TCP的序列号)。
Length:数据长度。
- 作用:通过Stream ID + Offset唯一标识数据内容,即使重传数据包的Packet Number不同,也能正确重组数据。
3. 流量控制
Stream级流量控制:每个Stream独立维护滑动窗口,互不影响。
Connection级流量控制:限制所有Stream的总数据量,防止整体过载。
- 实现方式:
通过WINDOW_UPDATE帧动态调整窗口大小。
通过BLOCKED帧通知发送方流量阻塞。
4. 拥塞控制改进
默认使用TCP的Cubic算法,同时支持BBR、PCC等算法。
- 优势:
在应用层实现,无需内核支持,灵活升级算法。
可为不同应用定制拥塞策略(如视频流与文件传输使用不同算法)。
### 2. QUIC解决TCP的四大缺陷
1. 协议升级困难
TCP协议栈内置于操作系统,升级依赖内核更新;QUIC在应用层实现,通过更新浏览器或应用即可部署新特性。
2. 连接建立延迟
1-RTT握手:QUIC将TLS 1.3集成到协议中,首次连接仅需1个RTT完成密钥协商和连接建立。
0-RTT恢复:会话恢复时可直接发送数据,无需握手。
3. 队头阻塞问题
Stream独立滑动窗口:每个HTTP请求(Stream)拥有独立窗口,某个Stream丢包不影响其他Stream。
乱序确认:基于Packet Number和Offset,允许接收方乱序确认数据包。
4. 网络迁移需重建连接
连接ID标识:QUIC通过连接ID(而非IP+端口)标识连接,网络切换(如4G转WiFi)时无需重建连接,实现无缝迁移。
### 3. QUIC与HTTP/3的关系
HTTP/3基于QUIC协议,将传输层从TCP替换为QUIC,彻底解决HTTP/2的TCP队头阻塞问题。
QUIC在UDP报文头部与HTTP数据之间添加三层头部:
1. Long/Short Packet Header:管理连接ID与数据包编号。
2. QUIC Frame Header:标识Stream及数据偏移。
3. HTTP/3帧:承载实际应用数据。