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是业界最成熟的选择:
传统HTTP的问题:
WebSocket解决方案:
最简单的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核心流程总结:
// 客户端发起WebSocket请求
// 服务端升级HTTP连接
conn, err := upgrader.Upgrade(w, r, nil)
for {
// 读取消息
messageType, message, err := conn.ReadMessage()
// 处理业务逻辑
// 发送响应
conn.WriteMessage(messageType, response)
}
defer conn.Close() // 确保资源释放
消息类型处理:
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)
}
}
}
WebSocket核心概念:
gorilla/websocket核心用法:
upgrader := websocket.Upgrader{}conn, err := upgrader.Upgrade(w, r, nil)