文章目录

  • WebSocket
    • ajax轮询
    • long poll
    • Websocket
  • Channels
    • WSGI
    • ASGI
  • Django中使用
    • 信息交互的周期
  • 前端实现WebSocket
  • 前后端分离项目实现Websocket

WebSocket

在讲Websocket之前,先了解下 long pollajax轮询 的原理。

ajax轮询

ajax轮询的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。

long poll

long poll 其实原理跟 ajax轮询 差不多,都是采用轮询的方式,不过采取的是阻塞模型(一直打电话,没收到就不挂电话),也就是说,客户端发起连接后,如果没消息,就一直不返回Response给客户端。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始。

ajax轮询 需要服务器有很快的处理速度和资源(速度)。long poll 需要有很高的并发,也就是说同时接待客户的能力(场地大小)。

Websocket

WebSocket是一种在单个TCP连接上进行全双工通讯的协议。WebSocket允许服务端主动向客户端推送数据。在WebSocket协议中,客户端浏览器和服务器只需要完成一次握手就可以创建持久性的连接,并在浏览器和服务器之间进行双向的数据传输。

WebSocket的请求头中重要的字段:

  • ConnectionUpgrade:表示客户端发起的WebSocket请求
  • Sec-WebSocket-Version:客户端所使用的WebSocket协议版本号,服务端会确认是否支持该版本号
  • Sec-WebSocket-Key:一个Base64编码值,由浏览器随机生成,用于升级request

WebSocket的响应头中重要的字段:

  • HTTP/1.1 101 Swi tching Protocols:切换协议,WebSocket协议通过HTTP协议来建立运输层的TCP连接
  • ConnectionUpgrade:表示服务端发起的WebSocket响应
  • Sec-WebSocket-Accept:表示服务器接受了客户端的请求,由Sec-WebSocket-Key计算得来

WebSocket协议的优点:

  • 支持双向通信,实时性更强
  • 数据格式比较轻量,性能开销小,通信高效
  • 支持扩展,用户可以扩展协议或者实现自定义的子协议(比如支持自定义压缩算法等)

WebSocket协议的优点:

  • 少部分浏览器不支持,浏览器支持的程度与方式有区别
  • 长连接对后端处理业务的代码稳定性要求更高,后端推送功能相对复杂
  • 成熟的HTTP生态下有大量的组件可以复用,WebSocket较少

WebSocket的应用场景:

  • 即时聊天通信,网站消息通知
  • 在线协同编辑,如腾讯文档
  • 多玩家在线游戏,视频弹幕,股票基金实施报价

Channels

Django本身不支持WebSocket,但可以通过集成Channels框架来实现WebSocket

Channels是针对Django项目的一个增强框架,可以使Django不仅支持HTTP协议,还能支持WebSocketMQTT等多种协议,同时Channels还整合了Djangoauth以及session系统方便进行用户管理及认证。

channels中文件和配置的含义

  • asgi.py:介于网络协议服务和Python应用之间的接口,能够处理多种通用协议类型,包括HTTPHTTP2WebSocket
  • channel_layers:在settings.py中配置。类似于一个通道,发送者(producer)在一段发送消息,消费者(consumer)在另一端进行监听
  • routings.py:相当于Django中的urls.py
  • consumers.py:相当于Django中的views.py

WSGI

WSGI(Python Web Server Gateway Interface):为Python语言定义的Web服务器和Web应用程序或者框架之间的一种简单而通用的接口。

ASGI

ASGI(Asynchronous Web Server Gateway Interface):异步网关协议接口,一个介于网络协议服务和Python应用之间的标准接口,能够处理多种通用的协议类型,包括HTTPHTTP2WebSocket

WSGI是基于HTTP协议模式的,不支持WebSocket,而ASGI的诞生则是为了解决Python常用的WSGI不支持当前Web开发中的一些新的协议标准。同时,ASGI对于WSGI原有的模式的支持和WebSocket的扩展,即ASGIWSGI的扩展。

Django中使用

1、安装channels,要注意版本的对应,在channels官网中可以得到对应的django版本

pip install channels==2.1.7

2、修改settings.py文件,

# APPS中添加channels
INSTALLED_APPS = ['django.contrib.staticfiles',... ...'channels',
]
# 指定ASGI的路由地址
ASGI_APPLICATION = 'webapp.routing.application' #ASGI_APPLICATION 指定主路由的位置为webapp下的routing.py文件中的application

3、setting.py的同级目录下创建routing.py路由文件,routing.py类似于Django中的url.py指明websocket协议的路由

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import chat.routing
# 第一种设置的方法
from channels.security.websocket import AllowedHostsOriginValidator
application = ProtocolTypeRouter({# 普通的HTTP协议在这里不需要写,框架会自己指明'websocket': AllowedHostsOriginValidator(AuthMiddlewareStack(URLRouter(# 指定去对应应用的routing中去找路由chat.routing.websocket_urlpatterns)),)
})# 第二种设置的方法,需要手动指定可以访问的IP
from channels.security.websocket import OriginValidator
application = ProtocolTypeRouter({# 普通的HTTP协议在这里不需要写,框架会自己指明'websocket': OriginValidator(AuthMiddlewareStack(URLRouter(# 指定去对应应用的routing中去找路由chat.routing.websocket_urlpatterns)),# 设置可以访问的IP列表['*'])
})

ProtocolTypeRouterASIG支持多种不同的协议,在这里可以指定特定协议的路由信息,我们只使用了websocket协议,这里只配置websocket即可

AllowedHostsOriginValidator:指定允许访问的IP,设置后会去Django中的settings.py中去查找ALLOWED_HOSTS设置的IP

AuthMiddlewareStack:用于WebSocket认证,继承了Cookie MiddlewareSessionMiddleware,SessionMiddleware。djangochannels封装了djangoauth模块,使用这个配置我们就可以在consumer中通过下边的代码获取到用户的信息

def connect(self):self.user = self.scope["user"]

self.scope类似于django中的request,包含了请求的type、path、header、cookie、session、user等等有用的信息

URLRouter: 指定路由文件的路径,也可以直接将路由信息写在这里,代码中配置了路由文件的路径,会去对应应用下的routeing.py文件中查找websocket_urlpatterns

chat/routing.py内容如下

from django.urls import path
from chat.consumers import ChatConsumerwebsocket_urlpatterns = [path('ws/chat/', EchoConsumer), # 这里可以定义自己的路由path('ws/<str:username>/',MessagesConsumer) # 如果是传参的路由在连接中获取关键字参数方法:self.scope['url_route']['kwargs']['username']
]

routing.py路由文件跟djangourl.py功能类似,语法也一样,意思就是访问ws/chat/都交给ChatConsumer处理。

4、在要使用WebSocket的应用中创建consumers.pyconsumers.py是用来开发ASGI接口规范的python应用,而Django中的view.py是用来开发符合WSGI接口规范的python应用。

首先了解下面的意思:
event loop事件循环、event handler事件处理器、sync同步、async异步

下面是一个同步的consumers.py

from channels.consumer import SyncConsumerclass EchoConsumer(SyncConsumer):def websocket_connect(self, event):self.send({'type': "websocket.accept"  # 这里是固定的写法,type不可以改变,是ASGI的接口规范,})def websocket_receive(self, event):user = self.scope['user'] # 获取当前用户,没有登录显示匿名用户path = self.scope['path'] # Request请求的路径,HTTP,WebSocket# ORM 同步代码 假如要查询数据库user = User.objects.filter(username=username)self.send({"type": "websocket.send",  # 这里是固定的写法,type不可以改变"text": event['text']  # 把前端返回过来的text返回回去})

异步的consumers.py

from channels.consumer import AsyncConsumerclass EchoConsumer(AsyncConsumer):async def websocket_connect(self, event):await self.send({'type': "websocket.accept"})async def websocket_receive(self, event):# 在异步中所有的操作都需要异步执行,比如发送请求,操作ORM# 对于异步的请求可以使用模块aiohttp实现异步的request请求# ORM 异步代码 假如要查询数据库# 第一种方式 使用channels通过的模块from channels.db import database_sync_to_asyncuser = await database_sync_to_async(User.objects.filter(username=username))# 第二种方式 使用装饰器@database_sync_to_asyncdef get_username():return User.objects.filter(username=username)await self.send({"type": "websocket.send", "text": event['text']  })

需要注意的是在异步中所有的逻辑都应该是异步的,不可以那同步的和异步的代码混合使用。

继承WebSocketConsumer的连接

from channels.generic.websocket import AsyncWebsocketConsumerclass MessageConsumer(AsyncWebsocketConsumer):def connect(self):if self.scope['user'].is_anonymous:# 没有登陆的用户直接断开连接self.close()else:# 加入聊天组,并监听对应的频道# self.channel_layer进行监听频道# self.scope['user'].username以用户名作为组名,# self.channel_name 要进行监听的频道,会自己生成唯一的频道self.channel_layer.group_add(self.scope['user'].username,self.channel_name)self.accept()def receive(self, text_data=None, bytes_data=None):'''接受私信'''self.send(text_data=json.dumps(text_data)) # 将接收到的信息返回出去def disconnect(self, code):'''离开聊天组'''# self.scope['user'].username要结束的组名self.channel_layer.group_discard(self.scope['user'].username,self.channel_name)

要改为异步和前面的方法一致

信息交互的周期

项目中可以在视图中直接推送信息给用户

view.py
from channels.layers import get_channel_layer
def send_message(request):... ...channel_layer = get_channel_layer()payload = {'type':'receive', # 这里的写法是固定的,receive代表的是consumers中的receive函数'message':'要发送的信息','sender':sender.username, # 发送者的昵称}channel_layer.group_send(receiver_username,payload)

前端实现WebSocket

WebSocket对象一个支持四个消息:onopenonmessageoncluseonerror,我们这里用了两个onmessage和onclose

onopen: 当浏览器和websocket服务端连接成功后会触发onopen消息

onerror: 如果连接失败,或者发送、接收数据失败,或者数据处理出错都会触发onerror消息

onmessage: 当浏览器接收到websocket服务器发送过来的数据时,就会触发onmessage消息,参数e包含了服务端发送过来的数据

onclose: 当浏览器接收到websocket服务器发送过来的关闭连接请求时,会触发onclose消息载请注明出处。

% extends "base.html" %}{% block content %}<textarea class="form-control" id="chat-log" disabled rows="20"></textarea><br/><input class="form-control" id="chat-message-input" type="text"/><br/><input class="btn btn-success btn-block" id="chat-message-submit" type="button" value="Send"/>
{% endblock %}{% block js %}
<script>var chatSocket = new WebSocket('ws://' + window.location.host + '/ws/chat/');chatSocket.onmessage = function(e) {var data = JSON.parse(e.data);var message = data['message'];document.querySelector('#chat-log').value += (message + '\n');};chatSocket.onclose = function(e) {console.error('Chat socket closed unexpectedly');};document.querySelector('#chat-message-input').focus();document.querySelector('#chat-message-input').onkeyup = function(e) {if (e.keyCode === 13) {  // enter, returndocument.querySelector('#chat-message-submit').click();}};document.querySelector('#chat-message-submit').onclick = function(e) {var messageInputDom = document.querySelector('#chat-message-input');var message = messageInputDom.value;chatSocket.send(JSON.stringify({'message': message}));messageInputDom.value = '';};
</script>
{% endblock %}

参考博客:https://juejin.im/post/5cb67fc3e51d456e6a1d0237

前后端分离项目实现Websocket

环境版本:django==2.0channels==2.2.0channels-redis==2.3.2

vue实现代码:

全局配置 websocket.js

const path = window.location.host
const WSS_URL = 'wss://' + path + '/ws/chat/'
let Socket = ''
let setIntervalWebsocketPush = null/** 建立连接 */
export function createSocket(projectId) {if (!Socket) {console.log('建立websocket连接')Socket = new WebSocket(WSS_URL + projectId)Socket.onopen = onopenWSSocket.onmessage = onmessageWSSocket.onerror = onerrorWSSocket.onclose = oncloseWS} else {console.log('websocket已连接')}
}
/** 打开WS之后发送心跳 */
export function onopenWS() {sendPing() // 发送心跳
}
/** 连接失败重连 */
export function onerrorWS() {clearInterval(setIntervalWebsocketPush)Socket.close()createSocket() // 重连
}
/** WS数据接收统一处理 */
export function onmessageWS(e) {window.dispatchEvent(new CustomEvent('onmessageWS', {detail: e.data}))
}
/** 发送数据 */
export function sendWSPush(eventTypeArr) {const obj = {appId: 'airShip',cover: 0,event: eventTypeArr}if (Socket !== null && Socket.readyState === 3) {Socket.close()createSocket() // 重连} else if (Socket.readyState === 1) {Socket.send(JSON.stringify(obj))} else if (Socket.readyState === 0) {setTimeout(() => {Socket.send(JSON.stringify(obj))}, 3000)}
}
/** 关闭WS */
export function oncloseWS() {clearInterval(setIntervalWebsocketPush) // 取消由setInterval()设置的timeout。Socket = ''console.log('websocket已断开')
}
/** 发送心跳 */
export function sendPing() {Socket.send('ping')setIntervalWebsocketPush = setInterval(() => {Socket.send('ping')}, 5000)
}

组件内使用 Index.vue

import { createSocket } from '@/api/websocket'
destroyed() {// 根据需要,销毁事件监听window.removeEventListener('onmessageWS', this.getDataFunc)
},
created() {createSocket(projectId)// 添加事件监听window.addEventListener('onmessageWS', this.getDataFunc)
},
methods:{// 监听ws数据响应getDataFunc(e) {const tempData = JSON.parse(e.detail)}
}

django实现代码:

settings.py

# 在应用中注册 channels
# Channels
ASGI_APPLICATION = 'cmdb.routing.application'
CHANNEL_LAYERS = {"default": {"BACKEND": "channels_redis.core.RedisChannelLayer","CONFIG": {"hosts": [('127.0.0.1', 6379)],},},
}

consumers.py

import json
from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_syncclass MsgConsumer(WebsocketConsumer):def __init__(self, *args, **kwargs):self.room_group_name = ""super(MsgConsumer, self).__init__(*args, **kwargs)def connect(self):# 链接后将应用id作为组名,project_id = self.scope["path_remaining"]self.room_group_name = project_idasync_to_sync(self.channel_layer.group_add)(self.room_group_name,self.channel_name)self.accept()def disconnect(self, close_code):# 断开连接时从组里面删除async_to_sync(self.channel_layer.group_discard)(self.room_group_name,self.channel_name)def receive(self, text_data=None, bytes_data=None):# 接受到信息时执行text_data_json = json.loads(text_data)message = text_data_json['message']async_to_sync(self.channel_layer.group_send)(self.room_group_name, {'type': 'chat.message',  # 必须在MsgConsumer类中定义chat_message'message': message})def send_message(self, event): # 发送信息是执行message = event['message']self.send(text_data=json.dumps({'message': message}))def chat_message(self, event):message = event['message']self.send(text_data=json.dumps({'message': message}))

在其他视图内使用

view.py

# 测试函数
def send_fun():from asgiref.sync import async_to_syncfrom channels.layers import get_channel_layerchannels_layer = get_channel_layer()data = '我是追加的内容\n'for i in [0, 1, 2, 3, 4, 5, 6]:send_dic = {"type": "send.message","message": {'step': i,'content': data}}if i < 5:import timefor j in range(19):time.sleep(0.5)send_dic = {"type": "send.message", # 必须在MsgConsumer类中定义send_message"message": {'step': i,'content': data}}time.sleep(0.5)async_to_sync(channels_layer.group_send)(room_group_name , send_dic)else:async_to_sync(channels_layer.group_send)(room_group_name , send_dic)

Django与Channels实现WebSocket相关推荐

  1. Django使用Channels实现WebSocket消息通知功能

    更多内容请点击 我的博客 查看,欢迎来访. Django Channels https://channels.readthedocs.io/en/latest/installation.html Ch ...

  2. Python+Django+channels实现websocket

    Python+Django+channels实现websocket 前言 公司需要实现一个长连接,用的Python的Django框架.研究了很长时间,发现Django+channels可以实现webs ...

  3. Django:使用channels创建websocket,实现消息推送

    感谢:(1)Django+channels配置与编写:https://blog.csdn.net/weixin_40105587/article/details/87375886 (2)Django+ ...

  4. django框架channels实现私信功能

    这里写自定义目录标题 功能实现 一.安装channels 1. 通过pip在终端安装 2. 将channels加进setting 3. 配置项目默认路由 二.创建私信APP 部分问题 功能实现 一.安 ...

  5. Django插件Channels ——实现即时通信

    注明 本笔记主要参考<Django应用开发实战><Django企业开发实战>,这两本书前者详细,后者精炼.学习之后真的是感觉自己进步了很多.值得一读,如果您遇到了值得一读的书籍 ...

  6. channels实现websocket实时通讯和消息推送

    Django+channels实现websocket实时通讯@channels Django框架集合channels实现实时通讯和消息推送 channel是Django团队的一个研发的一个给Djang ...

  7. Django中channels的配置

    特别注意: 最近在学习django中的websocket的时候遇见了一些配置的问题,被卡了很长的一段时间,所以希望这篇文章可以帮助一些正在学习Djano的小伙伴提供一些解决方法 channels的版本 ...

  8. Django使用channels进行消息推送

    Django使用channels进行消息推送 说明 这个只是说了Django后端的搭建,前端我就不讲了, 可以使用这个网站进行测试 http://www.websocket-test.com/ 环境 ...

  9. 保姆级别 附带源码 Django集成channels(一)实现简单聊天功能

    目录 前言 不想看我瞎BB可以直接跳到这里 1.WebSocket 1.1 ajax轮询 1.2 long poll 1.3 Websocket 2.Channels 2.1 WSGI 2.2 ASG ...

最新文章

  1. RuntimeException与CheckedException
  2. 【快应用篇01】快应用它来了!带你了解什么是快应用!
  3. 仅需一个参数就可搞定OneProxy的VIP机制
  4. 《Tensorflow 实战》(完整版,附源码)
  5. Java中的集合HashSet、LinkedHashSet、TreeSet和EnumSet(二)
  6. python--List extend()方法
  7. 2016年6月份那些最实用的 jQuery 插件专辑
  8. appstore ip地址
  9. 监控工具Zabbix之原理及部署
  10. 迁移操作系统:如何把系统迁移到固态硬盘SSD?
  11. WPF字体图标——FontAwesom
  12. 家用洗地扫地机一体机哪家好、家用小型洗地机推荐
  13. Windows battery report
  14. IPsec IKE第一阶段主模式和野蛮模式
  15. 超实用!手把手教你如何将废旧的 Android 手机改造成一个好用的 Linux 服务器!...
  16. 计算机金融sci,经济金融类SSCISCI 四区类(垃圾类)杂志汇总,欢迎补充
  17. 【自学Java】桌球游戏-边界检测,桌球碰撞反弹的实现,以及模拟真实运动,小球减速运动
  18. SAST——Checkmarx静态检测工具收集(2)
  19. Oracle 将字符串分割成集合
  20. matlab C 混编 --- 在MATLAB中使用c语言函数

热门文章

  1. Spring分布式缓存
  2. python的核心数据类型_核心数据类型--字符串
  3. QT4程序在QT5环境编译运行
  4. Java之主函数——main函数
  5. Unity制作即时战略游戏毕设
  6. day11_animal,_eclipse_test,_lianxi,_Obejct
  7. 电脑发展这么多年,为啥没能革掉鼠标键盘的命?
  8. android viewpager的使用,ViewPager的基本使用
  9. 计算机弹钢琴谱子没有加减乘除,学钢琴不是为了学会多少曲子,而是学会举一反三...
  10. 【哈工大版】动态ReLU:自适应参数化ReLU及Keras代码(调参记录11)