💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## **websocket** > WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。 现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种方式是让浏览器不断的向服务器发送请求,因为HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。 ![](https://img.kancloud.cn/37/92/379278605a69626b3915c6829b1a2cb5_834x444.png) Websocket的处理方式是让浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。 当你获取 Web Socket 连接后,你可以通过`send()`方法来向服务器发送数据,并通过`onmessage`事件来接收服务器返回的数据。 ## **聊天功能原理** 由客户端发送Http(`Ajax`)请求,请求后端TP框架。通过TP框架来鉴定客户端的身份(验证Token或数据合法性),GatewayWorker只是实现了一个单向推送功能,不会去接收用户的任何信息(除了绑定之外),复杂的逻辑使用TP框架处理的。 ![](https://box.kancloud.cn/238d9394e0bbe3e03fe72cc73b3f61b6_439x352.png) > GatewayWorker是通过`sendToClient`接口发送某一条信息到客户端 ``` void Gateway::sendToClient(string $client_id, string $send_data); ``` ## **前端聊天原理流程** ![](https://img.kancloud.cn/7c/43/7c4375995eeeac44e10685f411729d68_670x453.jpg) > 连接websocket需要socket地址,因为在服务器上配置了wss,所以此处域名后面不再接入端口号,而是已经配置好的wss ``` // websocket地址 websocketUrl:"wss://ltkf.gxyuchi.com/wss", ``` ## 配置websocket的信息 ``` // socket地址 url:Config.websocketUrl, // 连接状态 IsOpen:false, // SocketTask SocketTask:false, // 是否上线(会员id绑定客户端id,验证用户身份,通过则绑定) IsOnline:false, // 当前聊天对象(进入聊天页面获取) CurrentToUser:{ userid:0, // 通过判断userid是否为0,当前用户处在什么场景下 username:"", userpic:"" ``` ## 连接和关闭websocket 连接websocket,连接时使用`connectSocket`方法需要传入`url`路径和`complete`。 传入`complete`的目的是为了返回一个[socketTask](https://uniapp.dcloud.io/api/request/socket-task)对象 ``` this.SocketTask = uni.connectSocket({ url:this.url, complete: (e)=> { }, }); ``` ## 监听关闭websocket 关闭websocker连接 ``` // 关闭连接 Close(){ if (this.IsOpen){ this.SocketTask.close(); return uni.removeTabBarBadge({ index:Config.TabbarIndex }); } }, ``` 设置连接状态为false,并清空`socketTask` ``` this.IsOpen = false; this.SocketTask = false; ``` 当连接成功时,进行用户绑定,并判断用户绑定结果 ``` $http.post('/chat/bind',{ type:'bind', client_id:client_id },{ token:true }).then(data=>{ let [err,res] = data; console.log(res) // 错误处理 if (!$http.errorCheck(err,res)) { // 退出登录,重新链接等处理 return; } // 成功 return this.resultUserBind(res.data.data); }); ``` 当通过判断`bind`来确认用户绑定成功时,将状态改为上线并初始化读取未读信息方法 判断条件 ``` res.status && res.type == 'bind' ``` 调用方法 ``` // 获取总未读数,并且渲染到tabbar的badge this.initTabbarBadge(); // 获取未读信息 this.getChatMessages(); ``` 绑定失败则断开连接 ``` uni.showToast({ title: res.msg, icon:"none" }); this.SocketTask.close(); ``` ## **处理未读信息** ``` // 初始化tabbarBadge initTabbarBadge(){ // 获取总未读数 let noreadnum = uni.getStorageSync('noreadnum'+User.userinfo.id); this.__UpdateTabbarBadge(noreadnum); }, ``` ## 存储到chatdetail 存储到chatdetail的目的是保存用户与用户之间的历史聊天记录,通过`issend `来判断场景是否是发送信息场景,当用户发送信息成功后,开始保存历史记录 ``` res,issend = false ``` 保存历史记录时,要考虑旧数据和新数据的同时保存 ``` let userid = issend ? this.CurrentToUser.userid : res.from_id; // 获取旧数据( chatdetail_[当前用户id]_[聊天对象id] ) let list = uni.getStorageSync('chatdetail_'+User.userinfo.id+'_'+userid); list = list ? JSON.parse(list) : []; // 追加 list.push(this.__format(res,{ type:"chatdetail", isme:issend, olddata:list, })); ``` ## **更新chatlist** 更新chatlist的目的是将当前会话置顶,修改chatlist中当前会话的data和time显示。值得注意的是要进行一个判断,判断当前会话是否存在,存在:将当前会话置顶;不存在:追加至头部 ``` // 判断是否已存在该会话,存在:将当前会话置顶;不存在:追加至头部 let index = chatlist.findIndex((item)=>{ return item.userid == res.to_id || item.userid == res.from_id; }) // 不存在 if (index == -1) { let obj = this.__format(res,{ type:"chatlist" }); // 忽略本人发送 if (res.from_id !== User.userinfo.id) { obj.noreadnum = 1; } chatlist.unshift(obj); }else{ // 存在:将当前会话置顶,修改chatlist中当前会话的data和time显示 chatlist[index].data = res.data; chatlist[index].type = res.type; chatlist[index].time = res.time; // 当前聊天对象不是该id,未读数+1(排除本人发送消息) if (res.from_id !== User.userinfo.id && this.CurrentToUser.userid !== chatlist[index].userid) { chatlist[index].noreadnum++; } // 置顶当前会话 chatlist = this.__toFirst(chatlist,index); } ``` ``` // 数组置顶 __toFirst(arr,index){ if (index != 0) { arr.unshift(arr.splice(index,1)[0]); } return arr; } ``` ## **发送消息** 发送消息的格式分为不同种情况 ``` // 发送的格式 let senddata = this.__format(data,{type:"send"}); ``` 发送时,要对消息的数据字段进行一个转换 ``` return { to_id:this.CurrentToUser.userid, from_id:User.userinfo.id, from_username:User.userinfo.username, from_userpic:User.userinfo.userpic, type:data.type, data:data.data, time:new Date().getTime() } ``` 之后将已发消息进行历史记录保存和置顶操作 ``` // 存储到chatdetail this.__UpdateChatdetail(senddata,true); // 存储到chatlist(将当前会话置顶,修改chatlist中当前会话的data和time显示) this.__UpdateChatlist(senddata); // 发送到服务器(交由页面去做) return senddata; ``` ## 读取当前会话 读取当前会话时,通过用户发送消息的最后时间来拿到最新的消息 ``` // 拿到当前会话的索引 let index = chatlist.findIndex((value)=>{ return value.userid == item.userid; }); let oldnoreadnum = chatlist[index].noreadnum; // 如果会话存在 if (index !== -1) { chatlist[index].noreadnum = 0; // 存储 uni.setStorage({ key:"chatlist"+User.userinfo.id,data:JSON.stringify(chatlist) }) // 更新tabbar的badge this.__UpdateNoReadNum({ type:"read",num:oldnoreadnum }); } ```