缓冲可减少系统调用次数,但需依场景选择大小;bufio.Reader 的 Peek() 用于协议类型判断,配合 Discard() 或 Read() 推进读位置,使用时须处理 io.ErrShortBuffer。

缓冲能减少系统调用次数,但不是越多越好
Go 的 net.Conn 默认不带缓冲,每次 Read() 或 Write() 都可能触发一次系统调用。频繁的小包读写会显著拖慢性能,尤其在高并发短连接或流式协议(如 HTTP/1.1 分块传输)中。加缓冲的核心目的就是把多次小操作“攒”成一次系统调用。
但缓冲区大小要匹配实际场景:bufio.NewReader(conn) 默认用 4096 字节,对多数文本协议够用;若处理大文件上传,可设为 64 * 1024;而对延迟敏感的实时通信(如游戏心跳),过大的缓冲反而增加首字节延迟。
bufio.Reader 的 Peek() 和 Discard() 是协议解析关键
很多自定义协议依赖“窥探前几个字节判断类型”,比如 MQTT 的固定头、Redis 的 RESP 类型标识。直接 Read() 会移动读位置,导致后续解析错位;Peek(n) 则只查看不消费,配合 Discard(n) 或 Read() 才真正推进。
常见错误是忽略 Peek() 可能返回 io.ErrShortBuffer —— 缓冲区没填满就尝试窥探,需先确保有足够数据:
立即学习“go语言免费学习笔记(深入)”;
buf := bufio.NewReader(conn)
n, _ := buf.Peek(2) // 检查前2字节
if len(n) < 2 {
// 需要等待更多数据,不能直接解析
return
}Write 侧缓冲要小心 flush 时机,避免粘包或截断
bufio.NewWriter(conn) 把写入暂存在内存,直到缓冲区满、显式调用 Flush() 或 writer 关闭。这在批量响应时提升明显,但交互式协议(如 Telnet、REPL)必须手动 Flush(),否则客户端永远收不到回显。
容易踩的坑:
- 忘记
Flush()导致响应卡住 - 在
http.ResponseWriter上误用bufio.Writer—— 它已由 net/http 内部管理,额外包装会破坏 header 写入逻辑 - 并发写同一
bufio.Writer而未加锁,引发 panic 或数据错乱
缓冲与 context 超时、连接关闭的协同问题
缓冲层会掩盖底层连接状态。例如 bufio.Reader.Read() 在缓冲区有数据时直接返回,哪怕连接已被对端关闭;只有缓冲区空了才会触发底层 Read() 并发现 io.EOF。这会让超时判断失准。
正确做法是:用 conn.SetReadDeadline() 控制底层 socket,而非依赖缓冲读的耗时。同时注意 bufio.Reader 的 Read() 不响应 context.Context,需自行封装或改用 io.ReadFull() + context.WithTimeout() 组合。
最易被忽略的一点:缓冲区残留数据在连接复用(如 HTTP/1.1 keep-alive)中可能污染下一次请求,务必在连接归还池前清空或重建 bufio.Reader。











