利用Channels实现WebSocket协议

HTTP协议与WebSocket协议

HTTP协议简介

​ HTTP协议指超文本传输协议(HyperText Transfer Protocol),是用于从万维网服务器传输超文本到本地浏览器的传送协议,是基于TCP/IP协议之上的应用层协议。

主要特点

  1. 简单快速:客户向服务器请求服务时,只需传送请求方法和路径,通信速度很快。
  2. 灵活:HTTP允许传输任意类型的数据对象。
  3. 无连接:限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
  4. 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
  5. 支持客户端/服务器模型

工作原理

HTTP是基于客户/服务器模式,且面向连接的。典型的HTTP事务处理有如下的过程:

  1. 客户与服务器建立连接;

    1. 客户向服务器提出请求;
    2. 服务器接受请求,并根据请求返回相应的文件作为应答;
    3. 客户与服务器关闭连接。

WebSocket协议简介

​ 现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

​ HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

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

Django Channels实现WebSocket实时通讯

官方文档

Channels是一个采用Django并将其功能拓展到HTTP以外的项目,以处理WebSocket协议。它基于称为ASGI的Python规范构建。

​ 接下来基于官方Tutorial1-2简单介绍Channels的使用。

​ 首先需要安装Django和Channels

pip install django
pip install channels

​ 创建一个Django项目,并进入项目根目录:

django-admin startproject mysite
cd mysite

​ 接下来创建Channels的根路由routing.py,channels的路由配置与Django URLconf相似,它告诉Channels当Channels服务器接收到HTTP请求时要运行什么代码。

​ 首先从一个空的路由配置开始,创建文件mysite/routing.py,包含以下代码:

# mysite/routing.py
from channels.routing import ProtocolTypeRouter

application = ProtocolTypeRouter({
    # (http->django views is added by default)
})

​ 然后将Channels库在应用列表中进行注册:

# mysite/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 新添加
    'channels',
]

​ 继续编辑mtsite/settings.py将Channels指向根路由配置,添加以下代码:

# mysite/settings.py
# Channels
ASGI_APPLICATION = 'mysite.routing.application'

​ 此时Channels可以控制runserver命令,用Channels开发服务器代替标准的Django开发服务器。

​ 运行Django项目:

py manage.py runserver

​ 将会看到以下输出:

Performing system checks...

System check identified no issues (0 silenced).

You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
March 07, 2020 - 22:45:04
Django version 3.0.2, using settings 'mysite.settings'
Starting ASGI/Channels version 2.4.0 development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
HTTP GET / 200 [0.04, 127.0.0.1:54871]

​ 可以看到Starting ASGI/Channels development server at http://127.0.0.1:8000/,说明Channels开发服务器已从Django开发服务器接管项目。打开初始界面,你将看到熟悉的小火箭:

​ 关闭服务器,创建appevent

py manage.py startapp event

​ 注册app:

# mysite/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels',
    # 新添加
    'event',
]

​ 在根目录新建文件夹templates以及templates/event文件夹放置html文件,在mysite/settings.py中添加路径:

# mysite/settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        # 修改
        'DIRS': [os.path.join(BASE_DIR, 'templates').replace('\\', '/')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

​ 在templates中添加视图文件event/list.html,其中WebSocket请求部分模板如下:

<script type="text/javascript">
	function Filter() {
        if ("WebSocket" in window) {
            //alert("您的浏览器支持WebSocket!");
            // 重定向URL
            let ws = new WebSocket("ws://"+ window.location.host + "/ws/event/list/");
            ws.onopen = function () {
                ws.send(JSON.stringify({
                    'message': "测试",
                    // 需要传输的数据
                }));
            }
            ws.onmessage = function (evt) {
                let received_msg = JSON.parse(evt.data);
                let feed_back = received_msg['feedback'];
                alert(feedback);
                // 处理接受数据
            }
            ws.onclose = function () {
                //alert("WebSocket连接已关闭...");
            }
        }
        else {
            alert("你的浏览器不支持WebSocket!");
        }
    }
</script>

​ 创建视图功能event/views.py

# event/views.py
from django.shortcuts import render

def list(request):
    return render(request, 'event/list.html', {})

​ 创建路由event/urls.py

# event/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path('list/', views.list, name='list'),
]

​ 将event app的路由添加到项目的根路由中:

# mysite/urls.py
from django.conf.urls import include
from django.urls import path
from django.contrib import admin

urlpatterns = [
    path('admin/', admin.site.urls),
    path('event/', include('event.urls', namespace='event')),
]

Channels处理请求过程:

1. Channels接受HTTP请求
2. 查询根URLconf查找**消费者(consumer)**
3. 在**消费者(consumer)**中调用各种功能来处理连接的事件

创建消费者(consumer)文件event/consumers.py

event/
    __init__.py
    ……
    consumers.py
    ……
    urls.py
    views.py

​ 代码模板如下:

# event/consumers.py
from channels.generic.websocket import WebsocketConsumer
import json

class ListDataConsumer(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({
        'feedback': "Accept",
        # 返回数据
    }))

​ 为consumers.py配置路由,新建event/routing.py

# event/routing.py
from django.urls import re_path

from . import consumers

websocket_urlpatterns = [
    re_path(r'ws/event/list/$', consumers.ListDataConsumer),
]

​ 接下来将根路由指向event/routing.py文件:

# mysite/routing.py
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter

import event.routing

application = ProtocolTypeRouter({
    # (http->django views is added by default)
    'websocket': AuthMiddlewareStack(
        URLRouter(
            event.routing.websocket_urlpatterns
        )
    ),
})

​ 此根路由配置指定当与Channels开发服务器建立连接时,ProtocolTypeRouter将首先检查连接的类型。如果它是WebSocket连接(ws://wss://),则该连接将分配给AuthMiddlewareStack

​ 在AuthMiddlewareStack将填充的连接的范围覆盖到当前认证的用户,然后将连接到URLRouter。该URLRouter会研究基础上,提供连接到路由到特定消费者的HTTP路径,基于url模式。

​ 之后进行数据库模型迁移:

py manage.py makemigrations
py manage.py migrate

​ 运行项目:

py manage.py runserver

​ 如果连接建立成功,后台应该有如下显示:

HTTP GET /event/list/ 200 [0.06, 127.0.0.1:58855]
WebSocket HANDSHAKING /ws/event/list/ [127.0.0.1:58906]
WebSocket CONNECT /ws/event/list/ [127.0.0.1:58906]

总结

​ 使用Channels的一般流程如上,在配置ASGI之后,Channels的服务器会替代原有的Django服务器处理请求。

​ 只有在需要使用WebSocket协议进行实时通信时需要配置routing.pyconsumers.py,由routing.py指向consumers.py处理WebSocket请求,其余仅使用HTTP协议请求的使用方式与之前并没有任何区别

​ 因为只是在一个项目中的某个app中使用了Channels,可以参考该项目代码中的event.

猜你喜欢

转载自www.cnblogs.com/liyishan/p/12624106.html