在现代 Web 应用开发中,实时数据交互已成为核心需求。WebSocket 和 Server-Sent Events(SSE)作为两种主流的实时通信技术方案,分别适用于双向交互与单向推送场景。本文将基于 Go 语言的 Gin 框架,通过前后端代码示例,演示这两种技术的基本用法。
一、WebSocket 通信实现解析
1. 核心概念与优势
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,允许服务器主动向客户端推送数据,解决了 HTTP 协议的单向通信限制,适用于实时聊天、在线协作等场景。
2. Go 语言 Gin 框架实现代码
package controllers import ( "go_cli/utils" "log" "net/http" "strconv" "sync" "time" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) // Connection 表示一个客户端连接 type Connection struct { ID string WebSocket *websocket.Conn LastSeen time.Time IP string } // 全局连接管理器,直接使用sync.Map var connectionManager = sync.Map{} func HandleWebSocket(ctx *gin.Context) { // 配置WebSocket upgrader var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, // 允许所有CORS请求 CheckOrigin: func(r *http.Request) bool { return true }, } // 将HTTP连接升级为WebSocket连接 conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil) if err != nil { // utils.Fail(ctx, 500, "升级连接失败", err) return } defer conn.Close() log.Printf("客户端已连接: %s", ctx.Request.RemoteAddr) // 生成唯一客户端ID clientID := "websocket-" + strconv.FormatInt(time.Now().UnixNano(), 10) // 创建新连接对象 connection := &Connection{ ID: clientID, WebSocket: conn, LastSeen: time.Now(), IP: ctx.ClientIP(), } // 添加到连接管理器 connectionManager.Store(clientID, connection) log.Printf("新客户端已连接 (ID: %s, IP: %s)", clientID, connection.IP) // 向客户端发送欢迎消息,包含其ID if err := conn.WriteMessage(websocket.TextMessage, []byte("欢迎连接到WebSocket服务器! 您的ID: "+clientID)); err != nil { log.Println("写入欢迎消息失败:", err) return } // 持续读取客户端消息 for { // 更新最后活跃时间 connection.LastSeen = time.Now() // 读取消息 messageType, message, err := conn.ReadMessage() if err != nil { log.Printf("客户端 (ID: %s) 读取消息错误: %v", clientID, err) break } // 记录收到的消息 log.Printf("从客户端 (ID: %s) 收到消息: %s", clientID, message) // 将消息发送回客户端(回显),添加ID前缀 response := []byte("[" + clientID + "] " + string(message)) if err := conn.WriteMessage(messageType, response); err != nil { log.Printf("向客户端 (ID: %s) 写入消息失败: %v", clientID, err) break } } // 从连接管理器中移除 connectionManager.Delete(clientID) log.Printf("客户端断开连接 (ID: %s, IP: %s)", clientID, connection.IP) } // ListConnections 获取所有连接列表 func ListConnections() []*Connection { var connections []*Connection connectionManager.Range(func(key, value any) bool { if conn, ok := value.(*Connection); ok { connections = append(connections, conn) } return true }) return connections } // GetConnection 获取指定ID的连接 func GetConnection(clientID string) (*Connection, bool) { value, exists := connectionManager.Load(clientID) if !exists { return nil, false } conn, ok := value.(*Connection) return conn, ok }
3. 前端Vue实现WebSocket客户端
onMounted(() => { startWebSocket() }) onUnmounted(() => { stopWebSocket() }) let ws:WebSocket const startWebSocket = () => { ws = new WebSocket('ws://127.0.0.1:9000/ws/connect') ws.onopen = () => { console.log('WebSocket连接成功') // 在连接建立后,可以发送消息到服务器 sendWebSocketMessage('Hello, WebSocket!') } ws.onmessage = (event) => { console.log('接收到消息:', event.data) } ws.onclose = () => { console.log('WebSocket连接关闭') // 连接关闭后,重新连接 setTimeout(startWebSocket, 1000) } ws.onerror = (error) => { console.error('WebSocket错误:', error) } } const stopWebSocket = () => { // 主动关闭WebSocket连接 ws && ws.close() } const sendWebSocketMessage = (message:string | ArrayBufferLike | Blob | ArrayBufferView) => { // 向服务端发送消息 ws && ws.send(message) }
4. 关键逻辑说明
服务端
连接升级:通过gorilla/websocket库的Upgrader将 HTTP 请求升级为 WebSocket 协议,CheckOrigin允许跨域访问。
连接管理:使用sync.Map存储客户端连接,保证并发安全,包含 ID、连接对象、最后活跃时间和 IP 信息。
消息处理:循环读取客户端消息并回显,同时更新连接的活跃时间,断开时自动从连接池中移除。
客户端
创建WebSocket连接:使用new WebSocket(url)构造函数创建一个新的WebSocket对象,其中url是WebSocket服务器的地址。
设置事件处理程序:为WebSocket对象设置各种事件处理程序,如onopen、onmessage、onerror和onclose。
发送消息:当WebSocket连接成功建立后(即onopen事件触发时),客户端可以通过调用send方法发送消息。
接收消息:当服务器发送消息时(即onmessage事件触发时),客户端可以接收消息。
关闭连接:当不再需要WebSocket连接时,可以调用close方法关闭连接。
二、SSE(Server-Sent Events)通信实现解析
1. 核心概念与优势
SSE 是基于 HTTP 的单向通信协议,由服务器主动向客户端推送数据,适合新闻推送、实时通知等场景。其优势在于简单易用,无需复杂的连接管理,且支持自动重连。
2. Go 语言 Gin 框架实现代码
package controllers import ( "log" "net/http" "strconv" "sync" "time" "github.com/gin-gonic/gin" ) // SSEClient 表示一个SSE客户端连接 type SSEClient struct { ID string Writer http.ResponseWriter IP string Connected time.Time } // 全局客户端连接管理器 var sseClients = sync.Map{} func HandleSSE(ctx *gin.Context) { // 设置响应头 ctx.Writer.Header().Set("Content-Type", "text/event-stream") ctx.Writer.Header().Set("Cache-Control", "no-cache") ctx.Writer.Header().Set("Connection", "keep-alive") // 生成唯一客户端ID clientID := "sse-" + strconv.FormatInt(time.Now().UnixNano(), 10) // 创建新客户端连接 client := &SSEClient{ ID: clientID, Writer: ctx.Writer, IP: ctx.ClientIP(), Connected: time.Now(), } // 添加到连接管理器 sseClients.Store(clientID, client) log.Printf("新SSE客户端已连接 (ID: %s, IP: %s)", clientID, client.IP) // 发送欢迎消息 ctx.SSEvent("welcome", gin.H{"clientID": clientID}) // ctx.Writer.Write([]byte("data: 欢迎使用SSE服务!\n\n")) ctx.Writer.Flush() // 监听客户端断开事件 notify := ctx.Writer.CloseNotify() // 定时发送当前时间 ticker := time.NewTicker(2 * time.Second) defer ticker.Stop() for { select { case <-notify: // 客户端断开连接,从管理器中移除 sseClients.Delete(clientID) log.Printf("SSE客户端断开连接 (ID: %s, IP: %s)", clientID, client.IP) case <-ticker.C: // 发送当前时间 ctx.SSEvent("message", gin.H{"time": time.Now().Format("2006-01-02 15:04:05")}) // ctx.Writer.Write([]byte("data: " + time.Now().Format("2006-01-02 15:04:05") + "\n\n")) ctx.Writer.Flush() } } } // 获取当前所有连接的客户端 func GetAllClients() []*SSEClient { var clients []*SSEClient sseClients.Range(func(key, value interface{}) bool { if client, ok := value.(*SSEClient); ok { clients = append(clients, client) } return true }) return clients } // 获取指定ID的连接 func GetSSEClient(clientID string) (*SSEClient, bool) { value, exists := sseClients.Load(clientID) if !exists { return nil, false } conn, ok := value.(*SSEClient) return conn, ok }
3. 前端Vue实现SSE客户端
onMounted(() => { startSSE() }) onUnmounted(() => { stopSSE() }) let sse:EventSource const startSSE = () => { sse = new EventSource('http://127.0.0.1:9000/sse/connect') sse.onopen = () => { console.log('SSE连接成功') } sse.onmessage = (event) => { console.log('接收到消息:', event.data) } sse.onerror = (error) => { console.error('SSE错误:', error) } } const stopSSE = () => { // 主动关闭SSE连接 sse && sse.close() }
4. 关键逻辑说明
响应头设置:必须设置Content-Type: text/event-stream,禁用缓存并保持长连接。
连接管理:使用sync.Map存储客户端的ResponseWriter,定时任务每秒推送当前时间。
断开处理:通过CloseNotify监听客户端断开事件,自动移除连接。
三、技术对比与适用场景
特性 | WebSocket | SSE |
---|---|---|
通信方向 | 全双工 (双向) | 单工 (服务器→客户端) |
协议基础 | 基于 TCP,独立协议(ws/wss) | 基于 HTTP |
典型场景 | 实时聊天、在线协作、游戏 | 新闻推送、系统通知、日志监控 |
连接管理 | 需要手动处理心跳、重连 | 自动重连,服务端单向维护 |
数据格式 | 支持文本 / 二进制,灵活性高 | 仅限文本格式(JSON / 纯文本) |
跨域处理 | 需要服务端显式配置CheckOrigin | 依赖 HTTP 响应头Access-Control |