阅读:97
目录
3.6 在chat中创建一个名为urls.py用来存放路由:
网上绝大多数博客都是发送端或者接收端同时作为服务器,这不扯么… 要不就是写的乱七八糟的根本运行不了,实在受不了,经过一段时间的学习,决定自己写一份保姆级别的集成文章,文末附带源码!
为了实现双向奔赴通信,我选择websocket;但是Django 3.0往上走就不支持websocket了就很无语,所以这里我通过channels实现websocket。
在讲Websocket
之前,先了解下 long poll
和 ajax轮询
的原理。
ajax轮询的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。这样就很浪费服务器资源,项目小还好,项目要是大一点老板直接让你走人。
long poll
其实原理跟 ajax
轮询 差不多,都是采用轮询的方式,不过采取的是阻塞模型(一直打电话,没收到就不挂电话),也就是说,客户端发起连接后,如果没消息,就一直不返回Response给客户端。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始。
ajax
轮询 需要服务器有很快的处理速度和资源(速度)。long poll
需要有很高的并发,也就是说同时接待客户的能力(场地大小)。
WebSocket
是一种在单个TCP
连接上进行全双工通讯的协议。WebSocket
允许服务端主动向客户端推送数据。在WebSocket
协议中,客户端浏览器和服务器只需要完成一次握手就可以创建持久性的连接,并在浏览器和服务器之间进行双向的数据传输。
Django
本身不支持WebSocket
,但可以通过集成Channels
框架来实现WebSocket
Channels
是针对Django项目的一个增强框架,可以使Django
不仅支持HTTP
协议,还能支持WebSocket
,MQTT
等多种协议,同时Channels
还整合了Django
的auth
以及session
系统方便进行用户管理及认证。
WSGI(Python Web Server Gateway Interface)
:为Python语言定义的Web服务器和Web
应用程序或者框架之间的一种简单而通用的接口。
ASGI(Asynchronous Web Server Gateway Interface)
:异步网关协议接口,一个介于网络协议服务和Python
应用之间的标准接口,能够处理多种通用的协议类型,包括HTTP
,HTTP2
和WebSocket
。
WSGI
是基于HTTP
协议模式的,不支持WebSocket
,而ASGI
的诞生则是为了解决Python
常用的WSGI
不支持当前Web
开发中的一些新的协议标准。同时,ASGI
对于WSGI
原有的模式的支持和WebSocket
的扩展,即ASGI
是WSGI
的扩展。
本文是为 Channels 3.0 编写的,它支持 Python 3.6+ 和 Django 2.2+
python3 -m django --version
channels
pip install channels
python manage.py startapp chat
我们需要告诉我们的项目 chat
应用程序已安装。编辑 mysite/settings.py
文件并添加'chat'
到INSTALLED_APPS设置。它看起来像这样:
我们现在将创建第一个视图,一个索引视图,允许您键入要加入的聊天室的名称。
在
templates
中
目录中创建一个名为chat的文件夹。用来存放html页面。
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Chat Rooms</title>
</head>
<body>
What chat room would you like to enter?<br>
<input id="room-name-input" type="text" size="100"><br>
<input id="room-name-submit" type="button" value="Enter">
<script>
document.querySelector('#room-name-input').focus();
document.querySelector('#room-name-input').onkeyup = function(e) {
if (e.keyCode === 13) { // enter, return
document.querySelector('#room-name-submit').click();
}
};
document.querySelector('#room-name-submit').onclick = function(e) {
var roomName = document.querySelector('#room-name-input').value;
window.location.pathname = '/chat/' + roomName + '/';
};
</script>
</body>
</html>
chat/views.py中
创建视图函数:# chat/views.py
from django.shortcuts import render
def index(request):
return render(request, 'chat/index.html')
chat中
创建一个名为urls.py用来存放路由
:# chat/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
# mysite/urls.py
from django.conf.urls import include
from django.urls import path
from django.contrib import admin
urlpatterns = [
path('chat/', include('chat.urls')),
path('admin/', admin.site.urls),
]
Django 准备成功!
到目前为止,我们刚刚创建了一个常规的 Django 应用程序;我们根本没有使用过 Channels 库。现在是整合的时候了。
我从为 Channels 创建一个根路由配置开始。Channels路由配置是一个类似于 Django URLconf 的 ASGI 应用程序,因为它告诉 Channels 当 Channels 服务器接收到 HTTP 请求时要运行什么代码。
在mysite/asgi.py
文件下
没有这个python文件可以自己手动创建一个
# mysite/asgi.py
import os
from channels.routing import ProtocolTypeRouter
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
application = ProtocolTypeRouter({
"http": get_asgi_application(),
# Just HTTP for now. (We can add other protocols later.)
})
注意:
Django 2.2 没有内置的 ASGI 支持,所以我们需要使用 Channel 的后备替代方案。mysite/asgi.py
像这样创建:
# mysite/asgi.py
import os
import django
from channels.http import AsgiHandler
from channels.routing import ProtocolTypeRouter
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
django.setup()
application = ProtocolTypeRouter({
"http": AsgiHandler(),
# Just HTTP for now. (We can add other protocols later.)
})
现在将频道库添加到已安装的应用程序列表中。编辑mysite/settings.py
文件并添加'channels'
到 INSTALLED_APPS
设置中,还需要将 Channels 指向根路由配置
全新启动一下,下面这种就算配置成功。
4.2 添加视图
在templates/chat里面新增一个html页面room.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Chat Room</title>
</head>
<body>
<textarea id="chat-log" cols="100" rows="20"></textarea><br>
<input id="chat-message-input" type="text" size="100"><br>
<input id="chat-message-submit" type="button" value="Send">
{{ room_name|json_script:"room-name" }}
<script>
const roomName = JSON.parse(document.getElementById('room-name').textContent);
const chatSocket = new WebSocket(
'ws://'
+ window.location.host
+ '/ws/chat/'
+ roomName
+ '/'
);
chatSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
document.querySelector('#chat-log').value += (data.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, return
document.querySelector('#chat-message-submit').click();
}
};
document.querySelector('#chat-message-submit').onclick = function(e) {
const messageInputDom = document.querySelector('#chat-message-input');
const message = messageInputDom.value;
chatSocket.send(JSON.stringify({
'message': message
}));
messageInputDom.value = '';
};
</script>
</body>
</html>
chat/views.py
创建视图函数# chat/views.py
from django.shortcuts import render
def index(request):
return render(request, 'chat/index.html', {})
def room(request, room_name):
return render(request, 'chat/room.html', {
'room_name': room_name
})
chat/urls.py
创建路由# chat/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('<str:room_name>/', views.room, name='room'),
]
注意:
使用公共路径前缀(例如/ws/
将 WebSocket 连接与普通 HTTP 连接区分开来)是一种很好的做法,因为它会使在某些配置中更容易地将 Channels 部署到生产环境。
创建一个新文件chat/consumers.py
将以下代码写入chat/consumers.py
# chat/consumers.py
import json
from channels.generic.websocket import WebsocketConsumer
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.accept()
def disconnect(self, close_code):
pass
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
self.send(text_data=json.dumps({
'message': message
}))
现在还需要为chat
具有到消费者的路由的应用程序创建路由配置
创建一个新文件chat/routing.py
将以下代码写入chat/routing.py
# chat/routing.py
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
]
调用as_asgi()
类方法是为了获得一个 ASGI 应用程序,该应用程序将为每个用户连接实例化我们的消费者实例。这类似于 Django 的as_view()
,它对每个请求的 Django 视图实例起到相同的作用。
下一步是将根路由配置指向 chat.routing模块
# mysite/asgi.py
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
import chat.routing
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter(
chat.routing.websocket_urlpatterns
)
),
})
这个时候就已经配置好了运行之后发现根本聊天不起来!!
这不就完了吗。。。。。。。
解决办法:
我们将使用一个使用 Redis 作为其后备存储的通道层。要启动 Redis 服务器
要是启动redis出错请看我另外一篇文章:windows下Redis server启动一闪而过的解决方案
以便 Channels 知道如何与 Redis 交互
pip install channels_redis
在 mysite/settings.py
文件里面并CHANNEL_LAYERS
在底部添加一个设置
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
import channels.layers
channel_layer = channels.layers.get_channel_layer()
from asgiref.sync import async_to_sync
async_to_sync(channel_layer.send)('test_channel', {'type': 'hello'})
async_to_sync(channel_layer.receive)('test_channel')
#结果:
{'type': 'hello'}
现在我们有了一个通道层,在/chat/consumer
. 将以下代码写入chat/consumers.py
,替换旧代码
# chat/consumers.py
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Join room group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
def disconnect(self, close_code):
# Leave room group
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Send message to room group
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
# Receive message from room group
def chat_message(self, event):
message = event['message']
# Send message to WebSocket
self.send(text_data=json.dumps({
'message': message
}))
当用户发布消息时,JavaScript 函数将通过 WebSocket 将消息传输到 ChatConsumer。ChatConsumer将收到该消息并将其转发到与房间名称对应的组。同一组中(因此在同一房间中)的每个 ChatConsumer 都会收到来自该组的消息,并通过 WebSocket 将其转发回 JavaScript,然后将其附加到聊天日志中,从而实现聊天功能。
ChatConsumer
代码的几个部分进一步的解释:
self.scope['url_route']['kwargs']['room_name']
'room_name'
从chat/routing.py
打开到消费者的 WebSocket 连接的 URL 路由中获取参数。self.room_group_name = 'chat_%s' % self.room_name
async_to_sync(self.channel_layer.group_add)(...)
self.accept()
async_to_sync(self.channel_layer.group_discard)(...)
async_to_sync(self.channel_layer.group_send)
'type'
键,对应于应该在接收事件的消费者上调用的方法的名称。到这里已经完成了简单的channels在Django中的集成。
下一篇文章将会说到Django集成channels实现socket 摄像头实时视频传输