在现代 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 |






