设计一个即时聊天功能

目录

websocket

要实现一个即时聊天概念,首先需要实现一个全双工的通道,让通信双方能够及时的收到对方所发送的消息。 在过去,这通常是通过http轮询实现的,即设置一个定时器,每隔一段时间就发送一个请求获取最新数据,在这一发一收的过程中就完成了双向通讯。但是这种方法最大的弊端在于及时性,消息可能会无法及时的发送给对方,当然这可以通过缩短定时器间隔时间,但是这样做又会导致性能问题。

为了解决这个问题,HTML5 定义了 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。Websocket是一种全双工协议,使用 ws 或 wss 的统一资源标志符(URI),其中 wss 表示使用了 TLS 的 Websocket。websocket和http一样位于应用层,并且websocket的建立依赖于http,具体流程是:

  1. 客户端发送Get请求,并在请求头中设置Upgrade: websocket;Connection: Upgrade;来告诉服务器将转换成websocket协议来通讯。

  2. 服务器返回转状态码101的响应,并转换成websocket协议

  3. 双方使用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客户端来说,它会扮演两种角色的其中一种:

  1. 生产者,发送消息到指定地址。

  2. 消费者,通过发送subscribe帧进行消息订阅,当消费者发送消息到该订阅地址后,订阅该地址的其他消费者接收消息。

客户端在这两个角色中互相转换,这种订阅机制使得Websocket可以很方便地实现点对点通信和广播通讯。

实现点对点通讯和广播通讯的流程

  1. 客户端建立websocket连接

  2. 客户端获取消息订阅地址。

  3. 客户端作为消费者,通过stomp进行订阅(subsribe)消息地址,并定义接收消息的回调函数

  4. 客户端作为生产者,发送消息到指定地址,在消息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

最后更新于