网络编程
2026年9月6日
Go 标准库把常见网络协议收得很齐。net 管 TCP/UDP,net/http 管 HTTP,net/rpc 是基于连接的远程调用。浏览器长连接再用 WebSocket。
TCP
TCP 面向连接、按字节流交货。面试常问它怎么「可靠」:建立连接三次握手,断开四次挥手,再加上序号、确认和重传。应用层只要把连接当双向管道用。
TCP 服务端
ln, err := net.Listen("tcp", ":9000")
if err != nil {
log.Fatal(err)
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
log.Println(err)
continue
}
go func(c net.Conn) {
defer c.Close()
io.Copy(c, c) // 原样回显
}(conn)
}
go
一个连接一个 goroutine,别在 Accept 循环里同步处理。
TCP 客户端
conn, err := net.Dial("tcp", "127.0.0.1:9000")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
fmt.Fprintln(conn, "hello")
io.Copy(os.Stdout, conn)
go
HTTP
HTTP 服务端
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "ok")
})
log.Fatal(http.ListenAndServe(":8080", nil))
go
读 JSON 用 json.NewDecoder(r.Body),写状态码用 w.WriteHeader(http.StatusCreated)。生产环境换成自己的 http.Server,设 ReadTimeout / WriteTimeout。
HTTP 客户端
resp, err := http.Get("https://example.com")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
fmt.Println(resp.Status, len(b))
go
带超时:
client := &http.Client{Timeout: 3 * time.Second}
req, _ := http.NewRequest(http.MethodGet, "https://example.com", nil)
resp, err := client.Do(req)
go
RPC
远程过程调用:客户端像调本地函数一样调对面。标准库 net/rpc 用 Go 的 gob 编码,两端都是 Go 时最省事。
RPC 服务端
type Arith struct{}
func (Arith) Add(args [2]int, reply *int) error {
*reply = args[0] + args[1]
return nil
}
rpc.Register(new(Arith))
ln, _ := net.Listen("tcp", ":1234")
for {
conn, err := ln.Accept()
if err != nil {
continue
}
go rpc.ServeConn(conn)
}
go
方法必须导出,第二个参数是指针,返回 error。
RPC 客户端
client, err := rpc.Dial("tcp", "127.0.0.1:1234")
if err != nil {
log.Fatal(err)
}
var sum int
err = client.Call("Arith.Add", [2]int{1, 2}, &sum)
fmt.Println(sum, err)
go
跨语言更常见的是 gRPC 或 JSON-RPC,思路一样:定义服务、生成桩、走 HTTP/2 或 TCP。
WebSocket
WebSocket 先用 HTTP 握手,再升级成长期双向的 socket,浏览器和服务器都能主动推。标准库没有完整封装,常用 coder/websocket 或 gorilla。
WebSocket 服务端
一个连接读消息,也可以往多个客户端广播(用一把锁保护连接集合):
var (
mu sync.Mutex
conns = map[*websocket.Conn]struct{}{}
)
func ws(w http.ResponseWriter, r *http.Request) {
c, err := websocket.Accept(w, r, nil)
if err != nil {
return
}
mu.Lock()
conns[c] = struct{}{}
mu.Unlock()
defer func() {
mu.Lock()
delete(conns, c)
mu.Unlock()
c.Close(websocket.StatusNormalClosure, "")
}()
for {
_, _, err := c.Read(r.Context())
if err != nil {
return
}
}
}
go
WebSocket 客户端
浏览器用 new WebSocket("ws://localhost:8080/ws")。Go 里同样用库的 Dial。
Postman 作为客户端
Postman 的 WebSocket 请求填 ws://127.0.0.1:8080/ws,连上后可以直接发文本帧,适合先看服务端是否握手成功,再写页面。