client.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. package websocket
  2. import (
  3. "encoding/json"
  4. "log"
  5. "time"
  6. "github.com/gorilla/websocket"
  7. )
  8. const (
  9. // 客户端发送 ping 的最大等待时间
  10. writeWait = 10 * time.Second
  11. // 从客户端读取 pong 的最大等待时间
  12. pongWait = 60 * time.Second
  13. // 发送 ping 的频率(必须小于 pongWait)
  14. pingPeriod = (pongWait * 9) / 10
  15. // 最大消息大小
  16. maxMessageSize = 512 * 1024 // 512KB
  17. )
  18. // Client 是一个 WebSocket 客户端
  19. type Client struct {
  20. hub *Hub
  21. // WebSocket 连接
  22. conn *websocket.Conn
  23. // 发送消息的通道
  24. send chan *Message
  25. // 对话ID(这个客户端属于哪个对话)
  26. conversationID uint
  27. // 是否是访客(true 表示访客,false 表示客服)
  28. isVisitor bool
  29. }
  30. // NewClient 创建一个新的客户端
  31. func NewClient(hub *Hub, conn *websocket.Conn, conversationID uint, isVisitor bool) *Client {
  32. return &Client{
  33. hub: hub,
  34. conn: conn,
  35. send: make(chan *Message, 256),
  36. conversationID: conversationID,
  37. isVisitor: isVisitor,
  38. }
  39. }
  40. // ReadPump 从 WebSocket 连接读取消息
  41. func (c *Client) ReadPump() {
  42. defer func() {
  43. c.hub.unregister <- c
  44. c.conn.Close()
  45. }()
  46. // 设置读取限制和超时
  47. c.conn.SetReadDeadline(time.Now().Add(pongWait))
  48. c.conn.SetReadLimit(maxMessageSize)
  49. c.conn.SetPongHandler(func(string) error {
  50. c.conn.SetReadDeadline(time.Now().Add(pongWait))
  51. return nil
  52. })
  53. // 持续读取消息
  54. for {
  55. _, _, err := c.conn.ReadMessage()
  56. if err != nil {
  57. if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
  58. log.Printf("⚠️ WebSocket 读取错误: 对话ID=%d, 错误=%v", c.conversationID, err)
  59. }
  60. break
  61. }
  62. // 目前我们不需要处理客户端发送的消息,只接收心跳包
  63. // 如果需要双向通信,可以在这里处理客户端消息
  64. }
  65. }
  66. // WritePump 向 WebSocket 连接写入消息
  67. func (c *Client) WritePump() {
  68. ticker := time.NewTicker(pingPeriod)
  69. defer func() {
  70. ticker.Stop()
  71. c.conn.Close()
  72. }()
  73. for {
  74. select {
  75. case message, ok := <-c.send:
  76. c.conn.SetWriteDeadline(time.Now().Add(writeWait))
  77. if !ok {
  78. // Hub 关闭了通道
  79. c.conn.WriteMessage(websocket.CloseMessage, []byte{})
  80. return
  81. }
  82. // 发送消息
  83. if err := c.conn.WriteJSON(message); err != nil {
  84. log.Printf("❌ WebSocket 写入错误: 对话ID=%d, 类型=%s, 错误=%v",
  85. c.conversationID, message.Type, err)
  86. return
  87. }
  88. case <-ticker.C:
  89. // 定期发送 ping 保持连接
  90. c.conn.SetWriteDeadline(time.Now().Add(writeWait))
  91. if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
  92. log.Printf("❌ 发送 ping 失败: 对话ID=%d, 错误=%v", c.conversationID, err)
  93. return
  94. }
  95. }
  96. }
  97. }
  98. // SendMessage 发送消息给客户端(用于测试)
  99. func (c *Client) SendMessage(messageType string, data interface{}) error {
  100. message := &Message{
  101. ConversationID: c.conversationID,
  102. Type: messageType,
  103. Data: data,
  104. }
  105. messageJSON, err := json.Marshal(message)
  106. if err != nil {
  107. return err
  108. }
  109. c.conn.SetWriteDeadline(time.Now().Add(writeWait))
  110. return c.conn.WriteMessage(websocket.TextMessage, messageJSON)
  111. }