go gin 框架如何使用 websocket
在 Web 开发中,实时通讯是很多场景的核心需求 —— 比如在线聊天、实时数据监控、订单状态推送、协作工具同步等。
而 WebSocket 作为 HTML5 标准的实时通讯协议,能实现客户端与服务端的全双工通信,相比轮询、长轮询更高效、低延迟。
那么在 go gin 框架中应该如何使用 websocket 呢?
一、安装
Gin本身没有内置WebSocket模块,业界常用的是gorilla/websocket库,这个库成熟稳定,文档也比较全。
安装命令:
go get -u github.com/gorilla/websocket这里提一嘴,为什么不用标准库?主要是标准库的WebSocket支持比较基础,很多实际开发需要的功能比如心跳检测、消息分片都得自己实现,gorilla/websocket已经封装好了,能省不少事。
二、实现:Gin+WebSocket
这部分是核心,主要分服务端和客户端两部分。
(一)服务端实现
服务端的核心逻辑是接收客户端的HTTP请求,把它升级成WebSocket连接,然后持续监听客户端消息并处理。
直接上代码,关键地方我加了详细注释:
package main
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
// 定义升级器,用于把HTTP连接升级为WebSocket
var upgrader = websocket.Upgrader{
// 允许跨域,实际生产环境要指定具体的Origin,不要用*
CheckOrigin: func(r *http.Request) bool {
return true
},
// 设置读写缓冲区大小,根据实际需求调整
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
// WebSocket处理函数
func wsHandler(c *gin.Context) {
// 1. 升级HTTP连接为WebSocket连接
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Printf("升级连接失败: %v", err)
return
}
// 3. 延迟关闭连接,确保资源释放
defer func() {
if err := conn.Close(); err != nil {
log.Printf("关闭连接失败: %v", err)
}
}()
log.Println("客户端连接成功")
// 2. 持续监听客户端消息
for {
// 读取客户端发送的消息,messageType是消息类型(文本/二进制),p是消息内容
messageType, p, err := conn.ReadMessage()
if err != nil {
log.Printf("读取消息失败: %v", err)
break
}
// 打印客户端消息
log.Printf("收到客户端消息: %s", p)
// 3. 回复客户端消息(echo模式,实际开发可替换为业务逻辑)
err = conn.WriteMessage(messageType, []byte("服务端已收到: "+string(p)))
if err != nil {
log.Printf("发送消息失败: %v", err)
break
}
}
}
func main() {
// 初始化Gin引擎,生产环境用gin.ReleaseMode
r := gin.Default()
// 注册WebSocket路由,注意用GET方法
r.GET("/ws", wsHandler)
// 启动服务,监听8080端口
if err := r.Run(":8080"); err != nil {
log.Fatalf("服务启动失败: %v", err)
}
}这里有个细节得注意:WebSocket路由必须用GET方法,因为WebSocket握手是基于GET请求的。
另外,生产环境里CheckOrigin一定不能写return true,要根据自己的域名设置白名单,
比如return r.Header.Get("Origin") == "https://yourdomain.com",防止跨域攻击。
(三)客户端实现
客户端用原生的WebSocket API就行,写个简单的HTML页面就能测试。
创建一个index.html文件,内容如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Gin WebSocket测试</title>
</head>
<body>
<h1>Gin WebSocket测试工具</h1>
<div>
<input type="text" id="messageInput" placeholder="输入消息">
<button onclick="sendMessage()">发送</button>
<button onclick="closeConn()">关闭连接</button>
</div>
<div id="messageList" style="margin-top: 20px;"></div>
<script>
// 初始化WebSocket连接,注意协议是ws(http对应ws,https对应wss)
let ws = new WebSocket("ws://localhost:8080/ws");
let messageList = document.getElementById("messageList");
let messageInput = document.getElementById("messageInput");
// 连接成功回调
ws.onopen = function() {
addMessage("连接成功");
};
// 接收服务端消息回调
ws.onmessage = function(event) {
addMessage("服务端回复: " + event.data);
};
// 连接关闭回调
ws.onclose = function() {
addMessage("连接关闭");
};
// 连接错误回调
ws.onerror = function(error) {
addMessage("连接错误: " + error);
};
// 发送消息函数
function sendMessage() {
let msg = messageInput.value.trim();
if (!msg) {
alert("请输入消息");
return;
}
ws.send(msg);
addMessage("客户端发送: " + msg);
messageInput.value = "";
}
// 关闭连接函数
function closeConn() {
ws.close();
}
// 添加消息到页面
function addMessage(content) {
let div = document.createElement("div");
div.textContent = new Date().toLocaleString() + ": " + content;
messageList.appendChild(div);
}
</script>
</body>
</html>(三)启动测试
- 把服务端代码保存为
main.go,客户端代码保存为index.html,放在同一个目录下; - 执行
go run main.go启动服务端,看到“[GIN-debug] Listening and serving HTTP on :8080”说明启动成功; - 用浏览器打开
index.html,输入消息点击“发送”,就能看到客户端和服务端的消息交互了; - 同时观察服务端控制台,会打印客户端连接信息和收到的消息。
四、进阶优化
(一)心跳检测:防止连接被断开
很多网关或服务器会断开长时间没有数据交互的连接,所以必须加心跳检测。
原理就是客户端和服务端定期互相发送心跳包,证明连接还活着。
修改服务端的wsHandler函数,添加心跳检测逻辑
func wsHandler(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Printf("升级连接失败: %v", err)
return
}
defer conn.Close()
log.Println("客户端连接成功")
// 设置心跳超时时间:30秒内没收到客户端消息就关闭连接
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
// 心跳重置函数,每次收到客户端消息就刷新超时时间
conn.SetPongHandler(func(string) error {
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
return nil
})
// 启动心跳发送协程,每10秒发一次心跳包
go func() {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for range ticker.C {
// 发送ping消息(心跳包)
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
log.Printf("发送心跳失败: %v", err)
return
}
}
}()
// 消息监听逻辑不变
for {
messageType, p, err := conn.ReadMessage()
if err != nil {
log.Printf("读取消息失败: %v", err)
break
}
log.Printf("收到客户端消息: %s", p)
err = conn.WriteMessage(messageType, []byte("服务端已收到: "+string(p)))
if err != nil {
log.Printf("发送消息失败: %v", err)
break
}
}
log.Println("客户端连接断开")
}客户端也需要对应处理ping消息,在JS里添加:
// 处理服务端的ping消息,自动回复pong
ws.onping = function(event) {
ws.pong();
addMessage("收到服务端心跳包");
};(二)消息分片:处理大消息
如果要传输超过缓冲区大小的大消息,gorilla/websocket会自动分片,但需要确保客户端能正确处理。
服务端不需要额外配置,客户端原生WebSocket也支持自动拼接分片消息,所以只要缓冲区设置合理(前面代码里的ReadBufferSize和WriteBufferSize),一般没问题。
如果消息特别大(比如几MB),建议在业务层做分片,比如分块发送并带序号,服务端再拼接。
常见问题
Q1、连接失败:升级HTTP为WebSocket失败
问题现象:服务端报错“upgrader: request header not present”或客户端报“WebSocket connection to ‘ws://xxx’ failed”。
常见原因:
- 路由用了POST方法,WebSocket必须用GET;
- 跨域问题,
CheckOrigin没有允许客户端域名; - 请求头里没有WebSocket相关的字段,可能被网关拦截了。
解决方案:
- 确保路由是GET方法;
- 生产环境配置正确的
CheckOrigin白名单; - 如果有nginx等网关,配置转发时保留Upgrade和Connection头,比如nginx配置:
location /ws {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}Q2、消息发送失败:写入连接时出错
问题现象:服务端报“write: broken pipe”或“websocket: close sent”。
常见原因:
- 客户端已经断开连接,但服务端还在发消息;
- 心跳超时导致连接被关闭。
解决方案:
- 发送消息前检查连接状态,或用defer和错误处理捕获写入错误;
- 调整心跳超时时间,根据实际网络情况设置(一般10-60秒)。
Q3、消息粘包:多个消息被合并接收
问题现象:客户端连续发两条消息,服务端一次收到了合并后的内容。
常见原因:TCP是流式协议,没有消息边界,当发送频率过高时会出现粘包。
解决方案:
- 定义消息格式,比如用JSON包裹,每个消息带长度字段;
- 发送消息后加微小延迟(不推荐,影响性能);
- 使用gorilla/websocket的
NextReader和NextWriter方法,明确消息边界。
总结
go gin 使用 websocket 还是非常简单的,有非常成熟的库可以直接使用。
上面的代码我都实际跑过,有问题的话可以对照常见问题排查,或者在评论区留言交流!!!
版权声明
未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!
本文原文链接: https://fiveyoboy.com/articles/go-gin-websocket/
备用原文链接: https://blog.fiveyoboy.com/articles/go-gin-websocket/