不能直接用goroutine+net.conn传多个文件,因tcp无消息边界,多goroutine并发写会导致数据粘连错序;必须用长度前缀协议(如"1024\n")配合io.copyn,并通过select+channel实现连接级多路复用。

为什么不能直接用 goroutine + net.Conn 传多个文件
因为 TCP 是字节流,没有消息边界。你开 10 个 goroutine 分别往同一个 conn 写文件,数据会粘连、错序、互相覆盖——io.Copy 不知道“哪段属于哪个文件”,它只认 EOF 或阻塞。典型现象是服务端解析 strconv.Atoi 失败、bufio.ReadString('\n') 卡死、文件内容混杂。
根本问题不是并发本身,而是协议没定义分界。多文件传输必须显式携带长度元信息,否则接收方永远无法判断“该停在哪”。
- 每个文件前必须发送 ASCII 长度 +
\n,例如"1024\n" - 接收端用
r.ReadString('\n')提取长度,再转成int64传给io.CopyN(dst, src, n) - 长度值与实际字节数必须严格一致,差 1 字节就会导致后续全部偏移错乱
- 文件名也建议同样方式发送(带换行),避免空格或 UTF-8 多字节截断
如何用 select + channel 实现连接级多路复用
Go 的 select 本身不操作 socket,它只监听 channel。真正的 I/O 多路复用由 runtime 底层完成:当 conn.Read() 或 conn.Write() 阻塞时,goroutine 被挂起,线程交还给其他 goroutine;内核就绪后唤醒对应 goroutine。你只需把读写逻辑封装进 channel 通信中,让 select 管理状态流转。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 为每个活跃连接维护一个
chan []byte(写队列)和一个chan error(错误通知) - 启动两个 goroutine:一个 loop 调
conn.Write()拉取写队列;另一个 loop 调conn.Read()推送数据到处理 channel - 主逻辑用
select同时等待写完成、读就绪、超时、取消信号——这样单 goroutine 就能管理多个连接的 I/O 状态 - 别在
select里直接调conn.Read(),那会阻塞整个 case 分支;必须外包给独立 goroutine
并发上传大文件时 multipart.Writer 必须流式构造
把整个文件读进内存再塞进 multipart.Writer,OOM 风险极高;但若直接用 os.Open() 传给 http.NewRequest,又没法添加其他表单项(如 token、chunk_id)。正确姿势是用 io.Pipe 边读边写。
- 创建
pr, pw := io.Pipe(),pr作为请求 body,pw交给写 goroutine - 写 goroutine 中初始化
multipart.Writer,先w.WriteField("token", token),再w.CreateFormFile("file", name),最后io.Copy(part, file) - 务必在
pw.Close()前完成所有字段写入——pipe 一旦关闭,pr就返回 EOF,body 提交即终止 - 文件字段必须显式调
w.WriteField("filename", name),否则服务端可能收不到原始名 - 签名或预签名 URL 必须在 pipe 启动前计算完毕,pipe 写入过程不可中断或回退
并发下载必须限流,不是靠 goroutine 数量控制
http.Client 默认不限制并发连接数,开 100 个 goroutine 发请求,极易触发 DNS 超时、连接耗尽或服务端 429。关键不是“开了多少 goroutine”,而是“底层 Transport 是否可控”。
- 设置
http.Transport{MaxIdleConns: 20, MaxIdleConnsPerHost: 20},防止空闲连接堆积 - 用带缓冲 channel 做令牌桶:
sem := make(chan struct{}, 10),每个请求前sem ,完成后 <code> - 每个请求必须带
context.WithTimeout(ctx, 30*time.Second),否则失败请求会卡住整个 goroutine 池 - 自动重定向(302)默认开启,若目标域名多变,需禁用:
CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },避免 DNS 泛洪
真正难的不是写几个 goroutine,而是让每个连接知道自己该读多少、该写什么、什么时候该停、出错了怎么退。协议边界、资源水位、错误传播链——这些细节漏掉一个,整个并发传输就从提速变成事故。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










