io.Copy卡住因默认阻塞等EOF,网络连接可能不关也不发数据;须设net.Conn的SetReadDeadline超时,或用带超时的io.ReadWriter包装。

为什么 io.Copy 会卡住,而你没看到错误?
因为 io.Copy 默认阻塞等待 EOF,但网络连接可能既不写入数据也不关闭(比如服务端发一半就挂了、心跳超时未处理)。此时调用方看似“卡死”,实际是底层 Read 在等待更多字节。
- 务必为底层
net.Conn设置SetReadDeadline或SetDeadline,否则超时控制完全失效 - 避免直接对裸
net.Conn调用io.Copy;优先包装成带超时的io.ReadWriter - 若需细粒度控制(如解析 HTTP 流),改用
bufio.Reader+ReadSlice或ReadBytes,并手动检查err == io.EOF与err == net.ErrClosed
如何让 http.Response.Body 支持流式解析又不泄漏 goroutine?
HTTP 响应体是 io.ReadCloser,但默认不支持中断或限速。直接 io.Copy 到 os.Stdout 看似简单,一旦下游写慢(比如终端卡顿),上游 TCP 窗口会被压满,导致服务端停止发送 —— 这不是 bug,是 TCP 流控生效。
- 用
io.LimitReader(resp.Body, maxBytes)防止 OOM,尤其处理未知大小的响应 - 若需边读边解析 JSON 流,用
json.NewDecoder(body).Decode(&v),它内部按需调用Read,不会预加载整块 - 必须显式调用
resp.Body.Close(),否则底层连接无法复用(http.Transport会等它关闭才放回连接池)
context.WithTimeout 对 net.Conn 有效吗?
无效。Context 超时只影响 http.Client.Do 这类高层函数的发起阶段,不影响已建立连接上的读写。真正起作用的是连接自身的 deadline 设置。
- 正确做法:创建
net.Dialer并设置Timeout和KeepAlive,再传给http.Transport - 对已有
net.Conn,在每次Read/Write前调用SetReadDeadline/SetWriteDeadline,时间戳需每次动态计算(不能复用旧值) - 注意:UDP 场景下
SetReadDeadline有效;但unix.DgramConn等特殊类型需查文档确认支持情况
高并发下流式上传为何内存暴涨?
常见写法是 io.Copy(dst, r.Body) 处理上传,但 io.Copy 内部使用 32KB 缓冲区 —— 表面看没问题,可当 1000 个请求并发时,就是 32MB 瞬时内存占用,且缓冲区分配/回收频繁触发 GC。
立即学习“go语言免费学习笔记(深入)”;
- 改用
io.CopyBuffer(dst, src, make([]byte, 64*1024))复用大缓冲区,减少分配次数 - 对文件上传,优先用
multipart.Reader解析 boundary,跳过不需要的字段(比如只取file字段,忽略name),避免把整个 body 加载进内存 - 若后端是对象存储(如 S3),考虑用分块上传(
PutObjectPart)+ 流式签名,避免本地拼接完整 payload










