用 net/http 快速启动 http 服务需注意:监听地址应为 ":8080" 而非 "127.0.0.1:8080",路由区分大小写,响应前须调用 writeheader 或 write,主动连接推荐用 net.dialcontext 配合 context.withtimeout 控制全链路超时,json 应用 json.encoder/decoder 流式处理,手动建立的 net.conn 必须显式 close 防泄漏。

用 net/http 快速启动一个可调试的 HTTP 服务
不需要第三方框架,net/http 自带的 http.ListenAndServe 就能跑通基础通信。它默认使用 HTTP/1.1,监听 localhost:8080 时不会自动绑定外网地址,本地测试够用,但部署到服务器必须显式指定 ":8080"(不是 "127.0.0.1:8080"),否则外部请求连不上。
常见错误是写成 http.ListenAndServe("127.0.0.1:8080", nil),结果 curl 从另一台机器访问超时;还有人漏掉 log.Fatal 包裹,导致服务启动失败却静默退出。
- 路由注册用
http.HandleFunc,路径匹配区分大小写,"/api"和"/API"是两个路由 - 处理函数签名固定为
func(http.ResponseWriter, *http.Request),不能少参数也不能换顺序 - 响应前必须调用
w.WriteHeader(statusCode)或直接用w.Write(隐式写 200),否则客户端可能卡在 pending 状态
用 net.Dial 主动发起 TCP 连接并控制超时
net/http 封装太厚,做长连接、自定义协议或对接非 HTTP 服务(比如 Redis、MySQL 原生协议)时,得退回更底层的 net.Conn。直接调 net.Dial 风险在于:默认无超时,DNS 解析卡住或服务端不响应会导致 goroutine 永久阻塞。
正确做法是用 net.DialTimeout 或更推荐的 net.DialContext 配合 context.WithTimeout。后者能同时控制 DNS 解析、TCP 握手、TLS 协商全过程,而前者只管连接建立阶段。
-
net.Dial("tcp", "example.com:80", nil)—— 不安全,没超时 -
net.DialTimeout("tcp", "example.com:80", 5*time.Second)—— 超时仅作用于 connect 阶段 -
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second); net.DialContext(ctx, "tcp", "example.com:80")—— 全链路可控
用 json.Encoder / json.Decoder 替代 json.Marshal 直接序列化网络流
很多人习惯先 json.Marshal 得到字节切片,再 w.Write 发出去。这会把整个结构体一次性加载进内存,大对象(比如返回 10MB 日志列表)容易触发 GC 压力,还浪费中间拷贝。标准库提供流式编解码接口,直接读写 net.Conn 或 http.ResponseWriter。
注意 json.Encoder.Encode 会自动加换行符,多个消息连续发时,接收方用 json.Decoder.Decode 能自动按行解析;但如果用 io.Copy 转发原始字节,就别混用 Encoder,否则多出的换行会破坏协议边界。
- 服务端响应 JSON:用
json.NewEncoder(w).Encode(data),不用手动设Content-Type,Encoder不管 header - 客户端读 JSON:用
json.NewDecoder(resp.Body).Decode(&v),resp.Body关闭由调用方负责 - 避免
json.Unmarshal([]byte(...), &v),尤其当数据来自io.ReadCloser时,会多一次内存分配
关闭连接时必须显式调用 conn.Close(),别依赖 GC
Go 的 net.Conn 实现里,底层文件描述符不会被 GC 回收。哪怕你把 conn 变量置为 nil,只要没调 Close(),连接就一直占着系统资源,Linux 下表现为 TIME_WAIT 状态堆积,最终耗尽端口或触发 too many open files 错误。
HTTP 服务中,http.ResponseWriter 的底层 Conn 由标准库自动管理,无需手动关;但你自己用 net.Listener.Accept() 接收的连接、或用 net.Dial 建立的连接,都必须确保在业务逻辑结束时调 conn.Close()。用 defer 最稳妥,但要注意 defer 在 return 后执行,如果 handler 里有 long-running goroutine 仍在读写该 conn,提前 close 会导致 panic。
- HTTP handler 中自己
Dial的连接:defer conn.Close()放在 handler 函数开头 - 长连接服务(如 WebSocket 模拟):在读循环 break 前、或收到关闭信号后立即
conn.Close() - 检查是否已关闭:
conn.RemoteAddr() == nil不可靠,应捕获read/write: connection closed类错误来判断
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











