WebSocket的简单应用

Websocket简介

在HTML5中新增了WebSocket协议,它是在一个TCP连接上实现全双工通信的协议。

传统HTTP协议中,一次通信需要浏览器端主动发出请求后,由服务器端响应内容,建立的TCP连接断开,且无状态。而且必须是客户端主动请求后,服务器端才能响应,服务器端不能主动向浏览器端发送数据。每次浏览器端发起的请求包含较长头部,真正的请求数据就一小部分,这也会浪费很多带宽。

  • 较小的开销。协议层减小了包头,减小了开销
  • 更强的实时性。服务器端可以主动发数据给客户端,不需要等待客户端请求了,不需要客户端轮询了
  • 长连接。WebSocket需要创建TCP连接并维持它,有状态
  • 更好的二进制支持、压缩效果、扩展等

Websocket通信

前端不需要安装任何组件,因为HTML5支持,参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket

后端需要安装websockets,参考 https://websockets.readthedocs.io/en/stable/intro/index.html

简单应用

咱们要认识理解websocket通信,可以先发起简单的请求

前端部分

咱们只发起最简单的请求

这里发起最简单的请求,不做任何特殊处理

<template>
    <div>
        <h1>test</h1>
    </div>
</template>

<script>
export default {
    mounted() {
        if ('WebSocket' in window) {
            const ws = new WebSocket('ws://localhost:18888/1')
            // 当请求发起成功后的回调函数
            ws.onopen = (event) => {
                console.log('连接打开', event)
                ws.send('hello server')
            }
            // 当收到服务端消息时的回调函数
            ws.onmessage = (event) => {
                console.log('接收到消息', event.data)
            }
            // 当连接关闭时的回调函数
            ws.onclose = (event) => {
                console.log('连接关闭', event)
            }
            // 当连接出错时的回调函数
            ws.onerror = (event) => {
                console.log('连接出错', event)
            }
        } else {
            this.$message({
                showClose: true,
                message: '当前浏览器不支持websocket',
                type: 'error',
                center: true
            })
        }
    }
}
</script>

上面使用了四个回调函数,可以参考官网对事件的说明

后端部分

咱们后端采用的python

所以需要安装 websockets

pip install websockets

官网 https://websockets.readthedocs.io/en/stable/howto/quickstart.html

咱们这里做简单的接收和发送

参考官网代码修改

import asyncio
import websockets


async def hello(websocket, path):
    # 接收消息
    data = await websocket.recv()
    print(type(data), data)
    # 路径 可以根据路径不通做不同的处理
    print(path)
    # 发送消息
    await websocket.send("Hello!")


async def main():
    async with websockets.serve(hello, "localhost", 18888):
        await asyncio.Future()  # run forever


if __name__ == "__main__":
    asyncio.run(main())

以上是非常简单的一个websocket服务

接收后就发送字符串,然后连接就关闭了

当然,咱们既然发起使用的是websocket连接,如果只是一个来回就结束,那使用websocket就没什么意义

运行结果

前端

控制台中

网络请求

后端

<class 'str'> hello server
/1

在django中的应用

https://websockets.readthedocs.io/en/stable/howto/django.html

这里就以django中的认证来学习

这里的认证是使用jwt认证

关于jwt的相关配置

settings.py

INSTALLED_APPS = [
    ...
    'rest_framework_simplejwt',  # 注册应用,国际化,可选
    ...
]


REST_FRAMEWORK = {
    ...
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
    ...
}

from datetime import timedelta

SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(hours=8),
}

在django中使用websocket

后端部分

import os
import django

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mm.settings')
django.setup(set_prefix = False)
# 由于需要调用django的配置,所以,上面四行必须在最上面

import asyncio
import websockets
import json
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework_simplejwt.exceptions import InvalidToken
from websockets.frames import CloseCode


async def handler(websocket, path):
    token = await websocket.recv()
    token = json.loads(token)
    if token['type'] == 'token':
        t = token['token']
        print(t)
        # 下面是校验认证的部分
        jwt_auth = JWTAuthentication()

        # 3.8版本之前的
        from asgiref.sync import sync_to_async

        # get_validated_token_async = sync_to_async(jwt_auth.get_validated_token)
        # payload = await get_validated_token_async(t)
        # get_user_async = sync_to_async(jwt_auth.get_user)
        # user = await get_user_async(payload)
        # print(type(user), user)

        # 3.9版本之后的
        try:
            payload = await asyncio.to_thread(jwt_auth.get_validated_token, t)
            user = await asyncio.to_thread(jwt_auth.get_user, payload)
            print(type(user), user)
        except InvalidToken as e:
            await websocket.close(CloseCode.INTERNAL_ERROR, e.detail.get("detail"))
            return
    else:
        await websocket.close(CloseCode.INTERNAL_ERROR, "非法请求")
        return

    # 校验通过后,就可以正常收发信息
    while True:
        print(path)
        data = await websocket.recv()
        print(data, type(data))
        # 这里使用close作为结束的标志
        if data == 'close':
            await websocket.close()
            return
        # 接收到消息,就返回消息
        await websocket.send(f"Hello {data}!")


async def main():
    async with websockets.serve(handler, "localhost", 18888):
        await asyncio.Future()  # run forever


if __name__ == "__main__":
    asyncio.run(main())

上面的基础逻辑

默认第一条消息就是传输token的,如果第一条消息没传输token,那就被视为非法请求,会直接断开连接

如果令牌有效,就会获取用户,然后就允许进行收发消息

前端部分

<template>
    <div>
        <h1>test</h1>
    </div>
</template>

<script>
export default {
    mounted() {
        if ('WebSocket' in window) {
            const ws = new WebSocket('ws://localhost:18888/1')
            ws.onopen = (event) => {
                console.log('连接打开', event)
                ws.send(
                    // 在第一条消息中装配token
                    JSON.stringify({
                        type: 'token',
                        token: window.localStorage.getItem('token')
                    })
                )
                ws.send('hello server1')
                ws.send('hello server2')
                ws.send('hello server3')
                ws.send('close')
            }
            ws.onmessage = (event) => {
                console.log('接收到消息', event.data)
            }
            ws.onclose = (event) => {
                console.log('连接关闭', event, event.reason)
            }
            ws.onerror = (event) => {
                console.log('连接出错', event)
            }
        } else {
            this.$message({
                showClose: true,
                message: '当前浏览器不支持websocket',
                type: 'error',
                center: true
            })
        }
    }
}
</script>

注意事项

websocketshandler 中需要将异步函数转换为同步函数

如果不转换,会报错

connection handler failed
Traceback (most recent call last):
...............
django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.

3.8版本之前

需要使用sync_to_async

from asgiref.sync import sync_to_async


# 需要将函数先转换为异步函数
get_validated_token_async = sync_to_async(jwt_auth.get_validated_token)
# 然后再用新函数调用参数
payload = await get_validated_token_async(t)
get_user_async = sync_to_async(jwt_auth.get_user)
user = await get_user_async(payload)
print(type(user), user)

也可以作为装饰器使用

from asgiref.sync import sync_to_async

@sync_to_async
def to_async(fun , *args):
	return fun(*args)


# 校验部分
jwt_auth = JWTAuthentication()
payload = await to_async(jwt_auth.get_validated_token, t)
user = await to_async(jwt_auth.get_user, payload)
print(type(user), user)

或者变形直接使用

from asgiref.sync import sync_to_async


payload = sync_to_async(jwt_auth.get_validated_token)(t)
user = sync_to_async(jwt_auth.get_user)(payload)
print(type(user), user)

3.9版本之后

import asyncio

payload = await asyncio.to_thread(jwt_auth.get_validated_token, t)
user = await asyncio.to_thread(jwt_auth.get_user, payload)
print(type(user), user)
posted @ 2024-02-23 10:18  厚礼蝎  阅读(42)  评论(0编辑  收藏  举报