最近接到一个业务需求, 需要做一个聊天信息的实时展示的界面, 这就需要和服务器端建立 webSocket 连接, 从而实现数据的实时获取和视图的实时刷新. 在此将我的实现记录下来, 希望可以给有同样需求的人一些帮助. 废话少说, 下面我就来讲一下我的实现过程:
前提
要进行文章中的代码的测试, 需要服务端端开发人员配合你, 提供相关的通信接口. 来完成客户端和服务端的通信. 实现通信, 我们需要用到另个模块 https://github.com/sockjs/sockjs-client 模块和 stomjs https://github.com/jmesnil/stomp-websocket 模块, 接下来我会先对这两个模块做一个简单的介绍.
稍后我会写一篇使用 NodeJS+SockJS 实现视屏弹幕的功能的文章, 敬请期待~
关于实时通信
实现实时通信, 我们通常有三种方法:
ajax 轮询 ajax 轮询的原理非常简单, 让浏览器每隔几秒就像服务器发送一个请求, 询问服务器是否有新的信息.
http 长轮询 长轮询的机制和 ajax 轮询差不多, 都是采用轮询的方式, 不过才去的是阻塞模型 (一直打电话, 没收到就不挂电话), 也就是说, 客户端发起链接后, 如果没有消息, 就一直不返回 response 给客户端. 知道有新的消息才返回, 返回完之后, 客户端再此建立连接, 周而复始.
WebSocket WebSocket 是 html5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议. 在 WebSocket API 中, 浏览器和服务器只需要做一个握手的动作, 然后, 浏览器和服务器之间就形成了一条快速通道. 两者之间就直接可以数据互相传送, 不需要繁琐的询问和等待. 从上面的介绍很容易看出来, ajax 轮询和长轮询都是非常耗费资源的, ajax 轮询需要服务器有很快的处理速度和资源, http 长轮询需要有很高的并发, 也就是同时接待客户的能力. 而 WebSocket, 只需要经过一次 HTTP 请求, 就可以与服务端进行源源不断的消息收发了.
sockjs-client
https://github.com/sockjs/sockjs-client 是从 SockJS 中分离出来的用于客户端使用的通信模块. 所以我们就直接来看看 SockJS. SockJS 是一个浏览器的 JavaScript 库, 它提供了一个类似于网络的对象, SockJS 提供了一个连贯的, 跨浏览器的 JavaScriptAPI, 它在浏览器和 Web 服务器之间创建了一个低延迟, 全双工, 跨域通信通道. 你可能会问, 我为什么不直接用原生的 WebSocket 而要使用 SockJS 呢? 这得益于 SockJS 的一大特性, 一些浏览器中缺少对 WebSocket 的支持, 因此, 回退选项是必要的, 而 Spring 框架提供了基于 SockJS 协议的透明的回退选项. SockJS 提供了浏览器兼容性, 优先使用原生的 WebSocket, 如果某个浏览器不支持 WebSocket,SockJS 会自动降级为轮询.
stomjs
STOMP(Simple Text-Orientated Messaging Protocol) 面向消息的简单文本协议; WebSocket 是一个消息架构, 不强制使用任何特定的消息协议, 它依赖于应用层解释消息的含义. 与 HTTP 不同, WebSocket 是处在 TCP 上非常薄的一层, 会将字节流转化为文本 / 二进制消息, 因此, 对于实际应用来说, WebSocket 的通信形式层级过低, 因此, 可以在 WebSocket 之上使用 STOMP 协议, 来为浏览器 和 server 间的 通信增加适当的消息语义.
STOMP 与 WebSocket 的关系:
HTTP 协议解决了 web 浏览器发起请求以及 web 服务器响应请求的细节, 假设 HTTP 协议不存在, 只能使用 TCP 套接字来编写 web 应用, 你可能认为这是一件疯狂的事情;
直接使用 WebSocket(SockJS) 就很类似于使用 TCP 套接字来编写 web 应用, 因为没有高层协议, 就需要我们定义应用间发送消息的语义, 还需要确保连接的两端都能遵循这些语义;
同 HTTP 在 TCP 套接字上添加请求 - 响应模型层一样, STOMP 在 WebSocket 之上提供了一个基于帧的线路格式层, 用来定义消息语义.
代码实现
代码中除了最基本的连接, 还设置了一个定时器, 每隔十秒发送一条数据到服务器端, 如果发生错误, catch 这个错误, 重新建立连接.
- // 安装并引入相关模块
- import SockJS from 'sockjs-client';
- import Stomp from 'stompjs';
- export default {
- data() {
- return {
- dataList: []
- };
- },
- mounted:function(){
- this.initWebSocket();
- },
- beforeDestroy: function () {
- // 页面离开时断开连接, 清除定时器
- this.disconnect();
- clearInterval(this.timer);
- },
- methods: {
- initWebSocket() {
- this.connection();
- let self = this;
- // 断开重连机制, 尝试发送消息, 捕获异常发生时重连
- this.timer = setInterval(() => {
- try {
- self.stompClient.send("test");
- } catch (err) {
- console.log("断线了:" + err);
- self.connection();
- }
- }, 5000);
- },
- removeTab(targetName) {
- console.log(targetName)
- },
- connection() {
- // 建立连接对象
- this.socket = new SockJS('http://xxxxxx:8089/ws');// 连接服务端提供的通信接口, 连接以后才可以订阅广播消息和个人消息
- // 获取 STOMP 子协议的客户端对象
- this.stompClient = Stomp.over(this.socket);
- // 定义客户端的认证信息, 按需求配置
- var headers = {
- login: 'mylogin',
- passcode: 'mypasscode',
- // additional header
- 'client-id': 'my-client-id'
- };
- // 向服务器发起 websocket 连接
- this.stompClient.connect(headers,(frame) => {
- this.stompClient.subscribe('/topic/chat_msg', (msg) => { // 订阅服务端提供的某个 topic
- consolel.log(msg.body); // msg.body 存放的是服务端发送给我们的信息
- });
- }, (err) => {
- });
- },
- disconnect() {
- if (this.stompClient != null) {
- this.stompClient.disconnect();
- console.log("Disconnected");
- }
- }
- }
- };
来源: https://juejin.im/post/5b7fd02d6fb9a01a196f6276