Go中读取HTTP响应体应避免io.ReadAll,推荐用bufio.Scanner按行处理或io.Copy流式传输;需注意Scanner默认单行上限64KB,超长需调用scanner.Buffer调整缓冲区。

Go 中用 http.Response.Body 直接读取流数据最常用
HTTP 响应体本身就是 io.ReadCloser,天然支持流式读取,无需额外缓冲或转义。只要不调用 resp.Body.Close() 过早,就能持续读;但也不能一直不关,否则连接不会释放。
常见错误是:直接 io.ReadAll(resp.Body) 一次性加载全部内容——这会吃光内存,尤其面对大文件或长连接流(如 SSE、视频流)。
- 正确做法是用
bufio.NewReader或直接io.Copy到目标 writer - 若需按行处理(如日志流),用
scanner := bufio.NewScanner(resp.Body),并设置scanner.Split(bufio.ScanLines) - 注意:默认
Scanner单行上限 64KB,超长行会报bufio.Scanner: token too long,需提前调用scanner.Buffer(make([]byte, 4096), 1
用 io.Pipe 实现可控的流式生产与消费
当你要把一个异步生成的数据源(比如实时采集的传感器数据)以流方式暴露给 HTTP handler 或其他 reader 时,io.Pipe 是轻量且明确的选择。它返回一对 *io.PipeReader 和 *io.PipeWriter,写端写入、读端读出,阻塞同步。
关键点在于:写端必须在某个时刻调用 writer.Close(),否则读端会永远等待 EOF;如果写过程 panic,记得用 defer writer.Close() 或 recover 包裹。
立即学习“go语言免费学习笔记(深入)”;
- 不要在 goroutine 里无保护地多次写入
io.PipeWriter—— 它不是并发安全的 - 若需多 goroutine 写,先用
chan []byte聚合,再由单个 goroutine 顺序写入 pipe -
http.ServeContent不适用 pipe,因为它依赖Seeker,而io.PipeReader不实现该接口
处理 Server-Sent Events(SSE)时必须设对 Header 和 flush
SSE 依赖客户端持续接收带 data: 前缀的文本块,服务端必须禁用响应缓冲、保持连接打开,并手动 flush 每次写入。Go 的 http.ResponseWriter 默认不自动 flush,得靠 http.Flusher 接口。
典型失败现象:浏览器收不到任何 event,或只收到第一个后就断连——多半是没做 flush 或写了 Content-Length 导致连接被关闭。
- 必须设置:
w.Header().Set("Content-Type", "text/event-stream")和w.Header().Set("Cache-Control", "no-cache") - 必须检查
f, ok := w.(http.Flusher),然后每次fmt.Fprintln(w, "data: ..."); f.Flush() - 避免在 handler 中 return 前忘记 close 或 panic,否则连接残留,goroutine 泄漏
net.Conn 层面读取原始 TCP 流要注意粘包和边界
如果你直连 net.Conn(比如对接自定义协议设备),conn.Read() 不保证一次读完一整条消息。TCP 是字节流,没有天然“包”概念,所谓“一条消息”得靠上层协议约定边界(长度前缀、分隔符、固定长度等)。
常见误操作:把 conn.Read(buf) 返回的 n 当作完整消息长度,直接解析 buf[:n] —— 实际可能只读到半条,也可能读到两条拼在一起。
- 简单分隔符场景可用
bufio.NewReader(conn).ReadString('\n'),但注意换行符必须统一(\n 还是 \r\n) - 长度前缀(如 4 字节 big-endian 表示后续 payload 长度)需先读够头,再读 payload,两次 Read 都要检查 err 和 n
- 永远用
for { ... if err != nil { break } }循环读,而不是只读一次










