flyfei

WebSocket(gorilla/websocket库、实时通信场景)

1. 语法讲解

WebSocket是什么?

想象一下你和朋友打电话的场景:

WebSocket核心特点

WebSocket握手过程

客户端请求:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade

服务端响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade

为什么需要gorilla/websocket库? Go标准库没有提供完整的WebSocket实现,gorilla/websocket是业界最成熟的选择:

2. 应用场景

传统HTTP的问题

WebSocket解决方案

3. 编程实例

最简单的WebSocket服务器

先来看一个最基础的例子,理解WebSocket的核心流程:

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/gorilla/websocket"
)

// 创建WebSocket升级器
var upgrader = websocket.Upgrader{
    // 允许所有跨域请求(生产环境应该严格限制)
    CheckOrigin: func(r *http.Request) bool {
        return true
    },
}

func main() {
    // 设置WebSocket路由
    http.HandleFunc("/ws", handleWebSocket)
    
    // 启动静态文件服务(用于提供HTML页面)
    http.Handle("/", http.FileServer(http.Dir("./public")))
    
    fmt.Println("WebSocket服务器启动在 :8080")
    fmt.Println("访问 http://localhost:8080 测试聊天功能")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func handleWebSocket(w http.ResponseWriter, r *http.Request) {
    // 1. 升级HTTP连接到WebSocket连接
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println("升级WebSocket失败:", err)
        return
    }
    defer conn.Close() // 确保连接最终会关闭
    
    fmt.Println("新的WebSocket连接建立!")
    
    // 2. 持续监听和处理消息
    for {
        // 读取客户端发送的消息
        messageType, message, err := conn.ReadMessage()
        if err != nil {
            log.Println("读取消息失败:", err)
            break
        }
        
        fmt.Printf("收到消息: %s\n", message)
        
        // 3. 向客户端回送消息
        response := fmt.Sprintf("服务器回复: 收到你的消息 '%s'", message)
        err = conn.WriteMessage(messageType, []byte(response))
        if err != nil {
            log.Println("发送消息失败:", err)
            break
        }
    }
    
    fmt.Println("WebSocket连接关闭")
}

对应的前端页面(public/index.html)

<!DOCTYPE html>
<html>
<head>
    <title>WebSocket基础演示</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; }
        .container { border: 1px solid #ddd; padding: 20px; border-radius: 8px; }
        #messages { height: 300px; border: 1px solid #ccc; padding: 10px; overflow-y: scroll; margin-bottom: 10px; }
        input[type="text"] { width: 70%; padding: 8px; }
        button { padding: 8px 16px; background: #007bff; color: white; border: none; border-radius: 4px; }
    </style>
</head>
<body>
    <div class="container">
        <h2>WebSocket基础聊天演示</h2>
        <div id="messages"></div>
        <div>
            <input type="text" id="messageInput" placeholder="输入消息...">
            <button onclick="sendMessage()">发送</button>
            <button onclick="connect()" style="background: #28a745;">连接</button>
            <button onclick="disconnect()" style="background: #dc3545;">断开</button>
        </div>
        <div style="margin-top: 20px; color: #666;">
            <p><strong>操作流程:</strong></p>
            <ol>
                <li>点击"连接"建立WebSocket连接</li>
                <li>在输入框输入消息并发送</li>
                <li>观察服务器返回的响应</li>
                <li>点击"断开"关闭连接</li>
            </ol>
        </div>
    </div>

    <script>
        let ws = null;
        
        function addMessage(message, isSystem = false) {
            const messages = document.getElementById('messages');
            const messageDiv = document.createElement('div');
            messageDiv.style.padding = '5px';
            messageDiv.style.borderBottom = '1px solid #eee';
            if (isSystem) {
                messageDiv.style.color = 'green';
                messageDiv.style.fontStyle = 'italic';
            }
            messageDiv.textContent = message;
            messages.appendChild(messageDiv);
            messages.scrollTop = messages.scrollHeight;
        }
        
        function connect() {
            if (ws && ws.readyState === WebSocket.OPEN) {
                addMessage('已经连接到服务器了', true);
                return;
            }
            
            ws = new WebSocket('ws://localhost:8080/ws');
            
            ws.onopen = function() {
                addMessage('✅ WebSocket连接已建立!', true);
            };
            
            ws.onmessage = function(event) {
                addMessage('服务器: ' + event.data);
            };
            
            ws.onclose = function() {
                addMessage('❌ WebSocket连接已关闭', true);
            };
            
            ws.onerror = function(error) {
                addMessage('❌ WebSocket错误: ' + error, true);
            };
        }
        
        function sendMessage() {
            const input = document.getElementById('messageInput');
            const message = input.value.trim();
            
            if (!message) {
                alert('请输入消息');
                return;
            }
            
            if (!ws || ws.readyState !== WebSocket.OPEN) {
                alert('请先建立WebSocket连接');
                return;
            }
            
            ws.send(message);
            addMessage('我: ' + message);
            input.value = '';
        }
        
        function disconnect() {
            if (ws) {
                ws.close();
                ws = null;
            }
        }
        
        // 按回车发送消息
        document.getElementById('messageInput').addEventListener('keypress', function(e) {
            if (e.key === 'Enter') {
                sendMessage();
            }
        });
    </script>
</body>
</html>

WebSocket核心流程总结

  1. 连接建立
    // 客户端发起WebSocket请求
    // 服务端升级HTTP连接
    conn, err := upgrader.Upgrade(w, r, nil)
    
  2. 消息循环
    for {
        // 读取消息
        messageType, message, err := conn.ReadMessage()
           
        // 处理业务逻辑
           
        // 发送响应
        conn.WriteMessage(messageType, response)
    }
    
  3. 连接关闭
    defer conn.Close() // 确保资源释放
    

4. 其他用法

消息类型处理

func handleMessages(conn *websocket.Conn) {
    for {
        messageType, message, err := conn.ReadMessage()
        if err != nil {
            log.Println("读取失败:", err)
            break
        }
        
        switch messageType {
        case websocket.TextMessage:
            // 处理文本消息
            fmt.Printf("文本消息: %s\n", message)
            processTextMessage(conn, message)
            
        case websocket.BinaryMessage:
            // 处理二进制消息(如图片、文件)
            fmt.Printf("二进制消息,长度: %d bytes\n", len(message))
            processBinaryMessage(conn, message)
            
        case websocket.CloseMessage:
            // 处理关闭消息
            fmt.Println("客户端请求关闭连接")
            return
            
        case websocket.PingMessage:
            // 响应Ping消息
            conn.WriteMessage(websocket.PongMessage, nil)
            
        case websocket.PongMessage:
            // 处理Pong消息(心跳回应)
            fmt.Println("收到Pong消息")
        }
    }
}

连接状态管理

type ConnectionManager struct {
    connections map[*websocket.Conn]bool
    mutex       sync.Mutex
}

func (cm *ConnectionManager) addConnection(conn *websocket.Conn) {
    cm.mutex.Lock()
    defer cm.mutex.Unlock()
    cm.connections[conn] = true
    fmt.Printf("新连接加入,当前连接数: %d\n", len(cm.connections))
}

func (cm *ConnectionManager) removeConnection(conn *websocket.Conn) {
    cm.mutex.Lock()
    defer cm.mutex.Unlock()
    delete(cm.connections, conn)
    fmt.Printf("连接移除,当前连接数: %d\n", len(cm.connections))
}

func (cm *ConnectionManager) broadcast(message string) {
    cm.mutex.Lock()
    defer cm.mutex.Unlock()
    
    for conn := range cm.connections {
        err := conn.WriteMessage(websocket.TextMessage, []byte(message))
        if err != nil {
            conn.Close()
            delete(cm.connections, conn)
        }
    }
}

5. 课时总结

WebSocket核心概念

gorilla/websocket核心用法

  1. 创建升级器upgrader := websocket.Upgrader{}
  2. 升级连接conn, err := upgrader.Upgrade(w, r, nil)
  3. 消息循环:在for循环中读取和处理消息
  4. 资源清理:使用defer确保连接正确关闭