2小时快速搭建一个高可用的IM系统( 七 )


type Node struct {Conn *websocket.Conn//并行转串行,DataQueue chan []byteGroupSets set.Interface } 【2小时快速搭建一个高可用的IM系统】服务端创建了一个 Map , 将客户端用户 ID 和其 Node 关联起来:
//userid和Node映射关系表 var clientMap map[int64]*Node = make(map[int64]*Node, 0) 接下来是主要的用户逻辑了 , 服务端接收到客户端的参数之后 , 首先校验 Token 是否合法 , 由此确定是否要升级 HTTP 协议到 WebSocket 协议 , 建立长连接 , 这一步称为鉴权 。
//校验token是否合法 islegal := checkToken(userId, token)conn, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {return islegal}, }).Upgrade(writer, request, nil) 鉴权成功以后 , 服务端初始化一个 Node , 搜索该客户端用户所在的群组 ID , 填充到群组的 GroupSets 属性中 。
然后将 Node 节点添加到 ClientMap 中维护起来 , 我们对 ClientMap 的操作一定要加锁 , 因为 Go 语言在并发情况下 , 对 Map 的操作并不保证原子安全:
//获得websocket链接connnode := &Node{Conn:conn,DataQueue: make(chan []byte, 50),GroupSets: set.New(set.ThreadSafe),}//获取用户全部群IdcomIds := concatService.SearchComunityIds(userId)for _, v := range comIds {node.GroupSets.Add(v)}rwlocker.Lock()clientMap[userId] = noderwlocker.Unlock() 服务端和客户端建立了长链接之后 , 会开启两个协程专门来处理客户端消息的收发工作 , 对于 Go 语言来说 , 维护协程的代价是很低的 。
所以说我们的单机程序可以很轻松的支持成千上完的用户聊天 , 这还是在没有优化的情况下 。
...... //开启协程处理发送逻辑go sendproc(node)//开启协程完成接收逻辑go recvproc(node)sendMsg(userId, []byte("welcome!")) ......至此 , 我们的鉴权工作也已经完成了 , 客户端和服务端的连接已经建立好了 , 接下来我们就来实现具体的聊天功能吧 。
④实现单聊和群聊
实现聊天的过程中 , 消息体的设计至关重要 , 消息体设计的合理 , 功能拓展起来就非常的方便 , 后期维护、优化起来也比较简单 。
我们先来看一下 , 我们消息体的设计:
############################ //app/controller/chat.go ############################ type Message struct {Idint64`json:"id,omitempty" form:"id"`//消息IDUseridint64`json:"userid,omitempty" form:"userid"`//谁发的Cmdint`json:"cmd,omitempty" form:"cmd"`//群聊还是私聊Dstidint64`json:"dstid,omitempty" form:"dstid"`//对端用户ID/群IDMediaint`json:"media,omitempty" form:"media"`//消息按照什么样式展示Content string `json:"content,omitempty" form:"content"` //消息的内容Picstring `json:"pic,omitempty" form:"pic"`//预览图片Urlstring `json:"url,omitempty" form:"url"`//服务的URLMemostring `json:"memo,omitempty" form:"memo"`//简单描述Amountint`json:"amount,omitempty" form:"amount"`//其他和数字相关的 } 每一条消息都有一个唯一的 ID , 将来我们可以对消息持久化存储 , 但是我们系统中并没有做这件工作 , 读者可根据需要自行完成 。
然后是 userid , 发起消息的用户 , 对应的是 dstid , 要将消息发送给谁 。还有一个参数非常重要 , 就是 cmd , 它表示是群聊还是私聊 。
群聊和私聊的代码处理逻辑有所区别 , 我们为此专门定义了一些 cmd 常量:
//定义命令行格式 const (CmdSingleMsg = 10CmdRoomMsg= 11CmdHeart= 0 ) Media 是媒体类型 , 我们都知道微信支持语音、视频和各种其他的文件传输 , 我们设置了该参数之后 , 读者也可以自行拓展这些功能 。
Content 是消息文本 , 是聊天中最常用的一种形式 。Pic 和 URL 是为图片和其他链接资源所设置的 。
Memo 是简介 , Amount 是和数字相关的信息 , 比如说发红包业务有可能使用到该字段 。
消息体的设计就是这样 , 基于此消息体 , 我们来看一下 , 服务端如何收发消息 , 实现单聊和群聊吧 。
还是从上一节说起 , 我们为每一个客户端长链接开启了两个协程 , 用于收发消息 , 聊天的逻辑就在这两个协程当中实现 。


推荐阅读