go 的 net.conn 超时需每次 i/o 前调用 setreaddeadline 或 setwritedeadline,因其一次性生效;http 超时应使用 context.withtimeout 配合 timeouthandler 或显式监听 ctx.done();client.timeout 控制全周期,精细控制需拆解 transport 参数。

Go 的 net.Conn 超时设置为什么不能只调一次 SetDeadline
因为 SetDeadline 是一次性生效的:它只影响**下一次**读或写操作。用完即失效,后续 I/O 不再受控。很多人设完就以为“连接有超时了”,结果遇到卡死请求依然 hang 住。
常见错误现象:Read 卡在 TCP retransmit、对方不发 FIN、中间网络设备静默丢包,程序无响应;Write 卡在内核 socket buffer 满且对端迟迟不 ACK。
- 必须在每次
Read或Write前调用对应方法:SetReadDeadline或SetWriteDeadline -
SetDeadline(t)等价于同时设置读写,但同样只作用于下一次 I/O - 如果用
bufio.Reader包装连接,注意它的Read可能触发多次底层Read,需自己控制 deadline 设置时机
HTTP Server 中如何真正生效地限制 handler 超时
直接在 http.ResponseWriter 对应的 Conn 上设 deadline 是无效的——HTTP/1.1 keep-alive 下连接会被复用,且标准库已接管 I/O 控制权。
正确做法是用 context.WithTimeout 配合 http.TimeoutHandler,或在 handler 内部用 ctx.Done() 主动退出。
立即学习“go语言免费学习笔记(深入)”;
-
http.TimeoutHandler在 write header 前检查超时,超时后返回 503,但无法中断正在写的 response body - 若 handler 内部有阻塞调用(如数据库查询、rpc),必须把
context.Context传进去,并监听ctx.Done() - 不要依赖
time.AfterFunc或 goroutine sleep 来“模拟”超时——它和实际 I/O 无关联,容易误判
net/http.Client 的 Timeout 字段到底管什么
它只控制整个请求生命周期:从拨号开始,到响应 body 完全读完为止。不是“每个阶段分别超时”,也不是“仅连接建立超时”。
这意味着:如果服务器返回了 header 但 body 发送极慢(比如流式接口卡住),Timeout 仍会等待直到超时,而不是在收到 header 后就停止。
- 需要更精细控制?拆用
Transport的DialContext(控制拨号)、ResponseHeaderTimeout(控制 header 到达)、IdleConnTimeout(空闲连接) -
ReadTimeout和WriteTimeout已被弃用,不要用 —— 它们不作用于 TLS 握手或 HTTP/2 流控 - 使用
context.WithTimeout传给Client.Do才是最可靠的方式,它能中断任何阶段
自定义 net.Conn 实现时,deadline 必须支持 SetReadDeadline 和 SetWriteDeadline
如果你封装了底层连接(比如加了加密、代理、重连逻辑),而没实现这两个方法,上层代码(如 http.Transport)会直接 panic 或忽略超时。
典型错误:只实现了 Read/Write,但没实现 deadline 相关方法,或返回 errors.New("deadline not supported")。
- 必须同时实现
SetReadDeadline、SetWriteDeadline、SetDeadline,哪怕只是透传给底层Conn - 如果底层不支持 deadline(如某些串口、自定义协议通道),需用
time.Timer+select在Read/Write内部做超时判断 - 注意并发安全:多个 goroutine 同时调用
SetReadDeadline时,要确保最后设置的生效,避免竞态覆盖
事情说清了就结束。deadline 不是开关,是每次 I/O 前都要确认的契约;HTTP 超时不是配置项堆叠,是 context 与各环节的显式协作;自己造轮子时,漏掉一个 deadline 方法,整个超时体系就形同虚设。