Golang 如何实现一个长连接服务?如何处理连接的超时和心跳?
目录
如何实现一个长连接服务?
长连接服务是指客户端与服务器之间建立持久的 TCP 连接,用于实时通信或频繁的数据交换。常见的应用场景包括聊天服务器、实时数据推送、游戏服务器等。
以下是实现长连接服务的关键步骤:
1. 建立 TCP 连接
- 使用
net.Listen监听端口,接受客户端连接。 - 每个连接在一个独立的 Goroutine 中处理。
2. 维护连接状态
- 使用
sync.Map或map存储所有活跃的连接。 - 定期检查连接状态,清理断开的连接。
3. 处理数据读写
- 使用
bufio.Reader和bufio.Writer高效地读写数据。 - 使用协议(如 JSON、Protobuf)序列化和反序列化数据。
4. 实现心跳机制
- 客户端定期发送心跳包,服务器检测心跳以判断连接是否存活。
5. 处理连接超时
- 使用
SetDeadline设置读写超时,防止连接长时间无响应。
如何处理连接的超时和心跳?
1. 设置读写超时
- 使用
net.Conn的SetReadDeadline和SetWriteDeadline方法设置超时时间。 - 如果超过指定时间没有读写操作,连接会自动关闭。
示例:
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
}
}
}
}
总结
实现长连接服务的关键在于:
- 维护连接状态:使用
sync.Map或map存储活跃连接。 - 处理超时:通过
SetDeadline或context控制连接生命周期。 - 实现心跳机制:定期发送和检测心跳包,确保连接存活。
通过合理设计,可以实现高性能、高可靠的长连接服务。
Do not communicate by sharing memory; instead, share memory by communicating.

浙公网安备 33010602011771号