服务器向客户端主动推送
| 1.轮询/长轮询 [偷偷的主动发消息,客户端偷偷的给服务端发送消息] |
| 2.websocket [创建一个长链接,永远不断开]主流方式 |
ajax请求发送-补充内容
| 1.需要导入jq包进行使用 |
| https: |
| |
| 2.ajax |
| $.ajax({ |
| url:'http://127.0.0.1:5000/', |
| type:'get', |
| data:JSON.stringify({'name':'wkx','aeg':123}), |
| dataType:'JSON', |
| contentType:'application/json', |
| success:function (data){ |
| |
| console.log(data) |
| }, |
| error:function (err){ |
| |
| } |
| |
| }) |
队列(python内置)-补充内容
| |
| |
| import queue |
| |
| |
| q = queue.Queue() |
| |
| q.put('6') |
| q.put('7') |
| |
| print(q.get()) |
| print(q.get()) |
| |
| |
| |
| try: |
| val3 = q.get(timeout=1) |
| 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() |
| } |
| }) |
| } |
| |
| |
服务器向客户端主动推送方式
轮询
| # 服务端向客户端 【主动】 推送消息 |
| |
| 也就是让浏览器定时向后端发送请求,获取数据 |
| |
| 通过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> |
| |
| function sendmsg() { |
| let txt = document.getElementById('txt').value |
| $.ajax({ |
| url: '/send/message/', |
| type: 'POST', |
| data: {'msg': txt}, |
| success: function (data) { |
| console.log(data) |
| } |
| }) |
| } |
| |
| |
| |
| function getmsg() { |
| $.ajax({ |
| url: '/get/message/', |
| type: 'get', |
| dataType: 'JSON', |
| |
| data: {'name': `{{ name }}`}, |
| success: function (data) { |
| if (data.status) { |
| let div =`<div>${data.data}</dic>` |
| document.getElementById('content').insertAdjacentHTML('beforeend', div) |
| } |
| |
| getmsg() |
| } |
| }) |
| } |
| |
| |
| window.onload = function () { |
| getmsg() |
| } |
| </script> |
聊天室案例(queue)-后端接口
| from django.shortcuts import render,HttpResponse |
| from django.http import JsonResponse |
| import queue |
| |
| |
| QUEUE_DICT = {} |
| |
| |
| def home(request): |
| ''' |
| home 接口作用: 根据get请求中得name值创建对应的队列 返回聊天页面html |
| 不在做登录效果 |
| 通过 url?name=xxx 认定登录人 |
| :param request: |
| :return: |
| ''' |
| |
| name = request.GET.get('name') |
| |
| q = queue.Queue() |
| QUEUE_DICT[name] = q |
| return render(request, 'home.html', locals()) |
| |
| |
| def send_msg(request): |
| ''' |
| send_msg作用: 将用户发送的消息存储到每一个QUEUE_DICT中得队列中 |
| :param request: |
| :return: |
| ''' |
| |
| msg = request.POST.get('msg') |
| |
| for q in QUEUE_DICT.values(): |
| 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: |
| |
| |
| |
| data = q.get(timeout=10) |
| res['name'] = name |
| res['data'] = data |
| except queue.Empty as e: |
| |
| 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作用负责读数据头与数据的作用 |
| |
| 127 如果是127 向后读8个字节(64位) 除了前8个字节后面都是数据 |
| |
| 126 如果是126,向后读2个字节(16位)前4个字节是数据头,除了前4个字节后面都是数据 |
| |
| 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) |
| |
| |
| conn, address = sock.accept() |
| |
| |
| data = conn.recv(1024) |
| |
| |
| |
| |
| 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 |
| |
| |
| header_dict = get_headers(data) |
| |
| |
| Sec_WebSocket_Key = header_dict.get('Sec-WebSocket-Key') |
| |
| magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' |
| |
| val = Sec_WebSocket_Key + magic_string |
| |
| import hashlib |
| import base64 |
| ac = base64.b64encode(hashlib.sha1(val.encode('utf-8')).digest()) |
| |
| |
| |
| 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') |
| |
| |
| conn.send(bytes(response_tpl, encoding='utf-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> |
| |
| |
| |
| |
| |
| |
| |
| |
| var ws = new WebSocket('ws://127.0.0.1:8000/') |
| |
| </script> |
| </body> |
| </html> |
WebSoket-Pthon框架使用WebSocket对应模块
| |
| |
| 1.django: 本身不支持通过 Channels实现 |
| |
| 文档:https://channels.readthedocs.io/en/stable/ |
| pip install channels -U |
| |
| |
| 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 |
| |
| 2.创建一个django项目 |
| django-admin startproject 项目名 |
| django-admin startapp app名称 |
| |
| |
| |
| 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: |
| |
| |
| |
| 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
| |
| |
| |
| 1.需要在asgi.py中配置websocket协议与路由设置 |
| |
| import os |
| |
| from channels.routing import URLRouter |
| from channels.auth import AuthMiddlewareStack |
| from channels.routing import ProtocolTypeRouter |
| from channels.security.websocket import AllowedHostsOriginValidator |
| |
| from django.urls import re_path |
| from django.core.asgi import get_asgi_application |
| from app01 import consumers |
| |
| os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") |
| application = ProtocolTypeRouter( |
| { |
| "http": get_asgi_application(), |
| |
| 'websocket':AllowedHostsOriginValidator( |
| AuthMiddlewareStack( |
| |
| |
| URLRouter( |
| [ |
| re_path('^chat/$',consumers.ChatConsumer.as_asgi()) |
| ] |
| ) |
| ) |
| ) |
| } |
| ) |
| |
| 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 |
| |
| |
| class ChatConsumer(WebsocketConsumer): |
| '''重写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> |
| |
| |
| var ws = new WebSocket('ws://127.0.0.1:8000/char/') |
| |
| |
| ws.onopen = function () { |
| console.log('链接成功') |
| } |
| |
| |
| ws.onmessage = function (event) { |
| |
| let msg = JSON.parse(event.data).msg |
| |
| document.getElementById('content').insertAdjacentHTML('beforeend', `<div>${msg}<div>`) |
| } |
| |
| |
| function send_msg() { |
| let msg_txt = document.getElementById('text').value |
| |
| ws.send(JSON.stringify({'msg': msg_txt})) |
| } |
| |
| |
| 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作用: |
| 完成服务端主动向客户端推送消息(轮询与长轮询是伪造的) |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!