一、依赖包的安装

  • 1、在​​go​​语言中常见的​​websocket​​包有以下两个

二、在​​gin​​​中使用​​websocket​

  • 1、接入鉴权

​websocket​​​也可以和普通​​api​​​接口一样的做一个接口鉴权(​​token​​机制),如果验证通过可以继续往下走,没有验证不能往下走

func Chat(ctx *gin.Context) {
var upGrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
// 根据鉴权的方式来处理,如果不想鉴权的就直接返回true,如果需要鉴权就要根据判断来返回true,或者false
return true
},
}
}
  • 2、实现鉴权处理
func Chat(ctx *gin.Context) {
// 根据url地址上获取当前用户id和token
userId := ctx.DefaultQuery("userId", "")
token := ctx.DefaultQuery("token", "")
userIdInt, _ := strconv.ParseInt(userId, 10, 64)
isValied := checkToken(userIdInt, token)
var upGrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
// 根据判断token的方法来鉴权,如果没token就返回false
return isValied
},
}
}

// 内部使用判断token合法
func checkToken(userId int64, token string) bool {
...
return true
}
  • 3、将普通的​​get​​请求升级为​​websocket​​请求
...
//升级get请求为webSocket协议
conn, err := upGrader.Upgrade(ctx.Writer, ctx.Request, nil)
if err != nil {
fmt.Println("websocket连接错误")
return
}
  • 4、​​conn​​连接句柄的维护
// Node 当前用户节点 userId和Node的映射关系
type Node struct {
Conn *websocket.Conn
DataQueue chan []byte
// 群组的消息分发
GroupSet set.Interface
}

// 映射关系表
var clientMap map[int64]*Node = make(map[int64]*Node, 0)

// 读写锁
var rwLocker sync.RWMutex
  • 5、每次创建连接后将映射关系进行绑定
func Chat(ctx *gin.Context) {
userId := ctx.DefaultQuery("userId", "")
token := ctx.DefaultQuery("token", "")
userIdInt, _ := strconv.ParseInt(userId, 10, 64)
fmt.Println(token, userId, "=======")
isValied := checkToken(userIdInt, token)
var upGrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
// 根据判断token的方法来鉴权,如果没token就返回false
return isValied
},
}
//升级get请求为webSocket协议
conn, err := upGrader.Upgrade(ctx.Writer, ctx.Request, nil)
if err != nil {
fmt.Println("websocket连接错误")
return
}
// 绑定到当前节点
node := &Node{
Conn: conn,
DataQueue: make(chan []byte, 50),
GroupSet: set.New(set.ThreadSafe),
}
// 映射关系的绑定
rwLocker.Lock()
clientMap[userIdInt] = node
rwLocker.Unlock()
}
  • 6、创建一个发送消息到管道中
// 将数据推送到管道中
func sendMsg(userId int64, message []byte) {
rwLocker.RLock()
node, isOk := clientMap[userId]
rwLocker.RUnlock()
if isOk {
node.DataQueue <- message
}
}
  • 7、创建一个方法从管道中获取数据发送给前端
// 从管道中获取数据发送出去
func senProc(node *Node) {
for {
select {
case data := <-node.DataQueue:
err := node.Conn.WriteMessage(websocket.TextMessage, data)
if err != nil {
fmt.Println("发送消息失败")
return
}
}
}
}
  • 8、在​​Chat​​方法中使用
func Chat(ctx *gin.Context) {
...
// 连接成功就给当前用户发送一个hello word
sendMsg(userIdInt, []byte("hello word"))
// 发送数据给客户端
go senProc(node)
}
  • 9、接收客户端消息转发给另外一个用户
// 接收客户端数据
func recvProc(node *Node) {
for {
_, data, err := node.Conn.ReadMessage()
if err != nil {
fmt.Println("接收数据失败", err)
return
}
// 将数据处理转发给对应的人
dispatch(data)
}
}

// 分发数据
func dispatch(data []byte) {
type Message struct {
UserId int64 `json:"userId"`
Msg string `json:"msg"`
}
fmt.Println("接收到的数据", string(data))
// 解析出数据
message := Message{}
err := json.Unmarshal(data, &message)
if err != nil {
fmt.Println("解析数据失败:", err.Error())
return
}
fmt.Println("解析的数据为:", message)
// 发送数据
sendMsg(message.UserId, data)
}
  • 10、使用接收客户端数据的方法
func Chat(ctx *gin.Context) {
...
// 连接成功就给当前用户发送一个hello word
sendMsg(userIdInt, []byte("hello word"))
// 发送数据给客户端
go senProc(node)
// 接收客户端的数据
go recvProc(node)
}
  • 11、定义一个对外的方法(比如在别的接口中要发送数据到​​websocket​​中)
func SendMessage(userId int64, message interface{}) {
str, _ := json.Marshal(message)
sendMsg(userId, str)
}

三、消息群聊的使用

常见的场景有群聊,一个后台用户要给自己的顾客推送促销消息,这里就举例用后台给顾客推送促销消息

  • 1、根据当前连接的用户​​id​​来加入对应的群聊
// addGroupByAccountId 加入群聊
func AddGroupByAccountId(userId, groupId int64) {
fmt.Println(userId, "加入群聊")
rwLocker.Lock()
node, isOk := FrontClientMap[userId]
if isOk {
node.GroupSet.Add(groupId)
fmt.Println("加入群聊成功")
}
rwLocker.Unlock()
}
  • 2、在连接的时候加入群聊
func Chat(ctx *gin.Context) {
...
// 根据当前用户的id来加入群组
AddGroupByAccountId(userIdInt)
...
}
  • 3、推送消息,循环连接的​​Map​​判断如果当前用户在这个群聊里面就发送数据到管道中
// 2.websocket推送消息到h5群端
for _, v := range frontWs.FrontClientMap {
data := gin.H{
"messageType": int64(messageDto.MessageType),
"title": messageDto.Title,
"content": messageDto.Content,
}
if v.GroupSet.Has(int64(accountId)) {
msg, _ := json.Marshal(data)
v.DataQueue <- msg
}
}
  • 4、处理群聊过程中临时加入群聊的,直接在加入的时候调用加入群组的方法就可以