前言:最近后台写游戏更新版本功能,简单就是前端发送更新请求,后端需要对很多台服务器进行更新和各种操作,本来想着实现不难,后来发现因为后端需要执行很长时间,前端返回报错,后端会执行完毕,但是前端先断开了,这样在前端页面我就看不到更新结果了。通过调整nginx参数,设置超时时间,还是日志会报499状态码错误。后来了解到了websocket,对于需要长时间处理的请求,使用websocket会更好,通过使用websocket实现了自己的功能,简单分享下

一、什么是WebSocket

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

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

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

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

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

二、什么是Channels

Django本身不支持WebSocket,但可以通过集成Channels框架来实现WebSocket
Channels是针对Django项目的一个增强框架,可以使Django不仅支持HTTP协议,还能支持WebSocket,MQTT等多种协议,同时Channels还整合了Django的auth以及session系统方便进行用户管理及认证。

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

2.2channels文档链接

https://channels.readthedocs.io/en/latest/introduction.html

2.3.WSGI和ASGI不同

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

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

三、Django中使用Channel

3.1安装channels

pip install channels==2.1.7

3.2修改setting.py文件

INSTALLED_APPS = [
    'django.contrib.staticfiles',
    ... ...
    'channels',
]

# 指定ASGI的路由地址
ASGI_APPLICATION = 'webapp.routing.application' #ASGI_APPLICATION 指定主路由的位置为webapp下的routing.py文件中的application

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

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
import webapp.routing

application = ProtocolTypeRouter({
    'websocket':AllowedHostsOriginValidator(
        AuthMiddlewareStack(
            URLRouter(
                webapp.routing.websocket_urlpatterns
            )
        )
    )
})

ProtocolTypeRouter:ASGI支持多种不同的协议,在这里可以指定特定协议的路由信息,这里只使用了websocket协议,这里只配置websocket即可
AllowedHostsOriginValidator:指定允许访问的IP,设置后会去Django中的settings.py中去查找ALLOWED_HOSTS设置的IP
AuthMiddlewareStack:用于WebSocket认证,继承了Cookie Middleware,SessionMiddleware,SessionMiddleware。
django的channels封装了django的auth模块,使用这个配置我们就可以在consumer中通过下边的代码获取到用户的信息,和请求的url路径

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

self.scope类似于django中的request,包含了请求的type、path、header、cookie、session、user等等有用的信息
URLRouter: 指定路由文件的路径,也可以直接将路由信息写在这里,代码中配置了路由文件的路径,会去对应应用下的routeing.py文件中查找websocket_urlpatterns

3.4webapp.routing.py内容如下

from django.urls import path
from webapp.consumers import ChatConsumer

websocket_urlpatterns = [
  path('ws/chat/',ChatConsumer)
]

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

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

from channels.generic.websocket import WebsocketConsumer
from channels.generic.websocket import AsyncWebsocketConsumer
import json,time

Channels支持同步,也支持异步方式

同步方式代码如下:

class ChatConsumer(WebsocketConsumer):
    # websocket建立连接时执行方法
    def connect(self):
        self.accept()
    
    # websocket断开时执行方法
    def disconnect(self, close_code):
        self.close()
    
    # 从websocket接收到消息时执行函数
    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = f'结果:{text_data_json}'
        self.send(text_data=json.dumps(
        {
             'message': message
        }))

异步方式代码如下:

class ChatConsumer(AsyncWebsocketConsumer):
    #websocket建立连接时执行方法
    async def connect(self):
        await self.accept()

    # websocket断开时执行方法
    async def disconnect(self, close_code):
        print(close_code)


    # 从websocket接收到消息时执行函数
    async def receive(self, text_data):
        for i in range(10):
            time.sleep(i)
            message = '结果: ' + str(i)
            await self.send(text_data=json.dumps({
                'message': message
            })
        )

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

四、前端Websocket使用

WebSocket对象一个支持四个消息:onopen,onmessage,oncluse和onerror

onopen: 当浏览器和websocket服务端连接成功后会触发onopen消息
onerror: 如果连接失败,或者发送、接收数据失败,或者数据处理出错都会触发onerror消息
onmessage: 当浏览器接收到websocket服务器发送过来的数据时,就会触发onmessage消息,参数e包含了服务端发送过来的数据
onclose: 当浏览器接收到websocket服务器发送过来的关闭连接请求时,会触发onclose消息

拼接websocket请求地址,建立长连接

var chatSocket = new WebSocket('ws://' + window.location.host + '/ws/ver_update/');

连接事件  

chatSocket.onopen = function () {
    console.log(getCurrentDate(2) + ' ' + 'websocket connection success')
};

错误事件

chatSocket.onerror = function () {
    console.error(getCurrentDate(2) + ' ' + 'websocket connection error')
};

关闭事件

chatSocket.onclose = function (e) {
    layer.msg('websocket关闭,检查错误日志', {icon: 2});
    console.error(getCurrentDate(2) + ' ' + 'websocket closed unexpectedly 状态码:' + e.code);
    chatSocket.close();
};

接收事件

chatSocket.onmessage = function (e) {
    var data = JSON.parse(e.data);
}

五、测试Channels功能      

在Django中使用Channels功能_服务端

 

 

总结:自从使用Websocket功能后,再也没发生前端突然断开的情况了,对于长时间运行的任务,使用websocket是不错的选择~,有不足的地方请多多指教