客户端向服务器推送Web-Sockets

服务器向客户端主动推送

1.轮询/长轮询 [偷偷的主动发消息,客户端偷偷的给服务端发送消息]
2.websocket [创建一个长链接,永远不断开]主流方式

ajax请求发送-补充内容

1.需要导入jq包进行使用
https://code.jquery.com/jquery-3.6.3.min.js
2.ajax
$.ajax({
   url:'http://127.0.0.1:5000/', // 请求的地址
   type:'get', // 请求的类型
   data:JSON.stringify({'name':'wkx','aeg':123}), // 传后端的数据
   dataType:'JSON', // 接受后端数据从json转为对象类型 省略JSON.parse()
   contentType:'application/json', // 发送后端的请求为json格式
   success:function (data){
   // 正确信息
   console.log(data)
  },
   error:function (err){
   //错误信息
  }
})

队列(python内置)-补充内容

# 先进先出 与['先加入队列的值a1','后加入队列的新值b1'] 先a1后b1
# 理解为 火车头 + 车厢1 + 车厢2 .... 先组装的是火车头,那么出隧道的时候,就是火车头先出
import queue
# 1.创建队列
q = queue.Queue()
# 2.存放
q.put('6')
q.put('7')
# 3.获取
print(q.get())  # 6
print(q.get())  # 7
# 如果队列中没有值,那么就会阻塞,等待队列中有值
try:
   val3 = q.get(timeout=1)  # timeout= 20 最多等待20秒,20秒后就停止,抛出queue.Empty错误可以进行捕获
   print(val3)
except queue.Empty as e:
   print('抱歉!当前队列中没有内容,已经等待了1秒中')

递归-补充内容

最大的递归为1000,但是根据自身电脑不同次数不同 最大不能超过1000
python:
def func()
func()
func()
js:在js中不存在递归,没有层数限制,内部有循环事件机制
function func(){
   $.ajax({
       ....,
       success:function(data){
           func()
      }
  })
}

服务器向客户端主动推送方式

1.轮询
2.长轮询
3.websocket

轮询

# 服务端向客户端 【主动】 推送消息
也就是让浏览器定时向后端发送请求,获取数据
通过ajax + setTimeout 方式定时的进行发送请求
伪造服务端向后浏览器推送数据现象
缺点:
1.但是占用资源,
   2.延迟
例如:
    function show() {
       $.ajax({
           url: '/',
           type: 'post',
           data: JSON.stringify({'name': 'wkx', 'aeg': 123}),
           dataType: 'JSON',
           contentType: 'application/json',
           success: function (data) {
               // 正确信息
               console.log(data)
               # 设置定时器没3秒请求一下后端(在请求后就定时器开始递归请求show函数发送ajax)
               setTimeout(function () {
                   console.log(1)
                   show()
              }, 3000)
          },
           error: function (err) {
               //错误信息
          }
      })
  }

长轮询(兼容好)

# 解释:
   通过ajax向后端发请求,来获取数据,在这个过程中存在阻塞,最多阻塞30
# 说明:
   伪造的后端主动推送消息,还是不是真正的后端推送
# 主要体现:
阻塞让前端访问的后端的请求进行挂起(url会出现Pending状态挂起等待访问.前端与后端的链接是长时间存在的)
# 通过python queue 案例
采用队列的方式例如:[python内部的队列]
   q = queue.Queue() # 1.创建队列
q.put(10) # 2.往队列写入值
   q.get() # 3.获取值
   q.get() # 4.获取值,但是当前队列中没有值,就会阻塞,直到有值为止
主要利用了 q.get(timeout=0) 阻塞等待,url会出现Pending状态挂起等待访问 timeout=10,那么队列就会等待10秒。
# 补充说明:
1.队列使用都是:如rabbitmq使用的其他的第三方队列组件
2.长轮询的兼容性比较好,大多数地方使用的还是老ie浏览器,无法使用websocket,所以长轮询是最好的选择

聊天室案例(queue)-url

url:
   # 当前聊天室首页
   path('home/', home),
   # 发送消息的
   path('send/message/', send_msg),
   # 获取消息的
   path('get/message/',get_msg)

聊天室案例(queue)-前端

前端页面:
<div>
<input id="txt" type="text" placeholder="请发送消息">
<button id="but" onclick="sendmsg()">点击发送</button>
</div>
<div>
<h3>聊天室消息框</h3>
<div id="content">
</div>
</div>
<script>
// sendmsg作用: 点击发送信息
function sendmsg() {
let txt = document.getElementById('txt').value
$.ajax({
url: '/send/message/',
type: 'POST',
data: {'msg': txt},
success: function (data) {
console.log(data)
}
})
}
// getmsg 作用: 请求后端(前端发送给后端)信息
function getmsg() {
$.ajax({
url: '/get/message/',
type: 'get',
dataType: 'JSON',
// 将name(用户名)传入到后端
data: {'name': `{{ name }}`},
success: function (data) {
if (data.status) {
let div =`<div>${data.data}</dic>`
document.getElementById('content').insertAdjacentHTML('beforeend', div)
}
//重点: 回调函数,让ajax请求数据成功后,在进行回调访问,如果不设回调,那么只会主动执行一下(window.onload执行的)
getmsg()
}
})
}
// 作用:页面加载完毕后实现调用getmsg()ajax函数
window.onload = function () {
getmsg()
}
</script>

聊天室案例(queue)-后端接口

from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
import queue
# 全局变量存放负责存放队列 {name:队列对象}
QUEUE_DICT = {}
def home(request):
'''
home 接口作用: 根据get请求中得name值创建对应的队列 返回聊天页面html
不在做登录效果
通过 url?name=xxx 认定登录人
:param request:
:return:
'''
# 1.获取urlget请求中得name
name = request.GET.get('name')
# 2.为每一个name创建一个队列并且存放在全局变量字典中QUEUE_DICT
q = queue.Queue() # 创建一个队列
QUEUE_DICT[name] = q # 加到全局变量中{name:队列}
return render(request, 'home.html', locals())
def send_msg(request):
'''
send_msg作用: 将用户发送的消息存储到每一个QUEUE_DICT中得队列中
:param request:
:return:
'''
# 1.获取消息
msg = request.POST.get('msg')
# 2.给所有的人的队列中存放这个消息
for q in QUEUE_DICT.values(): # 获取全部的q【队列】
q.put(msg) # 将值添加到每一个队列中
return HttpResponse('发送成功消息')
def get_msg(request):
'''
get_msg作用: 被动触发接口【当聊天页面html】加载完成后自动会调用客户端的ajax请求,
传入-用户名称,根据用户名称从全局变量QUEUE_DICT,获取响应的队列从中取值
队列有值立马返回,如果没有值,队列就会阻塞10秒,那么url会出现Pending状态挂起等待访问[队列阻塞结束]
:param request:
:return:
'''
res = {'status': True, 'data': '','name':''}
name = request.GET.get('name')
# 获取当前用户的队列对象
q = QUEUE_DICT.get(name)
try:
# 当前端ajax(get_msg函数访问)当前接口
# 如果队列中有内容,将队列数据获取返回给前端页面
# 如果队列中没有内容,就会根据timeout=10,阻塞10中,页面也会进行挂起[url会出现Pending状态挂起等待访问]
data = q.get(timeout=10)
res['name'] = name
res['data'] = data
except queue.Empty as e:
# 10秒内没有队列中还是没有数据,就会抛出异常信息返回给前端
res['status'] = False
return JsonResponse(res)

websocket(请看websocket说明)

WebSoket

WebSoket-说明

新技术,现在主流的浏览器都支持
websoket 是什么:
理解为:
web:写网站让浏览器和服务端进行交互
socket:让网络上的两端创建链接进行收发数据(套接字)
# 让浏览器与服务端创建链接后,默认不在断开,两端可以实现收发数据
websoket: 是一种网络的协议[实现服务端让客户端推送消息]
# 无状态短链接 下一次访问不知道你是谁
# 无连接的含义是限制每次连接只处理一个请求,服务器处理完客户的请求,并收到客户的应答后,即断开连接,采用这种方式可以节省传输时间
http: 是网络协议,明文传输,没有加密信息,一次访问,一次请求(无状态短链接)
https:是网络协议,密文加密传输数据,一次访问,一次请求(非对称,对称秘钥)(无状态短链接)

WebSoket-原理过程(理解)

1.握手环节【是否支持websoket协议】:websoket进行创建链接 客户端与服务端创建链接
【验证1】生成一个随机的一串字符携带在请求头中,并通过http协议发送给服务端
【验证2】后端会 将请求头中字符串 + (全球公认 magic string)魔法字符串 通过特殊加密返回给客户端(加密方式:sha1/base64)
【验证3】将密文返回到用户的浏览器上
【验证4】浏览器会自动进行校验比对
2.收发数据 密文形式传输
【解密1】 读取数据的第2个字节(从1开始) 后 7bit(1字节 = 8 bit)
这一部分被称为:payload len作用负责读数据头与数据的作用
# 如果这7位 111 1111 2进制转10进制 127
127 如果是127 向后读8个字节(64位) 除了前8个字节后面都是数据
# 如果这7位 111 1110 2进制转10进制 126
126 如果是126,向后读2个字节(16位)前4个字节是数据头,除了前4个字节后面都是数据
# 如果这7位 111 1101 2进制转10进制 <=125
125 小于或等于125,那么数据头就这么长
【解密2】 masking key:从payload len读取的剩下的字节
获取的数据字节长度,还需要取出前后4个字节(32位)
前面4个字节(32位) 被称为 masking key 剩下的部分才是真正的数据进行解密

WebSoket-【模拟】原理代码

python后端

html:
// 向后端发送websokcet请求 协议 ws://
// 这段代码执行一下内容
// 1.创建随机字符串
// 2.将字符串发送给服务端
// 3.进行密文校验
// 密文校验失败浏览器就会报错
// 客户端就会存在一个ws对象
// ws.send(发送消息)
var ws = new WebSocket('ws://127.0.0.1:8000/')
python:
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8000))
sock.listen(5)
# 获取socket对象
conn, address = sock.accept()
# 获取客户端发送的消息
data = conn.recv(1024)
# Sec-WebSocket-Key 就是随机字符串
def get_headers(data):
"""
将请求头格式化成字典
:param data:
:return:
"""
header_dict = {}
data = str(data, encoding='utf-8')
header, body = data.split('\r\n\r\n', 1)
header_list = header.split('\r\n')
for i in range(0, len(header_list)):
if i == 0:
if len(header_list[i].split(' ')) == 3:
header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
else:
k, v = header_list[i].split(':', 1)
header_dict[k] = v.strip()
return header_dict
# 1.获取websocket随机字符
header_dict = get_headers(data)
# 2..websocket随机字符
Sec_WebSocket_Key = header_dict.get('Sec-WebSocket-Key')
# 3.魔法字符串 不能修改固定的
magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
# 4.魔法字符串与websocket随机字符+
val = Sec_WebSocket_Key + magic_string
# 5.加密 /官网固定用法
import hashlib
import base64
ac = base64.b64encode(hashlib.sha1(val.encode('utf-8')).digest()) # 获取的字节类型
# 6.将加密的字符返回给客户端浏览器
# Sec-WebSocket-Accept : 加密字符串
response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
"Upgrade:websocket\r\n" \
"Connection: Upgrade\r\n" \
"Sec-WebSocket-Accept: %s\r\n" \
"WebSocket-Location: ws://127.0.0.1:8000\r\n\r\n" % ac.decode('utf-8')
# 7.将信息返回给浏览器
# 转为bytes将请求信息发给websoket
conn.send(bytes(response_tpl, encoding='utf-8'))
# 8.解密函数
def get_data(info):
'''解密函数原理代码'''
print(info)
payload_len = info[1] & 127
print(payload_len)
if payload_len == 126:
extend_payload_len = info[2:4]
mask = info[4:8]
decoded = info[8:]
elif payload_len == 127:
extend_payload_len = info[2:10]
mask = info[10:14]
decoded = info[14:]
else:
extend_payload_len = None
mask = info[2:6]
decoded = info[6:]
bytes_list = bytearray()
for i in range(len(decoded)):
chunk = decoded[i] ^ mask[i % 4]
bytes_list.append(chunk)
body = str(bytes_list, encoding='utf-8')
return body
while True:
data = conn.recv(1024)
# 通过解密函数进行解密
val = get_data(data)
print(val)

HTML前端

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
// 向后端发送websokcet请求 协议 ws://
// 这段代码执行一下内容
// 1.创建随机字符串
// 2.将字符串发送给服务端
// 3.进行密文校验
// 密文校验失败浏览器就会报错
// 客户端就会存在一个ws对象
// ws.send(发送消息)
var ws = new WebSocket('ws://127.0.0.1:8000/')
</script>
</body>
</html>

WebSoket-Pthon框架使用WebSocket对应模块

# 必须通过第三方组件来让框架支持websocket ws协议
1.django: 本身不支持通过 Channels实现
# channels内部帮助我们写好了握手/加密/解密的全部环节
文档:https://channels.readthedocs.io/en/stable/
pip install channels -U # 查看官网的兼容性防止django与channels冲突
2.flask: 本事不支持通过Flask-SocketIO or Flask-Sockets实现
pip3 install geventwebsocket 常用
or
pip install Flask-SocketIO
or
pip install Flask-Sockets
3.fatapi 默认支持websocket
4.torado 默认支持websocket
注意: 不是所有的服务器支持websocket

WebSoket-Django框架实现

1.安装

1.安装
pip install channels -U # 注意版本问题
pip install daphne # Django项目维护的ASGI服务器
2.创建一个django项目
django-admin startproject 项目名
django-admin startapp app名称
# 注意 django 框架只能处理http协议
3.asgi 部署介绍
https://cloud.tencent.com/developer/article/1807566

2.修改Django框架启动服务器

修改:
1.这步操作是将django 同步wsgi默认服务器 -> 异步awsgi服务器
2.注意: 3.0版本支持asgi 之前版本不支持asgi配置不同
1.需要在项目中得asgi.py配置
# 3.0后才会有asgi.py文件,之前版本并没有需要创建[查看channels官网说明]
1.1 创先项目原配置
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'channel_demo.settings')
application = get_asgi_application()
1.2 修改后配置
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协议
"http": get_asgi_application(),
# 也可以使用ws协议
}
)
2.settings.py中INSTALLED_APPS添加一个异步服务器app程序
目的:将标准的Django开发服务器替换为 ASGI 兼容版本
2.1 安装daphne异步服务器
# Daphne 是一个纯Python编写的应用于UNIX环境的由Django项目维护的ASGI服务器。它扮演着ASGI参考服务器的角色。
pip install daphne
2.2 将daphne加入到INSTALLED_APPS中
INSTALLED_APPS = [
'daphne', # 添加uvicorn无法使用
....
]
3.settings.py中 需要添加变量ASGI_APPLICATION
目的:配置文件中配置启动asgi的启动服务器,变量需要放最后防止被覆盖
ASGI_APPLICATION = '项目名.asgi.application'
4.启动django项目
# 使用当前命令启动,配置上述参数后,启动使用就是daphne异步服务器
python manage.py runserver
命令行打印:# 如果出现'Starting ASGI/Daphne' 那么daphne配置成功
Starting ASGI/Daphne version 4.0.0 development server at http://127.0.0.1:8000/
5.补充项 注意 事项:
也可以在命令行进行启动
daphne 项目名称.asgi:application
daphne -b ip -p 端口 ... # 通过-b -p绑定ip端口
or
# 使用uvicorn异步服务器也可以 pip install uvicorn
# 不需要进行设置 INSTALLED_APPS 与 ASGI_APPLICATION
# 当前服务器只能在命令行启动
uvicorn 项目名称.asgi:application
uvicorn .... --host ip --port 端口 # 绑定端口ip

3.修改asgi.py文件支持ws协议 编写websocket 的 url

# 文档地址:
# https://channels.readthedocs.io/en/stable/deploying.html#configuring-the-asgi-application
1.需要在asgi.py中配置websocket协议与路由设置
import os
from channels.routing import URLRouter # 1.导入
from channels.auth import AuthMiddlewareStack # 2.导入
from channels.routing import ProtocolTypeRouter # 3.导入
from channels.security.websocket import AllowedHostsOriginValidator # 4.导入
from django.urls import re_path # 4. 导入django 的re_path规则路由规则
from django.core.asgi import get_asgi_application
from app01 import consumers # websokcet路由 api接口
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
application = ProtocolTypeRouter(
{ # 标明http协议
"http": get_asgi_application(),
# 标明ws协议使用
'websocket':AllowedHostsOriginValidator(
AuthMiddlewareStack(
# ws协议的路由列表 websokceturl写在这里
# 路由规则re_path('路由','websocket处理类'),
URLRouter(
[ # 案例:
re_path('^chat/$',consumers.ChatConsumer.as_asgi())
]
)
)
)
}
)
# channels导入的作用
ProtocolTypeRouter作用: ASGI支持多种不同的协议,可以让asgi支持websocket协议
AllowedHostsOriginValidator作用:指定允许访问的IP,设置后会去Django中的settings.py中去查找ALLOWED_HOSTS设置的IP
AuthMiddlewareStack作用:用于WebSocket认证,继承了Cookie Middleware,
URLRouter作用: 指定路由于路由对应的api的路径

4.编写websocket对应的api接口

import json
from channels.generic.websocket import WebsocketConsumer # 需要继承channels 中得 WebsocketConsumer类
class ChatConsumer(WebsocketConsumer): # ChatConsumer是当前chat路由的api接口
'''重写WebsocketConsumer它的方法 重写这三个方法'''
def connect(self):
'''
websocket建立连接时执行方法
接受客户端随机字符 + 全球的魔法字符串 + 加密算法 返回给客户端浏览器
'''
# 服务接受链接,向客户端浏览器加密字符串()
self.accept()
def disconnect(self, close_code):
'''
websocket断开连接时执行方法
'''
pass
def receive(self, text_data=None, bytes_data=None):
'''
客户端浏览器向服务端发送消息,此方法立即触发 这是最主要
'''
text_data_json = json.loads(text_data)
message = text_data_json["message"]
# 服务端向客户端发送消息
self.send(text_data=json.dumps({"message": message}))

5.前端对后端websocket-url发送链接

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>欢迎进入聊天室</h1>
<div>
<input type="text" id="text" placeholder="请输入">
<button onclick="send_msg()">发送聊天信息</button>
<button onclick="closes()">点击点击</button>
</div>
<div>
<h3>聊天记录</h3>
<div id="content"></div>
</div>
<script>
// 1.创建websocket对象
// ws://127.0.0.1:8000/char/ 往wabsocket后端的路由char发起请求,让当前char路由进行api接口处理内容
var ws = new WebSocket('ws://127.0.0.1:8000/char/')
// 2.ws.onopen:客户端与服务端握手成功后,自动执行当前方法
ws.onopen = function () {
console.log('链接成功')
}
// 3. ws.onmessage 当服务端发送消息时进行触发获取 后端发送的对象
ws.onmessage = function (event) { // event 是当前发送消息接受的对象
// console.log(event.data) // 接受服务端推送的消息
let msg = JSON.parse(event.data).msg
// 插入标签
document.getElementById('content').insertAdjacentHTML('beforeend', `<div>${msg}<div>`)
}
// 4.ws.send 主动对websocket服务端发送消息
function send_msg() {
let msg_txt = document.getElementById('text').value
// 将用户的信息发送给后台 websocket 路由'char'发送信息为json格式信息
ws.send(JSON.stringify({'msg': msg_txt}))
}
// 5.用来websocket断开链接
function closes() {
ws.close()
}
</script>
</body>
</html>
当进行链接到后端服务时
WebSocket HANDSHAKING /char/ [127.0.0.1:59404] - >握手
WebSocket CONNECT /char/ [127.0.0.1:59404]- > 链接
当前断开链接时
WebSocket DISCONNECT /char/ [127.0.0.1:61721] -> 链接断开

群发消息

使用channel-layers
https://channels.readthedocs.io/en/latest/topics/channel_layers.html

总结

websokcet是什么?
服务端主动向客户端推送消息的协议
django实现websocket协议?
依赖于channel(django官网写的)
websocket作用:
完成服务端主动向客户端推送消息(轮询与长轮询是伪造的)

 

本文作者:_wangkx

本文链接:https://www.cnblogs.com/kaixinblog/p/17157652.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   _wangkx  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起