Golang 如何实现一个长连接服务?如何处理连接的超时和心跳?


如何实现一个长连接服务?

长连接服务是指客户端与服务器之间建立持久的 TCP 连接,用于实时通信或频繁的数据交换。常见的应用场景包括聊天服务器、实时数据推送、游戏服务器等。

以下是实现长连接服务的关键步骤:

1. 建立 TCP 连接

  • 使用 net.Listen 监听端口,接受客户端连接。
  • 每个连接在一个独立的 Goroutine 中处理。

2. 维护连接状态

  • 使用 sync.Mapmap 存储所有活跃的连接。
  • 定期检查连接状态,清理断开的连接。

3. 处理数据读写

  • 使用 bufio.Readerbufio.Writer 高效地读写数据。
  • 使用协议(如 JSON、Protobuf)序列化和反序列化数据。

4. 实现心跳机制

  • 客户端定期发送心跳包,服务器检测心跳以判断连接是否存活。

5. 处理连接超时

  • 使用 SetDeadline 设置读写超时,防止连接长时间无响应。

如何处理连接的超时和心跳?

1. 设置读写超时

  • 使用 net.ConnSetReadDeadlineSetWriteDeadline 方法设置超时时间。
  • 如果超过指定时间没有读写操作,连接会自动关闭。

示例:

conn.SetReadDeadline(time.Now().Add(10 * time.Second))
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))

2. 实现心跳机制

  • 客户端定期发送心跳包(如 PING),服务器回复 PONG
  • 服务器定期检查客户端的心跳时间,如果超时则关闭连接。

示例:

func handleConnection(conn net.Conn) {
    defer conn.Close()
    lastHeartbeat := time.Now()

    go func() {
        for {
            _, err := conn.Write([]byte("PING\n"))
            if err != nil {
                log.Println("Failed to send heartbeat:", err)
                return
            }
            time.Sleep(5 * time.Second)
        }
    }()

    scanner := bufio.NewScanner(conn)
    for scanner.Scan() {
        msg := scanner.Text()
        if msg == "PONG" {
            lastHeartbeat = time.Now()
        } else {
            log.Println("Received message:", msg)
        }

        // 检查心跳超时
        if time.Since(lastHeartbeat) > 10*time.Second {
            log.Println("Heartbeat timeout")
            return
        }
    }
}

3. 使用 context 控制连接生命周期

  • 使用 context.Context 控制 Goroutine 的生命周期,确保连接关闭时资源被正确释放。

示例:

func handleConnection(ctx context.Context, conn net.Conn) {
    defer conn.Close()
    for {
        select {
        case <-ctx.Done():
            log.Println("Connection closed")
            return
        default:
            // 处理数据读写
        }
    }
}

完整的长连接服务示例

以下是一个完整的长连接服务示例,包含超时和心跳处理:

package main

import (
    "bufio"
    "context"
    "log"
    "net"
    "sync"
    "time"
)

type Connection struct {
    conn          net.Conn
    lastHeartbeat time.Time
}

var connections sync.Map

func main() {
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatal("Failed to start server:", err)
    }
    defer listener.Close()

    log.Println("Server started at :8080")

    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Println("Failed to accept connection:", err)
            continue
        }
        go handleConnection(conn)
    }
}

func handleConnection(conn net.Conn) {
    defer conn.Close()
    log.Println("New connection:", conn.RemoteAddr())

    // 初始化连接
    connection := &Connection{
        conn:          conn,
        lastHeartbeat: time.Now(),
    }
    connections.Store(conn.RemoteAddr().String(), connection)

    // 启动心跳检测
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    go checkHeartbeat(ctx, connection)

    // 处理数据读写
    scanner := bufio.NewScanner(conn)
    for scanner.Scan() {
        msg := scanner.Text()
        if msg == "PONG" {
            connection.lastHeartbeat = time.Now()
            log.Println("Received PONG from:", conn.RemoteAddr())
        } else {
            log.Println("Received message from", conn.RemoteAddr(), ":", msg)
        }
    }

    // 清理连接
    connections.Delete(conn.RemoteAddr().String())
    log.Println("Connection closed:", conn.RemoteAddr())
}

func checkHeartbeat(ctx context.Context, conn *Connection) {
    ticker := time.NewTicker(5 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            if time.Since(conn.lastHeartbeat) > 10*time.Second {
                log.Println("Heartbeat timeout, closing connection:", conn.conn.RemoteAddr())
                conn.conn.Close()
                return
            }
            // 发送心跳包
            _, err := conn.conn.Write([]byte("PING\n"))
            if err != nil {
                log.Println("Failed to send heartbeat:", err)
                return
            }
        }
    }
}

总结

实现长连接服务的关键在于:

  1. 维护连接状态:使用 sync.Mapmap 存储活跃连接。
  2. 处理超时:通过 SetDeadlinecontext 控制连接生命周期。
  3. 实现心跳机制:定期发送和检测心跳包,确保连接存活。

通过合理设计,可以实现高性能、高可靠的长连接服务。

posted @ 2025-02-19 15:39  guanyubo  阅读(487)  评论(0)    收藏  举报