UDP文件流传输必须自行实现ACK+序列号+重传机制,因WriteTo成功仅表示数据入内核队列,不保证送达;TCP场景下加应用层重传会引发重复和乱序。

WriteTo 成功不代表文件块送达,UDP 场景下必须自己管重传;TCP 场景下加应用层重传等于制造重复和错序。
UDP 文件流传输必须自己实现 ACK + 序列号 + 重传
UDP 没有连接、没有确认、没有滑动窗口,WriteToUDP 返回 nil 只表示数据进了内核发送队列,对方收没收到完全无感知。文件分块传输时,丢一个包,整个文件校验就失败。
- 每个文件块头部预留 4 字节
seq(binary.BigEndian.PutUint32编码),避免结构体内存对齐陷阱 - 接收端收到后立即回一个纯
ack_seq的极简 ACK 包(不带 payload),降低延迟和开销 - 发送端为每个
seq启动独立*time.Timer,超时触发重传;收到对应ack_seq后立刻调timer.Stop() - 绝对不要复用同一个
time.Timer实例管理多个seq——Reset()在已触发状态下返回false,漏判会导致重传失控 - 单包限制 ≤1200 字节:避开 MTU 分片导致的静默丢包(云环境 VPC/K8s CNI 常见)
TCP 文件流传输别加应用层重传
TCP 栈本身已处理丢包重传、顺序重组和校验,conn.Write() 成功只代表数据拷贝进内核缓冲区,不是“对方收到了”。盲目重发会直接导致:
- 服务端重复处理同一文件块(如多次写入磁盘、重复解压)
- 客户端收到乱序响应(比如第 3 块响应先到,第 1 块后到),破坏流式拼接逻辑
- HTTP/2 或 gRPC stream 中旧响应突然抵达,引发状态错乱
真正该做的是:SetReadDeadline 配合心跳探测通路是否可用,再靠幂等性(如文件块 hash 校验、服务端去重 ID)兜底。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
文件流场景下超时时间不能写死
固定设 100ms 或 500ms 的重传间隔在真实网络中基本无效。弱网下要么卡死,要么加剧拥塞。
- 初始 RTO 建议设为 200ms,RTTvar 初始 50ms,RTO = rtt + 4 × rttvar
- 每轮重试用指数退避:
time.Duration(base * float64(1,上限封顶到 1s - 读取文件块时,
io.ReadFull比裸conn.Read更安全——它自动重试直到填满或返回真实错误,防io.ErrUnexpectedEOF - 别对每个
WriteToUDP都设SetWriteDeadline:它控制的是拷贝进内核队列的耗时,不是网络往返,设太短(如 5ms)容易误判失败
并发读写 inflight 状态最容易 panic
Go 的 *net.UDPConn 是线程安全的,但你自定义的 inflight map[uint32]*pendingPacket、recvMap map[uint32][]byte 不是。
- 接收协程更新
rcv_nxt时,发送协程同时清理inflight:用独立sync.Mutex锁inflight,别锁整个结构体 - 多个重传定时器 goroutine 同时访问
inflight:每次timer触发前先mu.Lock(),检查该seq是否已被 ACK,确认未确认后再重发,然后mu.Unlock() - 接收端缓存乱序包用
map[uint32][]byte,但没加锁就直接delete(recvMap, seq):读多写少场景下,用sync.RWMutex保护该 map,交付连续段后批量清理
真正难的不是写重传逻辑,而是决定什么时候不该重传——比如非幂等文件块、下游未承诺至少一次语义、或当前连接已由心跳判定为不可用时,重传只会让问题更糟。

















