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>
上面使用了四个回调函数,可以参考官网对事件的说明
- onopen https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/open_event
- onclose https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/close_event
- onmessage https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/message_event
- onerror https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/error_event
后端部分
咱们后端采用的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>
注意事项
在websockets
的 handler
中需要将异步函数转换为同步函数
如果不转换,会报错
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)
标签:WebSocket,get,await,token,user,应用,简单,async,event
From: https://www.cnblogs.com/guangdelw/p/18028902