Golang/Gin-WebSocket实现实时消息推送
前言
WebSocket在 HTML5 游戏和网页消息推送都使用比较多。
WebSocket 是 HTML5 的重要特性,它实现了基于浏览器的远程socket,它使浏览器和服务器可以进行全双工通信。
目前Go中用的比较多的WebSocket包是gorilla/websocket ,本文将介绍如何使用gorilla/websocket ,在Gin框架下编写WebSocket实时消息推送。
gorilla/websocket基础用法
gorilla/websocket 相关链接:
Github地址:https://github.com/gorilla/websocket
文档:https://godoc.org/github.com/gorilla/websocket
gorilla/websocket 的安装:
go get github.com/gorilla/websocket
Upgrader用于升级 http 请求,把 http 请求升级为长连接的 WebSocket。
Hello World示例:
package main
import (
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"log"
"net/http"
"time"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func main() {
engine := gin.Default()
engine.GET("/helloWebSocket", func(context *gin.Context) {
client, _ := upgrader.Upgrade(context.Writer, context.Request, nil)
for {
err := client.WriteMessage(websocket.TextMessage, []byte("hello, WebSocket"))
if err != nil {
log.Println(err)
}
time.Sleep(time.Second * 2)
}
})
err := engine.Run(":8090")
if err != nil {
log.Fatalln(err)
}
}
写完以后你可以用websocket在线测试工具测试你的代码:http://coolaf.com/tool/chattest。
请求url前面的http://记得换成ws://。
实现实时消息推送
这部分我参考了GitHub上lwnmengjing的代码,自己做了一些修改以贴合自己的需求,大家也可以直接到https://github.com/lwnmengjing/pushMessage参考他的代码。
代码
以下是我自己修改后的代码:
package module
import (
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"log"
"net/http"
"sync"
"time"
)
var (
news = make(map[string]chan interface{})
client = make(map[string]*websocket.Conn)
mux sync.Mutex
)
func GetPushNews(context *gin.Context) {
id := context.Query("userId")
log.Println(id + "websocket链接")
WsHandler(context.Writer, context.Request, id)
}
func DeleteClient(context *gin.Context) {
id := context.Param("id")
conn, exist := getClient(id)
if exist {
conn.Close()
deleteClient(id)
} else {
context.JSON(http.StatusOK, gin.H{
"mesg": "未找到该客户端",
})
}
_, exist =getNewsChannel(id)
if exist {
deleteNewsChannel(id)
}
}
var wsupgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
HandshakeTimeout: 5 * time.Second,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func WsHandler(w http.ResponseWriter, r *http.Request, id string) {
var conn *websocket.Conn
var err error
var exist bool
pingTicker := time.NewTicker(time.Second * 10)
conn, err = wsupgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
addClient(id, conn)
m, exist := getNewsChannel(id)
if !exist {
m = make(chan interface{})
addNewsChannel(id, m)
}
conn.SetCloseHandler(func(code int, text string) error {
deleteClient(id)
log.Println(code)
return nil
})
for {
select {
case content, _ := <- m:
err = conn.WriteJSON(content)
if err != nil {
log.Println(err)
conn.Close()
deleteClient(id)
return
}
case <- pingTicker.C:
conn.SetWriteDeadline(time.Now().Add(time.Second * 20))
err = conn.WriteMessage(websocket.PingMessage, []byte{})
if err != nil {
log.Println("send ping err:", err)
conn.Close()
deleteClient(id)
return
}
}
}
}
func addClient(id string, conn *websocket.Conn) {
mux.Lock()
client[id] = conn
mux.Unlock()
}
func getClient(id string) (conn *websocket.Conn, exist bool) {
mux.Lock()
conn, exist = client[id]
mux.Unlock()
return
}
func deleteClient(id string) {
mux.Lock()
delete(client, id)
log.Println(id + "websocket退出")
mux.Unlock()
}
func addNewsChannel(id string, m chan interface{}) {
mux.Lock()
news[id] = m
mux.Unlock()
}
func getNewsChannel(id string) (m chan interface{}, exist bool) {
mux.Lock()
m, exist = news[id]
mux.Unlock()
return
}
func deleteNewsChannel(id string) {
mux.Lock()
if m, ok := news[id]; ok {
close(m)
delete(news, id)
}
mux.Unlock()
}
api
接口 | 请求方式 | 参数 | 用途 |
---|
/getPushNews | get | id,ws客户端链接标记 | websocket客户端连接 | /deleteClient:id | delete | :id为ws客户端链接标记 | 销毁已经连接过的客户端 |
补充说明
当你要给某个用户推送消息时,你只需要使用getNewsChannel() 方法获取该用户的消息通道,然后把消息送入通道就可以了。
若用户离线,你可以把消息直接存到用户所有消息中,或者设置一个消息队列,把消息放到用户未读消息队列中,下次用户上线时再一次性推送给用户。
服务端心跳:服务端每隔20秒回ping一下用户,查看其是否还在线,若ping不到,则服务端自动关闭websocket链接。
|