Go语言的WebSocket开发
WebSocket基础
WebSocket是一种在单个TCP连接上进行全双工通信的协议,它允许服务器和客户端之间进行实时数据交换。在Go语言中,WebSocket支持由gorilla/websocket包提供。
基本使用
安装依赖
go get github.com/gorilla/websocket创建WebSocket服务器
package main import ( "fmt" "net/http" "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true // 允许所有来源的请求 }, } func wsHandler(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { fmt.Println("Error upgrading to WebSocket:", err) return } defer conn.Close() for { // 读取消息 messageType, message, err := conn.ReadMessage() if err != nil { fmt.Println("Error reading message:", err) break } fmt.Printf("Received message: %s\n", message) // 发送消息 err = conn.WriteMessage(messageType, message) if err != nil { fmt.Println("Error writing message:", err) break } } } func main() { http.HandleFunc("/ws", wsHandler) fmt.Println("WebSocket server starting on port 8080...") http.ListenAndServe(":8080", nil) }客户端实现
使用JavaScript客户端
<!DOCTYPE html> <html> <head> <title>WebSocket Client</title> </head> <body> <input type="text" id="message" placeholder="Enter message"> <button onclick="sendMessage()">Send</button> <div id="messages"></div> <script> const socket = new WebSocket('ws://localhost:8080/ws'); socket.onopen = function(event) { console.log('Connected to WebSocket server'); }; socket.onmessage = function(event) { const messages = document.getElementById('messages'); messages.innerHTML += '<p>Server: ' + event.data + '</p>'; }; socket.onclose = function(event) { console.log('Disconnected from WebSocket server'); }; socket.onerror = function(error) { console.error('WebSocket error:', error); }; function sendMessage() { const messageInput = document.getElementById('message'); const message = messageInput.value; if (message) { socket.send(message); const messages = document.getElementById('messages'); messages.innerHTML += '<p>Client: ' + message + '</p>'; messageInput.value = ''; } } </script> </body> </html>高级功能
消息类型
WebSocket支持三种消息类型:
- TextMessage (1): 文本消息
- BinaryMessage (2): 二进制消息
- CloseMessage (8): 关闭连接消息
// 发送文本消息 conn.WriteMessage(websocket.TextMessage, []byte("Hello, WebSocket!")) // 发送二进制消息 conn.WriteMessage(websocket.BinaryMessage, []byte{1, 2, 3, 4, 5})心跳检测
package main import ( "fmt" "net/http" "time" "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } func wsHandler(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { fmt.Println("Error upgrading to WebSocket:", err) return } defer conn.Close() // 设置心跳检测 conn.SetReadDeadline(time.Now().Add(60 * time.Second)) conn.SetPongHandler(func(string) error { conn.SetReadDeadline(time.Now().Add(60 * time.Second)) return nil }) // 启动心跳发送协程 go func() { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: err := conn.WriteMessage(websocket.PingMessage, nil) if err != nil { return } } } }() // 处理消息 for { messageType, message, err := conn.ReadMessage() if err != nil { if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { fmt.Printf("Error: %v\n", err) } break } fmt.Printf("Received message: %s\n", message) err = conn.WriteMessage(messageType, message) if err != nil { fmt.Println("Error writing message:", err) break } } } func main() { http.HandleFunc("/ws", wsHandler) fmt.Println("WebSocket server starting on port 8080...") http.ListenAndServe(":8080", nil) }示例:实时聊天应用
服务器端
package main import ( "fmt" "net/http" "sync" "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } var ( clients = make(map[*websocket.Conn]bool) broadcast = make(chan []byte) clientsMu sync.Mutex ) func wsHandler(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { fmt.Println("Error upgrading to WebSocket:", err) return } defer conn.Close() // 添加客户端 clientsMu.Lock() clients[conn] = true clientsMu.Unlock() // 处理消息 for { _, message, err := conn.ReadMessage() if err != nil { clientsMu.Lock() delete(clients, conn) clientsMu.Unlock() break } broadcast <- message } } func broadcastMessages() { for { message := <-broadcast clientsMu.Lock() for client := range clients { err := client.WriteMessage(websocket.TextMessage, message) if err != nil { client.Close() delete(clients, client) } } clientsMu.Unlock() } } func main() { // 启动广播协程 go broadcastMessages() http.HandleFunc("/ws", wsHandler) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "index.html") }) fmt.Println("Chat server starting on port 8080...") http.ListenAndServe(":8080", nil) }客户端
<!DOCTYPE html> <html> <head> <title>Real-time Chat</title> <style> #messages { height: 300px; overflow-y: scroll; border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; } </style> </head> <body> <div id="messages"></div> <input type="text" id="message" placeholder="Enter message" style="width: 80%;"> <button onclick="sendMessage()">Send</button> <script> const socket = new WebSocket('ws://localhost:8080/ws'); socket.onopen = function(event) { console.log('Connected to chat server'); }; socket.onmessage = function(event) { const messages = document.getElementById('messages'); messages.innerHTML += '<p>' + event.data + '</p>'; messages.scrollTop = messages.scrollHeight; }; socket.onclose = function(event) { console.log('Disconnected from chat server'); }; socket.onerror = function(error) { console.error('WebSocket error:', error); }; function sendMessage() { const messageInput = document.getElementById('message'); const message = messageInput.value; if (message) { socket.send(message); messageInput.value = ''; } } // 按Enter键发送消息 document.getElementById('message').addEventListener('keypress', function(e) { if (e.key === 'Enter') { sendMessage(); } }); </script> </body> </html>性能优化
- 使用连接池管理WebSocket连接
- 实现消息批量发送
- 使用缓冲区减少内存分配
- 合理设置心跳检测间隔
- 限制单个连接的消息频率
总结
Go语言的gorilla/websocket包提供了强大的WebSocket支持,通过合理使用这些功能,可以构建实时聊天、实时数据更新等应用。WebSocket的全双工通信特性使其在需要实时数据交换的场景中非常有用。