网络编程
包总览
| 包 | 核心类型 | 用途 |
|---|
net | Conn, Listener, Dial, Listen | TCP/UDP/Unix Socket |
net/url | URL, Parse, Query | URL 解析 |
net/http | Client, Server, Handler | HTTP(详见 06-HTTP服务) |
net/rpc | Server, Client | Go RPC |
net/smtp | SendMail | 邮件发送 |
net/mail | Address, ParseAddress | 邮件地址解析 |
net/textproto | Conn, Reader, Writer | 文本协议(SMTP/HTTP/NNTP) |
net 包
TCP 服务端
// TCP Server
func main() {
listener, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
defer listener.Close()
log.Println("server listening on :8080")
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("accept error: %v", err)
continue
}
go handleConn(conn) // 每个连接一个 goroutine
}
}
func handleConn(conn net.Conn) {
defer conn.Close()
addr := conn.RemoteAddr()
log.Printf("connection from %s", addr)
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if err != nil {
if err != io.EOF {
log.Printf("read error: %v", err)
}
return
}
// 回显
conn.Write(buf[:n])
}
}
TCP 客户端
// TCP Client
func main() {
conn, err := net.Dial("tcp", "localhost:8080")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// 写入
conn.Write([]byte("Hello, Server!\n"))
// 读取响应
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
log.Fatal(err)
}
fmt.Printf("response: %s", buf[:n])
}
带超时的 Dial
// Dialer 带超时控制
d := net.Dialer{
Timeout: 5 * time.Second, // 连接超时
KeepAlive: 30 * time.Second, // keep-alive 间隔
}
conn, err := d.DialContext(ctx, "tcp", "example.com:443")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// 设置读写超时
conn.SetDeadline(time.Now().Add(10 * time.Second))
conn.SetReadDeadline(time.Now().Add(5 * time.Second))
conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
UDP
// UDP Server
func main() {
addr, _ := net.ResolveUDPAddr("udp", ":9090")
conn, err := net.ListenUDP("udp", addr)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
buf := make([]byte, 1024)
for {
n, remoteAddr, err := conn.ReadFromUDP(buf)
if err != nil {
log.Printf("read error: %v", err)
continue
}
go handleUDP(conn, remoteAddr, buf[:n])
}
}
// UDP Client
conn, err := net.DialUDP("udp", nil, &net.UDPAddr{
IP: net.IP{127, 0, 0, 1},
Port: 9090,
})
conn.Write([]byte("Hello UDP"))
buf := make([]byte, 1024)
n, _ := conn.Read(buf)
Unix Domain Socket
// Server
listener, err := net.Listen("unix", "/tmp/myapp.sock")
defer os.Remove("/tmp/myapp.sock") // 清理 socket 文件
defer listener.Close()
// Client
conn, err := net.Dial("unix", "/tmp/myapp.sock")
网络地址解析
// DNS 解析
ips, err := net.LookupIP("example.com")
for _, ip := range ips {
fmt.Println(ip)
}
// 反向 DNS
names, err := net.LookupAddr("8.8.8.8")
// ["dns.google."]
// CNAME 查询
cname, err := net.LookupCNAME("www.example.com")
// "example.com."
// MX 记录
mxs, err := net.LookupMX("example.com")
// 获取本机 IP
addrs, _ := net.InterfaceAddrs()
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
fmt.Println("IPv4:", ipnet.IP)
}
}
}
网络类型速查
| 网络类型 | Listen/Dial 参数 | 说明 |
|---|
| TCP | "tcp" | IPv4 + IPv6 |
| TCP4 | "tcp4" | 仅 IPv4 |
| TCP6 | "tcp6" | 仅 IPv6 |
| UDP | "udp" | IPv4 + IPv6 |
| Unix | "unix" | Unix Domain Socket |
| UnixPacket | "unixpacket" | 面向连接的 Unix Socket |
地址格式
// TCP 地址
"localhost:8080"
"127.0.0.1:8080"
"[::1]:8080" // IPv6
":8080" // 监听所有接口
"0.0.0.0:8080" // 监听所有 IPv4 接口
// Unix Socket 地址
"/tmp/myapp.sock"
net/url 包
URL 解析
u, err := url.Parse("https://user:pass@example.com:8443/path/to/resource?key=value&key2=value2#fragment")
fmt.Println(u.Scheme) // "https"
fmt.Println(u.Host) // "example.com:8443"
fmt.Println(u.Hostname()) // "example.com"
fmt.Println(u.Port()) // "8443"
fmt.Println(u.Path) // "/path/to/resource"
fmt.Println(u.RawQuery) // "key=value&key2=value2"
fmt.Println(u.Fragment) // "fragment"
fmt.Println(u.User) // user:pass
fmt.Println(u.User.Username()) // "user"
pass, _ := u.User.Password() // "pass"
Query 参数处理
// 解析 query string
values, err := url.ParseQuery("name=小徐&age=28&tags=a&tags=b")
values.Get("name") // "小徐"(第一个值)
values.Get("age") // "28"
values["tags"] // ["a", "b"](获取所有值)
values.Has("name") // true(Go 1.17+)
// 构建 query string
params := url.Values{}
params.Set("name", "小徐")
params.Set("age", "28")
params.Add("tags", "go")
params.Add("tags", "k8s")
params.Encode() // "age=28&name=%E5%B0%8F%E5%BE%90&tags=go&tags=k8s"
// URL 编码/解码
url.QueryEscape("hello world&foo=bar") // "hello+world%26foo%3Dbar"
url.QueryUnescape("hello+world") // "hello world"
// Path 编码(不对 / 编码)
url.PathEscape("path/to/file") // "path%2Fto%2Ffile" (不,/ 不编码)
// 实际上 PathEscape 会编码 /
// 正确:url.PathEscape("a b") → "a%20b"
URL 构建
// 安全拼接 URL
base, _ := url.Parse("https://api.example.com")
base.Path = "/v1/users"
base.RawQuery = url.Values{
"page": {"1"},
"limit": {"20"},
}.Encode()
fullURL := base.String()
// "https://api.example.com/v1/users?limit=20&page=1"
// 解析相对 URL
base, _ := url.Parse("https://example.com/a/b/c")
rel, _ := url.Parse("../d")
abs := base.ResolveReference(rel)
// "https://example.com/a/d"
自定义协议实现
长度前缀协议
// 消息格式:[4字节长度][消息体]
func WriteMessage(conn net.Conn, msg []byte) error {
header := make([]byte, 4)
binary.BigEndian.PutUint32(header, uint32(len(msg)))
if _, err := conn.Write(header); err != nil {
return err
}
if _, err := conn.Write(msg); err != nil {
return err
}
return nil
}
func ReadMessage(conn net.Conn) ([]byte, error) {
header := make([]byte, 4)
if _, err := io.ReadFull(conn, header); err != nil {
return nil, err
}
length := binary.BigEndian.Uint32(header)
// 限制最大消息大小(防止 OOM)
if length > 10*1024*1024 { // 10MB
return nil, fmt.Errorf("message too large: %d", length)
}
msg := make([]byte, length)
if _, err := io.ReadFull(conn, msg); err != nil {
return nil, err
}
return msg, nil
}
连接复用与心跳
type Conn struct {
net.Conn
lastActivity time.Time
timeout time.Duration
}
func (c *Conn) Read(b []byte) (int, error) {
n, err := c.Conn.Read(b)
c.lastActivity = time.Now()
return n, err
}
func (c *Conn) Write(b []byte) (int, error) {
n, err := c.Conn.Write(b)
c.lastActivity = time.Now()
return n, err
}
// 心跳检测
func (c *Conn) StartHeartbeat(interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
if time.Since(c.lastActivity) > c.timeout {
c.Close()
return
}
// 发送心跳包
c.Write([]byte{0x00}) // 心跳包
}
}
net/rpc(Go 原生 RPC)
// 服务端
type Arith struct{}
func (a *Arith) Multiply(args *Args, reply *int) error {
*reply = args.A * args.B
return nil
}
func main() {
arith := new(Arith)
rpc.Register(arith)
rpc.HandleHTTP()
listener, _ := net.Listen("tcp", ":1234")
go http.Serve(listener, nil)
}
// 客户端
client, _ := rpc.DialHTTP("tcp", "localhost:1234")
args := &Args{A: 7, B: 8}
var reply int
client.Call("Arith.Multiply", args, &reply)
// reply = 56
// 异步调用
call := client.Go("Arith.Multiply", args, &reply, nil)
replyCall := <-call.Done // 等待完成
注意:net/rpc 官方已不再推荐使用,生产环境建议用 gRPC。
网络编程检查清单
| 检查项 | 说明 |
|---|
| ✅ 设置超时 | 连接超时、读写超时、空闲超时 |
| ✅ 限制消息大小 | 防止恶意大消息导致 OOM |
| ✅ 处理半包/粘包 | 使用长度前缀或分隔符协议 |
| ✅ 优雅关闭 | 关闭前 flush 缓冲区 |
| ✅ 连接复用 | 避免频繁创建/销毁连接 |
| ✅ 错误处理 | 区分网络错误和业务错误 |
| ✅ 并发控制 | 限制最大连接数 |
| ✅ 心跳保活 | 检测死连接 |