使用WebSocket完成消息推送功能
Kale

学习了一下WebSocket,并用WebSocket对项目添加了消息推送功能.

WebSocket

WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议.
传统的HTTP协议,客户端是主动的,服务端是被动的,永远是一个request对应着一个response,因为HTTP是无状态的,所以服务端无法主动推送消息给客户端.虽然在HTTP1.1中出现了keep-alive,允许在一个HTTP连接中有多个request和response,但是还是一个request对应一个response,服务端还是被动的,那如果要完成推送功能,就比较麻烦.

ajax是可以实现近似消息推送功能的,叫ajax轮询,其实就是客户端每隔一定时间自动向服务端发起请求,所以核心还是request和response.

还有一种方法叫long poll,和ajax轮询的原理其实差不多,ajax轮询是设定了一定时间请求一次,而long poll是一直请求,没收到回复就不停循环.

可以看到这两种方法对资源的消耗都是非常严重的,而且HTTP还是无状态协议.HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分.所以有了WebSocket,WebSocket可以实现服务端的主动.并且WebSocket是有状态的,只需要建立一次连接,从HTTP协议升级到WebSocket协议之后,就成功建立连接了,然后可以不断传输消息.
从这里的图片可以看出此时协议已经从HTTP切换到WebSocket了.
Avatar

WebSocket因为是全双工的,所以客户端和服务端都可以自由通信,并且没有HTTP协议那么多固定的头,所以效率非常高.

使用WebSocket

前端是vue.js,本来在网上看到有个vue.js有个插件vue-websocket,但是试了不知道为什么失败了,应该是后端的配置问题,后来直接换的原始的方法,其实也很简单,流程就是创建一个WebSocket对象,然后定义WebSocket的onopen, onerror, onmessage, onclose事件.初始化一个连接,后端接收这个连接请求,然后就可以开始传输消息了.

后端是django,django可以使用dwebsocket来实现连接,需要用到@accept_websocket装饰器,这个装饰器表明这个视图函数可以接收WebSocket的连接请求,另外WebSocket是只可以传送字符串的,不可以传送对象,所以需要先加工一下.

消息推送的内容是需要实现别人点了一个赞,踩,回复等,被互动方需要有实时提示,关于记录新的点赞数等的内容是用redis实现了,那么只需要用WebSocket在服务端定时检查是否有新的点赞,有新的点赞就推送给指定客户端即可.

Tcp心跳包

当WebSocket连接后,可能会存在断开连接的问题,如果没有处理措施,则客户端会丧失推送功能,所以需要引入心跳包机制,也就是每隔一段时间客户端向服务端发送一个自定义包,如果服务端响应了,则重置等待时间,如果服务端推送了消息,也要重置等待时间.如果服务端没有对心跳包发起回应,则客户端重新申请连接服务端.思路很简单,下面是代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
data() {
lockReconnect: false,//是否真正建立连接
timeout: 30 * 1000,//30秒一次心跳
timeoutObj: null,//心跳倒计时
serverTimeoutObj: null,//心跳倒计时
timeOutNum: null,//断开 重连倒计时
}
init: function () {
if (typeof (WebSocket) === "undefined") {
alert("您的浏览器不支持socket");
} else {
this.socket = new WebSocket("ws://127.0.0.1:8000/pushMsg");
this.socket.onopen = this.open;
this.socket.onerror = this.error;
this.socket.onmessage = this.getMessage;
this.socket.onclose = this.close;
}
},

open: function () {
console.log("success");
this.start();
},
error: function () {
console.log("failed");
this.reconnect();
},
getMessage: function (msg) {
let res = JSON.parse(msg.data);
if (res.newReplyNum !== this.notice.newReplyNum) {
this.notice.newReplyNum = res.newReplyNum;
}
if (res.newLikeNum !== this.notice.newLikeNum) {
this.notice.newLikeNum = res.newLikeNum;
}
if (res.newDisLikeNum !== this.notice.newDisLikeNum) {
this.notice.newDisLikeNum = res.newDisLikeNum;
}
this.reset();
},
send: function (msg) {
this.socket.send(msg);
},
close: function () {
console.log("closed");
this.reconnect();
},
reconnect() {//重新连接
let that = this;
if (that.lockReconnect) {
return;
}
that.lockReconnect = true;
that.timeOutNum = setTimeout(function () {
that.init();
that.lockReconnect = false;
}, 5000);
},
reset() {
let that = this;
clearTimeout(that.timeoutObj);
clearTimeout(that.serverTimeoutObj);
that.start();
},
start() {
let self = this;
self.timeoutObj && clearTimeout(self.timeoutObj);
self.serverTimeoutObj && clearTimeout(self.serverTimeoutObj);
self.timeoutObj = setTimeout(function () {
if (self.socket.readyState == 1) {
self.socket.send("heartCheck");
} else {
self.reconnect();
}
self.serverTimeoutObj = setTimeout(function () {
self.socket.close();
}, self.timeout);

}, self.timeout);
},

Avatar
从图里可以看到服务端发送给客户端的消息以及客户端发送的heartCheck心跳包.


更新一下, 关于nginx和uWSGI如何配置WebSocket.

nginx方面:
nginx.conflocation /中添加:

1
2
3
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;

uWSGI方面:
需要在uwsgi.ini文件中添加:

http-websockets = true

  • 本文标题:使用WebSocket完成消息推送功能
  • 本文作者:Kale
  • 创建时间:2020-03-19 16:12:04
  • 本文链接:https://kalew515.com/2020/03/19/使用WebSocket完成消息推送功能/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!