设计一个即时聊天功能
目录
websocket
要实现一个即时聊天概念,首先需要实现一个全双工的通道,让通信双方能够及时的收到对方所发送的消息。 在过去,这通常是通过http轮询实现的,即设置一个定时器,每隔一段时间就发送一个请求获取最新数据,在这一发一收的过程中就完成了双向通讯。但是这种方法最大的弊端在于及时性,消息可能会无法及时的发送给对方,当然这可以通过缩短定时器间隔时间,但是这样做又会导致性能问题。
为了解决这个问题,HTML5 定义了 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。Websocket是一种全双工协议,使用 ws 或 wss 的统一资源标志符(URI),其中 wss 表示使用了 TLS 的 Websocket。websocket和http一样位于应用层,并且websocket的建立依赖于http,具体流程是:
客户端发送Get请求,并在请求头中设置
Upgrade: websocket
;Connection: Upgrade
;来告诉服务器将转换成websocket协议来通讯。服务器返回转状态码101的响应,并转换成websocket协议
双方使用ws协议来通信
实现一个websocket server
在nodejs中实现websocket,最简单的方法是使用相关的websocket库,常用的库有socket.io和ws,socket.io相对来说比较重量级一点,ws相对轻量一些,当然最重要的是socket.io实现的websocket,需要客户端也使用对应的socket-client包,这就显得太笨重了,而ws则可以在客户端中使用原生Websocket API。
首先我们安装ws
简单示例
用户权限验证
ws连接的建立依靠http,因此我们可以在http upgrade阶段进行身份鉴定,如果用户权限不够则不转换成ws协议,返回401。
这里要注意,ws的noServer要设置为true,并且host、port和noServer三个参数只能设置一个。“noServer”模式的作用是将 WebSocket 服务器与 HTTP/S 服务器完全分离。例如,这使得在多个 WebSocket 服务器之间共享单个 HTTP/S 服务器成为可能。
wss
wss协议相当于https协议,其实就是使用了 TLS 的 Websocket。使用wss也比较简单,只需要在ws基础上提供证书密钥即可。
广播
ws可以将消息广播到所有已经建立ws连接的客户端。
异常断线处理——心跳机制
在网络中,由于各种因素的影响,ws连接可能会断开,并且这种断开可能是客户端和服务器无感的,这就导致了在双方不知情的情况下仍然进行通信,这不仅浪费资源,还导致通讯双发无法接收对方的消息。
解决异常断线的方式是使用心跳机制,所谓的心跳机制是每隔一段时间就发送一个空消息(ping)来确认ws连接是否正常,对方收到ping后返回一个空数据的响应(pong),如果能正常收到pong,就说明ws连接正常,否则就证明ws连接异常,需要重新连接。
具体的做法是首先在每条ws上加上isAlive标记,该标记的作用是表明该条ws连接是否存活。然后每次发送ping前将isAlive设置为false,收到pong时将isAlive重新设置为true,假如第二次发送ping时isAlive是false,说明上个周期没有正常接收到pong,说明该条ws连接已经失效,则进行断线处理。
stomp
Stomp全称Simple Text Oriented Messaging Protocol,即简单文本定向消息协议。它提供一种特定的格式,以便Stomp客户端和Stomp服务器进行通讯。stomp支持文本,同时也支持二进制。
stomp帧
stomp发送的消息称为帧,帧由三部分组成: command、headers和body。这和http协议有些类似。
command :字符串类型 针的名称。例如“CONNECT”、“SEND”、“SUBSCRIBE”等。
headers : JavaScript对象,有content-length、content-type等字段。
body: 可以是二进制,也可以是文本。
command和headers总是会被定义,但是headers和body可以为空。body和headers之间通过一个空行来分隔。
stomp客户端
对于stomp客户端来说,它会扮演两种角色的其中一种:
生产者,发送消息到指定地址。
消费者,通过发送subscribe帧进行消息订阅,当消费者发送消息到该订阅地址后,订阅该地址的其他消费者接收消息。
客户端在这两个角色中互相转换,这种订阅机制使得Websocket可以很方便地实现点对点通信和广播通讯。
实现点对点通讯和广播通讯的流程
客户端建立websocket连接
客户端获取消息订阅地址。
客户端作为消费者,通过stomp进行订阅(subsribe)消息地址,并定义接收消息的回调函数
客户端作为生产者,发送消息到指定地址,在消息body中指定消息接收者的id,如果没有指定接收者id,则该地址的所有订阅者都会收到该消息。
调试
可以在debug中查看stomp发送或者接收的是什么
常用的Websocket API
事件
error
message
close
open
方法
close
send
属性
[WebSocket.readyState](https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/readyState)
:当前的链接状态。0:connecting、1:open、2:closing、3:closed
[WebSocket.url](https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/url)
:WebSocket 的绝对路径
[WebSocket.binaryType](https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/binaryType):
使用二进制的数据类型连接。
[WebSocket.bufferedAmount](https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/bufferedAmount)
:只读未发送至服务器的字节数。
[WebSocket.extensions](https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/extensions)
服务器选择的扩展。
踩过的坑
异常断线处理
所谓的异常断线,是指由于各种因素影响,导致ws连接异常断开,并且这种异常断开客户端和服务器都无法感知到。
这个问题是在微信小程序中出现,当微信小程序进入后台5s后,微信会将该小程序挂起,这时就会断开ws连接,最重要的是这种断开操作服务器和前端都无法感知,因此导致了消息发不出去的问题。
处理异常断线的方案是心跳机制。
心跳机制前面有讲过,所谓的心跳条件机制就是每隔一段时间就送一个空消息给对方,如果收到确认消息就表示ws连接有效,否则就证明ws连接失效,此时需要重新建立连接。
如何确保消息成功发送给了对方
stomp客户端接收到对方消息时,会自动进行应答(ack),这有些类似TCP协议,通过这种应答的方式后端能够判断消息是否正确地传递给了接收方,然后再将这一结果返回给发送方。
具体是思路是后端一旦在一定时间内没有接收到ack确认,就发送一个error事件通知发送方。
参考
ws文档: ws/ws.md at master · websockets/ws (github.com)
websocket api: https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket
websocket介绍: 一篇吃透WebSocket:概念、原理、易错常识、动手实践
stomp介绍: STOMP Over WebSocket (jmesnil.net)
stomp官网:stomp.github.io
stompjs: StompJs Family
最后更新于