网络编程

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,连上后可以直接发文本帧,适合先看服务端是否握手成功,再写页面。


参考文档