这篇文章主要是从前端/客户端的角度分享下IM的通用模块、接入云信的方法、以及遇到的问题。
先想一下我们要什么基础的功能?
比如聊天会话列表、群聊、一对一聊天、群管理、个人名片管理、好友申请与审核、黑名单、敏感词屏蔽、客服机器人、账号新建与登陆、图片\语音\表情聊天、消息撤回、消息已读。
从客户端的角度上看,可能需要的技术有: 长连接&短连接、消息存储与销毁、音/视频文件的上传和下载、内存管理
东西有点多,但是最基础的模型是。有个聊天会话列表、历史消息列表、能发送IM消息。一个简单的消息的定义可以是这样的。
再简化一点,维护聊天会话列表、历史消息列表,向客户端提供增删改查的接口。一个 60分的 IM功能就算做完了。
长连接&短连接都是客户端向服务端通信的手段。但是考虑到服务端的负载、效率等等因素,IM通常在服务端和客户端保持一条长链接,方便服务端向客户端推送消息。
通常长链接和短链接可以配合使用。比如发送消息,可以同时支持长链接和短链接发送。
(图片和音频,可以使用例如七牛云这样的专门做云存储的公司提供的服务。)
web上一般是直接用websocket。websocket我没接过,但是接过小程序的websocket。小程序的websocket有几个关键的方法。
// 发起链接 wx.connectSocket({ url: 'test.php' }) wx.onSocketOpen(function(res){ console.log('WebSocket连接已打开!') }) // 处理链接中断事件 wx.onSocketError(function(res){ console.log('WebSocket连接打开失败,请检查!') }) // 收到消息 wx.onSocketMessage(function(res) { console.log('收到服务器内容:' + res.data) }) // 发送消息 function sendSocketMessage(msg) { if (socketOpen) { wx.sendSocketMessage({ data:msg }) } else { socketMsgQueue.push(msg) } }可以看到,已经很高度抽象了。接起来也不会很麻烦。
客户端自己写的话,可以这样写:
长线程的socket最起码要单开一个线程处理。如何是Android的话,推荐使用HanderThread,它内置了一个消息队列。应用层发消息就直接dispatch进消息线程。可以用NIO处理连接。长链接要有个登陆、握手、加密的过程。举个例子,在链接之后,交换一下密钥,消息都用RC4加密,用加密之后的消息登陆。(这里要服务端的同学参与制定下细节协议)前后端可以统一用protobuf,抽象数据模型。处理好进程保活&代码健壮性的的东西。通常我们看到IM就说有工作量,其实是上面一部分的东西不少(没有做过的话)。 但是这次我们使用了云信,工作量就只是接入的工作量而已了。
虽然是使用云信,但是上面的有些关键点的东西,还是需要前端这边接入,比如登陆的账号&token,比如界面的消息展示和功能按钮。云信只是提供了一个基础的JS库,它把协议、数据结构、消息存储都做了,提供一堆简单的借口给前端使用。
看点代码细节吧:)
this.nim = SDK.NIM.getInstance({ debug: false, appKey: appKey, account: account, token: token, syncSessionUnread: true, autoMarkRead: true, db: true, onconnect: () => {}, onsessions: sessions => { this.session.onSessions(sessions); }, onmyinfo: user => { this.chat.onUpdateMyInfo(user); }, onpushevents: event => { this.session.onPushEvents(event); }, onupdatesession: session => { this.session.onUpdateSession(session); }, onmsg: msg => { this.chat.onMsg(msg); }, onsyncdone: () => {}, onwillreconnect: obj => {}, ondisconnect: obj => {}, onerror: error => { if (error) console.log(error); } }); nim.getHistoryMsgs({ scene: data.scene, to: data.to, limit: this.defaultFirstLimit, done: (error, obj) => { if (error) console.log('getHistoryMsgs error', error); if (obj) this._resetHistoryMsgs(obj); } }); nim.sendText({ scene: 'p2p', to: to, text: text, done: (error, msg) => { if (error) console.log('sendText fail', error, msg); // simple display the the input result. no resend. if (this.taInfo && to === this.taInfo.to) this._insert(msg); } });先停一下,想想我们需要什么?
我们需要给云信我们这边的账号标示、需要获取会话列表的信息、需要获取历史消息、需要获取帐号的在线状态、需要发送消息、需要发送文件和图片。界面的逻辑我们想自定义。
云信能支持到上面这些要求么?可以,上面每个要求都是一个函数。界面怎么显示,云信不管。
了解到这些之后,看下接入文档或者咨询下云信的小伙伴就可以了做接入了。
接入文档:https://dev.yunxin.163.com/docs/product/IM即时通讯/SDK开发集成/Web开发集成/概要介绍 SDK下载:https://yunxin.163.com/im-sdk-demo?from=nim&clueFrom=nim (PS:SDK中有一份细致的API文档,比较有价值)
问题在上一小节已经解决了一大半了,但是具体到细节上,我做了一些尝试。
细节见这篇博客: https://blog.csdn.net/yeshennet/article/details/88880252
这次的坑主要是删除聊天对象之后,云信返回的会话信息有个字段丢失了。造成聊天列表多了一个莫名其妙的会话。可以这样处理(拦截掉它)。
SDK.NIM.getInstance({ onsessions: sessions => { let realSessions = filterSessions(sessions); } } function filterSessions(sessions){ if(sessions == null)return sessions; // 修正云信获取会话列表字段丢失的问题 // 解决办法:直接丢弃数据不完整的session sessions = sessions.filter(item => { return item.to; }); let exist = false; for (let i = 0; i < sessions.length; i++) { sessions[i].undeletable = sessions[i].to === this.guestServiceId; if (sessions[i].undeletable) exist = true; } if (!exist) { sessions.push({ id: 'p2p-' + this.guestServiceId, scene: 'p2p', to: this.guestServiceId, unread: 0, undeletable: true }); } return sessions; }这篇文章先科普了背景知识,在对背景知识有了解的基础上,介绍了云信的功能,整理了几个通用的web上界面处理的问题。