## **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 });
}
```