############################ //app/controller/chat.go ############################ ...... //发送逻辑 func sendproc(node *Node) {for {select {case data := <-node.DataQueue:err := node.Conn.WriteMessage(websocket.TextMessage, data)if err != nil {log.Println(err.Error())return}}} }//接收逻辑 func recvproc(node *Node) {for {_, data, err := node.Conn.ReadMessage()if err != nil {log.Println(err.Error())return}dispatch(data)//todo对data进一步处理fmt.Printf("recv<=%s", data)} } ...... //后端调度逻辑处理 func dispatch(data []byte) {msg := Message{}err := json.Unmarshal(data, &msg)if err != nil {log.Println(err.Error())return}switch msg.Cmd {case CmdSingleMsg:sendMsg(msg.Dstid, data)case CmdRoomMsg:for _, v := range clientMap {if v.GroupSets.Has(msg.Dstid) {v.DataQueue <- data}}case CmdHeart://检测客户端的心跳} }//发送消息,发送到消息的管道 func sendMsg(userId int64, msg []byte) {rwlocker.RLock()node, ok := clientMap[userId]rwlocker.RUnlock()if ok {node.DataQueue <- msg} } ...... 服务端向客户端发送消息逻辑比较简单 , 就是将客户端发送过来的消息 , 直接添加到目标用户 Node 的 Channel 中去就好了 。
通过 WebSocket 的 WriteMessage 就可以实现此功能:
func sendproc(node *Node) {for {select {case data := <-node.DataQueue:err := node.Conn.WriteMessage(websocket.TextMessage, data)if err != nil {log.Println(err.Error())return}}} } 收发逻辑是这样的 , 服务端通过 WebSocket 的 ReadMessage 方法接收到用户信息 , 然后通过 dispatch 方法进行调度:
func recvproc(node *Node) {for {_, data, err := node.Conn.ReadMessage()if err != nil {log.Println(err.Error())return}dispatch(data)//todo对data进一步处理fmt.Printf("recv<=%s", data)} } dispatch 方法所做的工作有两件:
- 解析消息体到 Message 中 。
- 根据消息类型 , 将消息体添加到不同用户或者用户组的 Channel 当中 。
单聊和群聊的区别只是服务端将消息发送给群组还是个人 , 如果发送给群组 , 程序会遍历整个 clientMap , 看看哪个用户在这个群组当中 , 然后将消息发送 。
其实更好的实践是我们再维护一个群组和用户关系的 Map , 这样在发送群组消息的时候 , 取得用户信息就比遍历整个 clientMap 代价要小很多了 。
func dispatch(data []byte) {msg := Message{}err := json.Unmarshal(data, &msg)if err != nil {log.Println(err.Error())return}switch msg.Cmd {case CmdSingleMsg:sendMsg(msg.Dstid, data)case CmdRoomMsg:for _, v := range clientMap {if v.GroupSets.Has(msg.Dstid) {v.DataQueue <- data}}case CmdHeart://检测客户端的心跳} } ...... func sendMsg(userId int64, msg []byte) {rwlocker.RLock()node, ok := clientMap[userId]rwlocker.RUnlock()if ok {node.DataQueue <- msg} } 可以看到 , 通过 Channel , 我们实现用户聊天功能还是非常方便的 , 代码可读性很强 , 构建的程序也很健壮 。下边是笔者本地聊天的示意图:

文章插图

文章插图
⑤发送表情和图片
下边我们再来看一下聊天中经常使用到的发送表情和图片功能是如何实现的吧 。
其实表情也是小图片 , 只是和聊天中图片不同的是 , 表情图片比较小 , 可以缓存在客户端 , 或者直接存放到客户端代码的代码文件中(不过现在微信聊天中有的表情包都是通过网络传输的) 。
下边是一个聊天中返回的图标文本数据:
{ "dstid":1, "cmd":10, "userid":2, "media":4, "url":"/asset/plugins/doutu//emoj/2.gif" } 客户端拿到 URL 后 , 就加载本地的小图标 。聊天中用户发送图片也是一样的原理 , 不过聊天中用户的图片需要先上传到服务器 , 然后服务端返回 URL , 客户端再进行加载 , 我们的 IM 系统也支持此功能 。我们看一下图片上传的程序:
############################ //app/controller/upload.go ############################ func init() {os.MkdirAll("./resource", os.ModePerm) }func FileUpload(writer http.ResponseWriter, request *http.Request) {UploadLocal(writer, request) }//将文件存储在本地/im_resource目录下 func UploadLocal(writer http.ResponseWriter, request *http.Request) {//获得上传源文件srcFile, head, err := request.FormFile("file")if err != nil {util.RespFail(writer, err.Error())}//创建一个新的文件suffix := ".png"srcFilename := head.FilenamesplitMsg := strings.Split(srcFilename, ".")if len(splitMsg) > 1 {suffix = "." + splitMsg[len(splitMsg)-1]}filetype := request.FormValue("filetype")if len(filetype) > 0 {suffix = filetype}filename := fmt.Sprintf("%d%s%s", time.Now().Unix(), util.GenRandomStr(32), suffix)//创建文件filepath := "./resource/" + filenamedstfile, err := os.Create(filepath)if err != nil {util.RespFail(writer, err.Error())return}//将源文件拷贝到新文件_, err = io.Copy(dstfile, srcFile)if err != nil {util.RespFail(writer, err.Error())return}util.RespOk(writer, filepath, "") } ...... ############################ //main.go ############################ func main() {http.HandleFunc("/attach/upload", controller.FileUpload) }
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 如何快速处理mysql连接数占满的问题?
- 快速剥蒜的小窍门
- 淘宝怎样升级最新版本 淘宝如何快速升级
- 7大面试技巧,让你成为“面霸”,快速找到心仪的工作
- 抗癌效果最好的几种茶,四大步骤教你如何简易而快速的冲泡百合花茶
- MySQL如何快速插入数据
- 十种最神奇的快速减肥偏方,揭密11种美容消脂茶饮的配方
- 如何将5G大文件快速传给对方?1个小软件2分钟搞定
- 如何防止买到假货?帮助你快速识别10个著名品牌真伪的技巧
- 淘宝特价红包怎么用 淘宝特价如何快速邀请
