go net.conn无自动缓冲扩容,需用bufio.reader/writer手动处理;readslice/readbytes遇超长数据返回errtoolong而非扩容;write超限会报错或阻塞,须检查返回值并分块处理。

Go net.Conn 读写缓冲区不会自动扩容,得自己包一层
Go 标准库的 net.Conn 接口本身不管理缓冲区,Read 和 Write 都是直接操作底层 socket,传入的 []byte 切片多大,就尝试读/写多少。所谓“缓冲自动扩容”,其实是应用层要自己在 bufio.Reader 或 bufio.Writer 之上再封装逻辑——标准库的 bufio.Reader 虽然有 Read,但它内部的 buf 是固定大小的,遇到超长行或突发大数据时会直接返回 bufio.ErrTooLong,不是扩容,是报错。
bufio.Reader 的 ReadSlice 和 ReadBytes 容易触发 ErrTooLong
这两个方法默认受限于 bufio.Reader 初始化时指定的缓冲大小(比如 bufio.NewReaderSize(conn, 4096))。一旦遇到超过该长度的单次数据(如一个超长 HTTP header、未分帧的 JSON blob),就会返回 bufio.ErrTooLong,而不是帮你 realloc。
- 解决办法不是调大初始 size(治标),而是改用
Read+ 手动 grow:先读固定 buffer,检查是否读满且未遇分隔符,再append到目标切片,循环直到满足条件 - 若必须用
ReadBytes类接口,可临时替换为自定义实现,例如:func readUntil(r *bufio.Reader, delim byte) ([]byte, error) { var buf []byte for { b, err := r.ReadByte() if err != nil { return nil, err } buf = append(buf, b) if b == delim { return buf, nil } } } - 注意:频繁
append小块内存会导致多次底层数组复制,对高吞吐场景建议预估上限 +make([]byte, 0, estimated)
Write 侧扩容本质是分块 flush,不是“动态 buffer”
bufio.Writer 的 Write 方法会先把数据拷贝进内部 buf,等 Flush 或 buf 满了才真正发出去。它不会因为一次 Write 超过 buf 容量就自动扩容 buffer 并发——而是直接返回 bufio.ErrInvalidArgument(如果 len(p) > cap(buf))或者阻塞等待 flush 后腾出空间(取决于是否设置了 SetNoDelay 等)。
- 安全写法:总是检查
Write返回的n, err,并手动处理未写完部分:n, err := writer.Write(data) if err != nil { return err } if n < len(data) { _, err = writer.Write(data[n:]) return err } - 更稳妥的是用
io.Copy或io.WriteString,它们内部已做分块;但注意io.Copy对小数据不友好,可能触发多次系统调用 - 不要依赖
bufio.Writer.Size()判断能否容纳下一次写入——它只反映当前已缓存字节数,不是剩余容量
别在 conn 层混用 raw Read 和 bufio —— 缓冲区错位会丢数据
这是最隐蔽也最容易踩的坑:同一个 net.Conn,如果一部分逻辑用 conn.Read(),另一部分又包了 bufio.NewReader(conn),那么 bufio 内部预读的数据会从 conn 中“偷走”,导致原始 Read 调用再也读不到那部分字节。
立即学习“go语言免费学习笔记(深入)”;
- 所有读操作必须统一走同一层抽象:要么全用 raw
conn.Read,要么全走bufio.Reader,不能交叉 - 写同理:不能一边用
conn.Write,一边又用bufio.Writer写同一连接 - 调试时如果发现“少读几个字节”或“粘包但没分隔符”,第一反应该查是不是缓冲层混用了









