文章

网络编程

网络编程

包总览

核心类型用途
netConn, Listener, Dial, ListenTCP/UDP/Unix Socket
net/urlURL, Parse, QueryURL 解析
net/httpClient, Server, HandlerHTTP(详见 06-HTTP服务
net/rpcServer, ClientGo RPC
net/smtpSendMail邮件发送
net/mailAddress, ParseAddress邮件地址解析
net/textprotoConn, 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 缓冲区
✅ 连接复用避免频繁创建/销毁连接
✅ 错误处理区分网络错误和业务错误
✅ 并发控制限制最大连接数
✅ 心跳保活检测死连接