一、为什么需要websocket?:
前端和后端的交互模式最常见的就是前端发数据请求,从后端拿到数据后展示到页面中。如果前端不做操作,后端不能主动向前端推送数据,这也是http协议的缺陷。
因此,一种新的通信协议应运而生—websocket,他最大的特点就是服务端可以主动向客户端推送消息,客户端也可以主动向服务端发送消息,实现了真正的平等。
websocket其他特点如下:
(1)建立在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
二、vue部分(客户端):
api.js:
export const getWebSocket = () => {
let protocol = document.location.protocol === 'https:' ? 'wss://' : 'ws://'
let url = protocol + document.location.host + '/nms/ws/'
let ws = new WebSocket(url)
ws.onopen = onOpen
ws.onclose = onClose
return ws
}
home.vue
...
...
...
mounted: function() {
this.initWebSocket()
},
...
...
...
```javascript
methods: {
// ws消息处理
initWebSocket: function () {
this.ws = api.getWebSocket();
this.ws.onopen = onOpen
this.ws.onmessage = this.onMessage;
this.ws.onerror = this.websocketonerror;
this.ws.onclose = this.websocketclose;
},
websocketonerror(){//连接建立失败重连
this.initWebSocket();
},
onMessage(evt) {
console.log(evt.data);
let data = JSON.parse(evt.data);
let status = "";
// 根据需求做相应的处理
try {
switch (data.eventid) {
case "aaa":
// 抓包开始成功
...
...
...
break;
case "bbb":
// 抓包开始失败
...
...
...
break;
...
...
...
default:
break;
}
} catch (err) {
console.warn(err);
}
},
websocketclose(e){ //关闭
console.log('断开连接',e);
}
},
三、django部分(服务端):
Django通过dwebsocket实现websocket通信:
view.py
from django.shortcuts import render
import logging
import json
import threading
from dwebsocket.decorators import accept_websocket
import os
from nms_server.utils.myglobal import *
from nms_server.dao.diagnosis import add_capture_file, updata_capture_file
from nms_server.dao.redis.device import get_device_user_moid
from nms_server.rmq import RMQConsumerThread
from django.conf import settings
logger = logging.getLogger('nms.'+__name__)
# 存储连接websocket的用户
# 消费者创建标识
consumer_flag = True
@accept_websocket
def websocket_link(request):
global consumer_flag
# '连接websocket'
# 获取连接
if request.is_websocket:
lock = threading.RLock()#rlock线程锁
try:
lock.acquire()#抢占资源
s = {}
if consumer_flag:
try:
logger.info('init nms_webserver_consumer: %s' %
os.getpid())
RMQConsumerThread(
name='nms_webserver_consumer_{}'.format(os.getpid()),
amqp_url=settings.AMQP_URL,
exchange='nms.webserver.ex',
queue='nms.webserver.q.{}'.format(os.getpid()),
routing_key='nms.webserver.k',
exchange_type='topic'
).start()
consumer_flag = False
except Exception as e:
logger.error(e)
user = getattr(request, 'sso_user', None)
user_moid = ''
if user is not None:
user_moid = user['data']['moid']
else:
logger.error('sso_user in None')
raise ValueError
clients = get_clients()
# 因为同一个账号打开多个页面连接信息是不同的
if clients.get(user_moid) != None:
# 连接信息 键 连接名 值:连接保存
s[str(request.websocket)] = request.websocket
# 已存在的连接信息继续增加
clients[user_moid].update(s)
else:
# 连接信息 键 连接名 值:连接保存
s[str(request.websocket)] = request.websocket
# 新增 用户 连接信息
clients[user_moid] = s
set_clients(clients)
logger.info('pid:%d ,client: %s',os.getpid(),clients)
# 监听接收客户端发送的消息 或者 客户端断开连接
for message in request.websocket:
if not message:
break
else:
client_msg_handler(user_moid,message)
finally:
logger.info('close client connect,user_moid:%s,request.websocket:%s',user_moid,str(request.websocket))
# 通过用户名找到 连接信息 再通过 连接信息 k 找到 v (k就是连接信息)
clients = get_clients()
clients.get(user_moid).pop(str(request.websocket))
if not clients.get(user_moid):
clients.pop(user_moid)
set_clients(clients)
logger.info('client: %s',clients)
#释放锁
lock.release()
# 客户端消息处理
def client_msg_handler(user_moid,msg):
logger.info('[client_msg_handler] msg:%s' ,msg.decode('utf-8'))
# test代码
# import time
# data = {
# 'event':'ack',
# 'text':'hello world'
# }
# for i in range(5):
# data['id'] = i
# send_msg_to_client(user_moid,data)
# time.sleep(1)
pass
# 发送消息
def websocketMsg(client, msg):
# 因为一个账号会有多个页面打开 所以连接信息需要遍历
for cli in client:
b1 = json.dumps(msg).encode('utf-8')
client[cli].send(b1)
'''
@description: 服务端发送消息
@user_moid {str} 用户moid
@msg {json} 消息
@return:
'''
def send_msg_to_client(data):
clients = get_clients()
logger.info('pid:%d, send_msg_to_client(data): %s',os.getpid(),data)
logger.info('pid:%d ,client: %s',os.getpid(),clients)
try:
if data['user_moid'] == '':
data['user_moid'] = get_device_user_moid(data['devid'],data['devtype'])
user_moid = data['user_moid']
if clients[user_moid]:
if data['eventid'] == 'EV_PACKETCAPTURE_STOP_ACK':
# 保存抓包文件到数据库
file_name = data["url"].split("/")[-1]
create_time = data['rpttime'].replace("/", "-").split(':',1)
create_time = create_time[0] + " " + create_time[1]
add_capture_file(data['user_moid'],
data['devid'],
file_name,
data.get('size',0),
create_time)
if data['eventid'] == 'EV_PACKETCAPTURE_UPLOAD_PROGRESS_NTF':
# 更新终端抓包文件信息
file_name = data["url"].split("/")[-1]
if 'size' in data:
updata_capture_file(data['user_moid'],
data['devid'],
file_name,
data['size'])
websocketMsg(clients[user_moid],data)
except BaseException as e:
logger.error(e)
参考:
python websocket Django 实时消息推送
封装websocket请求-----vue项目实战
Django通过dwebsocket实现websocket
官方链接