Go中net.Conn.Write返回错误类型及正确写入循环实现问题
Go
net.Conn.Write() 常见问题解答 错误判断规则
net.Conn 严格遵守Go标准库的io.Writer接口约定:
If Write returns n < len(p), it must return a non-nil error.
也就是说:只要返回的写入字节数n小于待写入切片的长度,必然同时返回非空错误;反之如果返回错误为nil,则n一定等于len(p)。所以你不需要单独判断n < len(p),所有写入异常都可以通过判断返回的error捕获。
存在会导致n < len(p)的不可恢复错误,常见的包括连接被重置(ECONNRESET)、对端主动关闭连接(EPIPE)等,这类错误出现后无需重试,直接终止写入即可。
错误类型区分方法
使用Go标准库的errors.As、errors.Is做错误判定即可:
- 先判断是否为网络错误:
var netErr net.Error; if errors.As(err, &netErr)- 如果
netErr.Timeout()返回true:属于超时错误,是可重试的临时错误 - 如果
netErr.Temporary()返回true:属于其他临时网络异常,可重试 - 其余网络错误均为不可恢复错误,直接终止流程即可
- 如果
- 非网络错误一般都属于不可恢复错误,直接终止流程即可
大体积数据的正确写入循环写法
有两种常用实现方案:
- 直接调用标准库
io.Copy,它已经内置了符合约定的写入重试逻辑,无需自己实现循环:
// 示例:将本地文件内容写入net.Conn written, err := io.Copy(conn, localFile)
- 如需自定义业务逻辑,可以自己实现写入循环:
import ( "errors" "net" "time" ) func writeAll(conn net.Conn, buf []byte) error { offset := 0 for offset < len(buf) { n, err := conn.Write(buf[offset:]) offset += n if err != nil { // 临时错误重试逻辑,可根据业务需求调整 var netErr net.Error if errors.As(err, &netErr) && netErr.Temporary() { // 加短暂休眠避免重试风暴 time.Sleep(10 * time.Millisecond) continue } return err } } return nil }
缓冲区满的行为说明
默认情况下net.Conn使用阻塞IO模式:如果内核发送缓冲区已满,Write调用会直接阻塞,直到有足够空间写入所有数据、或者触发超时/网络错误,不会出现“写了部分数据但没返回错误”的情况,符合io.Writer的约定,不会导致边界检查多余。
只有你主动通过SetWriteDeadline设置了写超时的情况下,才会出现写入中途超时、返回部分写入字节和超时错误的情况,按上面的循环逻辑处理即可。
内容的提问来源于stack exchange,提问作者Daniel
相关产品推荐
相关产品推荐

