Files
pika/internal/websocket/manager.go
T
2025-12-12 23:24:43 +08:00

287 lines
7.1 KiB
Go

package websocket
import (
"context"
"encoding/json"
"sync"
"time"
"github.com/dushixiang/pika/internal/protocol"
"github.com/gorilla/websocket"
"go.uber.org/zap"
)
// Client WebSocket客户端
type Client struct {
ID string // 探针ID
Conn *websocket.Conn // WebSocket连接
Send chan []byte // 发送消息通道
Manager *Manager // 管理器引用
LastActive time.Time // 最后活跃时间
closed bool // 标记channel是否已关闭
closeMu sync.Mutex // 保护closed字段
}
// Manager WebSocket连接管理器
type Manager struct {
clients map[string]*Client // 客户端映射 probeID -> Client
register chan *Client // 注册通道
unregister chan *Client // 注销通道
broadcast chan []byte // 广播通道
mu sync.RWMutex // 读写锁
logger *zap.Logger // 日志
onMessage MessageHandler // 消息处理器
}
// MessageHandler 消息处理器接口
type MessageHandler func(ctx context.Context, probeID string, messageType string, data json.RawMessage) error
// NewManager 创建新的WebSocket管理器
func NewManager(logger *zap.Logger) *Manager {
return &Manager{
clients: make(map[string]*Client),
register: make(chan *Client, 10),
unregister: make(chan *Client, 10),
broadcast: make(chan []byte, 256),
logger: logger,
}
}
// SetMessageHandler 设置消息处理器
func (m *Manager) SetMessageHandler(handler MessageHandler) {
m.onMessage = handler
}
// Run 启动管理器
func (m *Manager) Run(ctx context.Context) {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
m.logger.Info("websocket manager stopped")
return
case client := <-m.register:
m.registerClient(client)
case client := <-m.unregister:
m.unregisterClient(client)
case message := <-m.broadcast:
m.broadcastMessage(message)
case <-ticker.C:
m.checkInactiveClients()
}
}
}
// Register 注册客户端(公开方法)
func (m *Manager) Register(client *Client) {
m.register <- client
}
// registerClient 注册客户端
func (m *Manager) registerClient(client *Client) {
m.mu.Lock()
defer m.mu.Unlock()
// 如果已存在该探针的连接,先关闭旧连接
if oldClient, exists := m.clients[client.ID]; exists {
m.logger.Info("agent reconnected, closing old connection", zap.String("agentID", client.ID))
oldClient.closeChannel()
oldClient.Conn.Close()
}
m.clients[client.ID] = client
m.logger.Info("agent connected", zap.String("agentID", client.ID), zap.Int("totalClients", len(m.clients)))
}
// unregisterClient 注销客户端
func (m *Manager) unregisterClient(client *Client) {
m.mu.Lock()
defer m.mu.Unlock()
if _, exists := m.clients[client.ID]; exists {
delete(m.clients, client.ID)
client.closeChannel()
m.logger.Info("agent disconnected", zap.String("agentID", client.ID), zap.Int("totalClients", len(m.clients)))
}
}
// broadcastMessage 广播消息
func (m *Manager) broadcastMessage(message []byte) {
m.mu.RLock()
defer m.mu.RUnlock()
for _, client := range m.clients {
select {
case client.Send <- message:
default:
// 发送失败,客户端可能已断开
m.logger.Warn("failed to send message, client may be disconnected", zap.String("agentID", client.ID))
}
}
}
// checkInactiveClients 检查不活跃的客户端
func (m *Manager) checkInactiveClients() {
m.mu.RLock()
inactiveClients := make([]*Client, 0)
timeout := 2 * time.Minute
for _, client := range m.clients {
if time.Since(client.LastActive) > timeout {
inactiveClients = append(inactiveClients, client)
}
}
m.mu.RUnlock()
// 断开不活跃的客户端
for _, client := range inactiveClients {
// 再次检查客户端是否仍然存在(避免竞态条件)
m.mu.RLock()
_, exists := m.clients[client.ID]
m.mu.RUnlock()
if exists {
m.logger.Warn("agent inactive timeout, disconnecting", zap.String("agentID", client.ID))
client.Conn.Close()
m.unregister <- client
}
}
}
// SendToClient 发送消息给指定客户端
func (m *Manager) SendToClient(probeID string, message []byte) error {
m.mu.RLock()
client, exists := m.clients[probeID]
m.mu.RUnlock()
if !exists {
return ErrClientNotFound
}
select {
case client.Send <- message:
return nil
default:
return ErrSendTimeout
}
}
// GetClient 获取客户端
func (m *Manager) GetClient(probeID string) (*Client, bool) {
m.mu.RLock()
defer m.mu.RUnlock()
client, exists := m.clients[probeID]
return client, exists
}
// GetAllClients 获取所有客户端ID
func (m *Manager) GetAllClients() []string {
m.mu.RLock()
defer m.mu.RUnlock()
ids := make([]string, 0, len(m.clients))
for id := range m.clients {
ids = append(ids, id)
}
return ids
}
// ClientCount 获取客户端数量
func (m *Manager) ClientCount() int {
m.mu.RLock()
defer m.mu.RUnlock()
return len(m.clients)
}
// ReadPump 读取客户端消息
func (c *Client) ReadPump(ctx context.Context) {
defer func() {
c.Manager.unregister <- c
c.Conn.Close()
}()
c.Conn.SetReadDeadline(time.Now().Add(60 * time.Second))
c.Conn.SetPongHandler(func(string) error {
c.Conn.SetReadDeadline(time.Now().Add(60 * time.Second))
c.LastActive = time.Now()
return nil
})
for {
_, message, err := c.Conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
c.Manager.logger.Error("websocket read error", zap.Error(err), zap.String("agentID", c.ID))
}
break
}
c.LastActive = time.Now()
// 解析消息
var msg protocol.InputMessage
if err := json.Unmarshal(message, &msg); err != nil {
c.Manager.logger.Error("failed to parse message", zap.Error(err), zap.String("agentID", c.ID))
continue
}
// 处理消息
if c.Manager.onMessage != nil {
if err := c.Manager.onMessage(ctx, c.ID, string(msg.Type), msg.Data); err != nil {
c.Manager.logger.Error("failed to handle message", zap.Error(err), zap.String("agentID", c.ID), zap.String("type", string(msg.Type)))
}
}
}
}
// WritePump 向客户端写入消息
func (c *Client) WritePump() {
ticker := time.NewTicker(30 * time.Second)
defer func() {
ticker.Stop()
c.Conn.Close()
}()
for {
select {
case message, ok := <-c.Send:
c.Conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
if !ok {
// 通道已关闭
c.Conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
if err := c.Conn.WriteMessage(websocket.TextMessage, message); err != nil {
c.Manager.logger.Error("failed to write message", zap.Error(err), zap.String("agentID", c.ID))
return
}
case <-ticker.C:
c.Conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
if err := c.Conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
}
}
}
// closeChannel 安全地关闭发送通道
func (c *Client) closeChannel() {
c.closeMu.Lock()
defer c.closeMu.Unlock()
if !c.closed {
close(c.Send)
c.closed = true
}
}
// 错误定义
var (
ErrClientNotFound = &websocket.CloseError{Code: 1000, Text: "client not found"}
ErrSendTimeout = &websocket.CloseError{Code: 1001, Text: "send timeout"}
)