Skip to content

7.6 网络编程高级 ​

高性能网络服务器设计 ​

并发模型 ​

go
package main

import (
    "io"
    "log"
    "net"
    "sync"
)

// 模型 1: 每连接一个 goroutine
func serveOneConnPerGoroutine(ln net.Listener) {
    for {
        conn, err := ln.Accept()
        if err != nil {
            log.Println(err)
            continue
        }
        go handleConn(conn)
    }
}

func handleConn(conn net.Conn) {
    defer conn.Close()
    io.Copy(conn, conn)  // echo
}

// 模型 2: Goroutine 池
type Server struct {
    pool chan struct{}  // 信号量控制并发数
    wg   sync.WaitGroup
}

func NewServer(maxConn int) *Server {
    return &Server{
        pool: make(chan struct{}, maxConn),
    }
}

func (s *Server) Serve(ln net.Listener) {
    for {
        conn, err := ln.Accept()
        if err != nil {
            log.Println(err)
            continue
        }

        s.pool <- struct{}{}  // 获取信号量
        s.wg.Add(1)

        go func() {
            defer s.wg.Done()
            defer func() { <-s.pool }()  // 释放信号量
            defer conn.Close()

            handleConn(conn)
        }()
    }
}

func (s *Server) Stop() {
    s.wg.Wait()
}

// 模型 3: Worker 池
type WorkerPool struct {
    workers   int
    jobQueue  chan net.Conn
    wg        sync.WaitGroup
}

func NewWorkerPool(workers int) *WorkerPool {
    return &WorkerPool{
        workers:  workers,
        jobQueue: make(chan net.Conn, 1024),
    }
}

func (wp *WorkerPool) Start() {
    for i := 0; i < wp.workers; i++ {
        wp.wg.Add(1)
        go func(id int) {
            defer wp.wg.Done()
            for conn := range wp.jobQueue {
                handleConn(conn)
                conn.Close()
            }
        }(i)
    }
}

func (wp *WorkerPool) AddJob(conn net.Conn) {
    wp.jobQueue <- conn
}

func (wp *WorkerPool) Stop() {
    close(wp.jobQueue)
    wp.wg.Wait()
}

epoll 与 kqueue ​

Linux epoll 示例 (使用 syscall) ​

go
package main

import (
    "syscall"
    "unsafe"
)

// epoll 封装
type Epoll struct {
    fd          int
    connections map[int]net.Conn
}

func NewEpoll() (*Epoll, error) {
    fd, err := syscall.EpollCreate1(0)
    if err != nil {
        return nil, err
    }
    return &Epoll{
        fd:          fd,
        connections: make(map[int]net.Conn),
    }, nil
}

func (e *Epoll) Add(conn net.Conn) error {
    fd := socketFD(conn)
    event := syscall.EpollEvent{
        Events: syscall.EPOLLIN | syscall.EPOLLOUT | syscall.EPOLLET,
        Fd:     int32(fd),
    }
    return syscall.EpollCtl(e.fd, syscall.EPOLL_CTL_ADD, fd, &event)
}

func (e *Epoll) Remove(conn net.Conn) error {
    fd := socketFD(conn)
    return syscall.EpollCtl(e.fd, syscall.EPOLL_CTL_DEL, fd, nil)
}

func (e *Epoll) Wait(events []syscall.EpollEvent, msec int) (int, error) {
    return syscall.EpollWait(e.fd, events, msec)
}

func (e *Epoll) Close() error {
    return syscall.Close(e.fd)
}

// 获取 socket fd (需要 unsafe 转换)
func socketFD(conn net.Conn) int {
    // 实际使用需要反射或类型断言获取底层 fd
    return 0
}

// 使用示例
func serveWithEpoll() {
    ln, _ := net.Listen("tcp", ":8080")
    epoll, _ := NewEpoll()
    defer epoll.Close()

    events := make([]syscall.EpollEvent, 1024)

    for {
        conn, _ := ln.Accept()
        epoll.Add(conn)

        n, _ := epoll.Wait(events, 1000)
        for i := 0; i < n; i++ {
            fd := int(events[i].Fd)
            // 处理事件...
        }
    }
}

使用 evio/evnet 库 ​

go
package main

import (
    "log"

    "github.com/tidwall/evio"
)

// evio 高性能事件循环
func main() {
    var events evio.Events

    // 连接建立
    events.Opened = func(c evio.Conn) (out []byte, action evio.Action) {
        log.Printf("连接:%v", c.RemoteAddr())
        return nil, evio.None
    }

    // 连接关闭
    events.Closed = func(c evio.Conn, err error) (action evio.Action) {
        log.Printf("断开:%v", c.RemoteAddr())
        return evio.None
    }

    // 接收数据
    events.Data = func(c evio.Conn, in []byte) (out []byte, action evio.Action) {
        // echo 服务器
        out = in
        return
    }

    log.Fatal(events.Serve(":8080"))
}

// 配置选项
func serveWithOpts() {
    var events evio.Events

    events.Serve(evio.ServeOptions{
        Network:     "tcp",
        Addr:        ":8080",
        Multicore:   true,     // 多核
        ReusePort:   true,     // SO_REUSEPORT
        KeepAlive:   60,       // TCP keepalive
        NumLoops:    -1,       // 自动检测 CPU 核心
        LoadBalance: evio.LeastConnections,
    })
}

零拷贝技术 ​

sendfile 系统调用 ​

go
package main

import (
    "io"
    "net/http"
    "os"
)

// 传统方式 (多次拷贝)
func serveFileTraditional(w http.ResponseWriter, r *http.Request) {
    file, _ := os.Open("large.mp4")
    defer file.Close()
    io.Copy(w, file)  // 内核->用户->内核->网络
}

// 零拷贝方式
func serveFileZeroCopy(w http.ResponseWriter, r *http.Request) {
    file, _ := os.Open("large.mp4")
    defer file.Close()

    // 使用 http.ServeContent 内部使用 sendfile
    stat, _ := file.Stat()
    http.ServeContent(w, r, "large.mp4", stat.ModTime(), file)
}

// 直接使用 sendfile (Linux)
func sendfile(dst io.Writer, src *os.File) (int64, error) {
    // net/http 内部会检测并使用 sendfile
    // 也可以使用 syscall.Sendfile
    return io.Copy(dst, src)
}

splice 系统调用 ​

go
package main

import (
    "syscall"
)

// splice 在内核空间移动数据
func spliceData(src, dst int) error {
    const maxSplice = 1 << 31

    for {
        n, err := syscall.Splice(src, nil, dst, nil, maxSplice,
            syscall.SPLICE_F_MOVE|syscall.SPLICE_F_NONBLOCK)
        if n == 0 {
            break
        }
        if err != nil {
            return err
        }
    }
    return nil
}

sync.Pool 对象复用 ​

go
package main

import (
    "net"
    "sync"
)

// 缓冲区池
var bufferPool = sync.Pool{
    New: func() interface{} {
        return make([]byte, 4096)
    },
}

func handleConnWithPool(conn net.Conn) {
    defer conn.Close()

    buf := bufferPool.Get().([]byte)
    defer bufferPool.Put(buf)

    for {
        n, err := conn.Read(buf)
        if err != nil {
            break
        }
        conn.Write(buf[:n])
    }
}

// 大对象池
var largeBufferPool = sync.Pool{
    New: func() interface{} {
        return make([]byte, 65536)
    },
}

// 连接池
type ConnPool struct {
    pool sync.Pool
    new  func() net.Conn
}

func NewConnPool(newConn func() net.Conn) *ConnPool {
    return &ConnPool{
        new: newConn,
        pool: sync.Pool{
            New: func() interface{} {
                return newConn()
            },
        },
    }
}

func (p *ConnPool) Get() net.Conn {
    return p.pool.Get().(net.Conn)
}

func (p *ConnPool) Put(conn net.Conn) {
    // 重置连接状态
    // conn.SetDeadline(time.Time{})
    p.pool.Put(conn)
}

连接池设计 ​

通用连接池 ​

go
package pool

import (
    "errors"
    "net"
    "sync"
    "time"
)

type Conn interface {
    Close() error
    IsClosed() bool
}

type ConnFactory func() (Conn, error)

type Config struct {
    MaxOpen     int           // 最大连接数
    MaxIdle     int           // 最大空闲连接
    MaxLifetime time.Duration // 连接最大生命周期
    IdleTimeout time.Duration // 空闲超时
}

type ConnPool struct {
    factory    ConnFactory
    config     Config
    mu         sync.Mutex
    opens      int
    idle       []Conn
    closed     bool
    openerCh   chan struct{}
    cleanerCh  chan struct{}
}

func NewConnPool(factory ConnFactory, config Config) (*ConnPool, error) {
    if config.MaxOpen <= 0 {
        return nil, errors.New("MaxOpen must be positive")
    }
    if config.MaxIdle > config.MaxOpen {
        config.MaxIdle = config.MaxOpen
    }

    p := &ConnPool{
        factory:   factory,
        config:    config,
        idle:      make([]Conn, 0, config.MaxIdle),
        openerCh:  make(chan struct{}, config.MaxOpen),
        cleanerCh: make(chan struct{}),
    }

    // 启动连接清理协程
    go p.connectionCleaner()

    return p, nil
}

func (p *ConnPool) Get() (Conn, error) {
    p.mu.Lock()

    if p.closed {
        p.mu.Unlock()
        return nil, errors.New("pool is closed")
    }

    // 尝试从空闲列表获取
    for len(p.idle) > 0 {
        conn := p.idle[len(p.idle)-1]
        p.idle = p.idle[:len(p.idle)-1]

        if conn.IsClosed() {
            p.opens--
            continue
        }

        p.mu.Unlock()
        return conn, nil
    }

    // 检查是否可以创建新连接
    if p.opens >= p.config.MaxOpen {
        p.mu.Unlock()
        // 等待连接释放
        <-p.openerCh
        return p.Get()
    }

    p.opens++
    p.mu.Unlock()

    // 创建新连接
    return p.factory()
}

func (p *ConnPool) Put(conn Conn) {
    p.mu.Lock()

    if p.closed {
        p.mu.Unlock()
        conn.Close()
        return
    }

    // 放入空闲列表
    if len(p.idle) < p.config.MaxIdle {
        p.idle = append(p.idle, conn)
        select {
        case p.openerCh <- struct{}{}:
        default:
        }
    } else {
        p.opens--
    }

    p.mu.Unlock()
}

func (p *ConnPool) Close() error {
    p.mu.Lock()
    defer p.mu.Unlock()

    if p.closed {
        return nil
    }

    p.closed = true
    close(p.cleanerCh)

    for _, conn := range p.idle {
        conn.Close()
    }

    return nil
}

func (p *ConnPool) connectionCleaner() {
    ticker := time.NewTicker(time.Minute)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            p.cleanIdle()
        case <-p.cleanerCh:
            return
        }
    }
}

func (p *ConnPool) cleanIdle() {
    p.mu.Lock()
    defer p.mu.Unlock()

    now := time.Now()
    valid := make([]Conn, 0, len(p.idle))

    for _, conn := range p.idle {
        // 检查空闲超时和生命周期
        if conn.IsClosed() {
            p.opens--
        } else {
            valid = append(valid, conn)
        }
    }

    p.idle = valid
}

TCP 调优 ​

TCP 参数配置 ​

go
package main

import (
    "net"
    "syscall"
    "time"
)

// 配置 TCP 参数
func dialWithOpts() {
    d := &net.Dialer{
        Timeout:   30 * time.Second,
        KeepAlive: 30 * time.Second,
        Control: func(network, address string, c syscall.RawConn) error {
            return c.Control(func(fd uintptr) {
                // 禁用 Nagle 算法 (低延迟)
                syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_NODELAY, 1)

                // 设置 TCP keepalive
                syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1)

                // Linux 特有:设置 keepalive 参数
                // syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, 10)
                // syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, 5)
                // syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPCNT, 3)

                // 设置发送缓冲区
                syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_SNDBUF, 65536)

                // 设置接收缓冲区
                syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_RCVBUF, 65536)

                // 启用快速打开 (Linux)
                // syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, syscall.TCP_FASTOPEN, 1)
            })
        },
    }

    conn, err := d.Dial("tcp", "localhost:8080")
    if err != nil {
        panic(err)
    }
    defer conn.Close()
}

// 监听器配置
func listenWithOpts() error {
    lc := &net.ListenConfig{
        Control: func(network, address string, c syscall.RawConn) error {
            return c.Control(func(fd uintptr) {
                // SO_REUSEADDR
                syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)

                // SO_REUSEPORT (Linux 4.5+, macOS)
                syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x200, 1) // SO_REUSEPORT

                // 关闭延迟
                syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_LINGER, 0)
            })
        },
    }

    ln, err := lc.Listen(context.Background(), "tcp", ":8080")
    if err != nil {
        return err
    }
    defer ln.Close()

    return nil
}

网络编程检查清单 ​

[ ] 使用 goroutine 池控制并发
[ ] 实现连接复用
[ ] 设置合理的超时时间
[ ] 使用零拷贝技术传输大文件
[ ] 使用 sync.Pool 复用缓冲区
[ ] 配置 TCP 参数优化性能
[ ] 实现优雅关闭
[ ] 监控连接数和延迟
[ ] 使用上下文取消控制
[ ] 处理连接泄露