阳江市建设网站,定制产品,网站宣传的方法有哪些,外贸零售网站建设目录 背景简介个人见解被动推送轮询简介实现 长轮询#xff08;comet#xff09;简介实现 比较 主动推送长连接#xff08;SSE#xff09;简介实现GETPOST 效果 webSocket简介WebSocket的工作原理:WebSocket的主要优点:WebSocket的主要缺点: 实现用法一用法二 **效果** 比较… 目录 背景简介个人见解被动推送轮询简介实现 长轮询comet简介实现 比较 主动推送长连接SSE简介实现GETPOST 效果 webSocket简介WebSocket的工作原理:WebSocket的主要优点:WebSocket的主要缺点: 实现用法一用法二 **效果** 比较 背景
服务端和客户端应该怎么通信才能实现客户端能获取服务端最新消息让用户有更好的交互体验如果是正常的发送一个请求首先要建立TCP连接然后等到服务器返回如果是开发者可以通过发包情况就能知道建立连接成功与否是否是在等待服务器响应但是做为非开发者的普通用户当他点击一个按钮却没有任何反应他会怀疑是不是没点到还是卡住了之类了。不是一直点就是点到暴躁的放弃不仅造成服务器的负担而且用户体验极差。也许我们可以在前端做一个虚假的转圈动画让客户知道正在处理但是如果是个需要处理1小时的任务没有个进度条他也不知道是否值得等待。又假如我们做一个投票系统或者一个聊天室我们要怎么让屏幕前的另一个彦祖及时看到呢
简介
Web端即时通讯技术 服务器端可以即时地将数据的更新或变化反应到客户端例如消息即时推送等功能都是通过这种技术实现的。但是在Web中由于浏览器的限制实现即时通讯需要借助一些方法。这种限制出现的主要原因是一般的Web通信都是浏览器先发送请求到服务器服务器再进行响应完成数据的现实更新。
实现Web端即时通讯的方法 实现即时通讯主要有四种方式它们分别是轮询、长轮询(comet)、长连接(SSE)、WebSocket。它们大体可以分为两类一种是在HTTP基础上实现的包括短轮询、comet和SSE另一种不是在HTTP基础上实现是即WebSocket。下面分别介绍一下这四种轮询方式以及它们各自的优缺点。
个人见解
以下纯属个人见解与实操经验如有不当之处可以联系修改感谢 有空再专门详细写一篇理论补充知识这个得从计算机网络的传输层协议开始说起了特别是websocket协议。他不是简单的三次握手在这之后还有使用魔法字符串和key加密。这篇文偏向实操所见即所得方便上手。 以下使用python的web框架的django实现原理一样实现大同小异尽量注释说明清楚有不清楚的也可以联系我解答。
被动推送
轮询
简介
短轮询的基本思路就是浏览器每隔一段时间向浏览器发送http请求服务器端在收到请求后不论是否有数据更新都直接进行响应。这种方式实现的即时通信本质上还是浏览器发送请求服务器接受请求的一个过程通过让客户端不断的进行请求使得客户端能够模拟实时地收到服务器端的数据的变化。
这种方式的优点是比较简单易于理解实现起来也没有什么技术难点。缺点是显而易见的这种方式由于需要不断的建立http连接严重浪费了服务器端和客户端的资源。尤其是在客户端距离来说如果有数量级想对比较大的人同时位于基于短轮询的应用中那么每一个用户的客户端都会疯狂的向服务器端发送http请求而且不会间断。人数越多服务器端压力越大这是很不合理的。
注意注意 重点是什么之前为了实现一个毫秒级的进度条打开**开发者模式(F12)**后一秒发出了一千次请求不说给服务器造成了什么样压力(是我的服务器我直接把你拉黑了)重点是不前面的请求记录一瞬间顶到天上去十分不方便调试
实现
定义轮询方法
function renovate(t) {console.log(come in )var sitv setInterval(function () {var prog_url /renovate?t t$.getJSON(prog_url, function (num_progress) {if (num_progress 0) {console.log(正在讀取中)$(.progress-bar).css(width, 20%);$(.progress-bar).text(正在讀取中);} else if (num_progress 99) {console.log(come in 99)clearInterval(sitv);$(.progress-bar).css(width, 99%);$(.progress-bar).text(99%);} else {console.log(t: t num_progress num_progress)$(.progress-bar).css(width, num_progress %);$(.progress-bar).text(num_progress %);}});}, 1000); // 每1000毫秒查询一次后台进度}调用轮询
$(function () {bindBtnAddEvent();})function bindBtnAddEvent() {$(#save).click(function () {{#alert(開始)#}var t $.now()renovate(t)});}这里我专门在后端写了个接口来读取数据库数据给前端获取很简单就不写出来了传了个时间参数是为了在后端构建一个字典使得获取到正确的属于该用户的进度条不然可能出现如果两个人同时访问脏数据的可能就以防万一。
长轮询comet
简介
ajax实现: 当服务器收到客户端发来的请求后,服务器端不会直接进行响应而是先将这个请求挂起然后判断服务器端数据是否有更新。如果有更新则进行响应如果一直没有数据则到达一定的时间限制(服务器端设置)才返回。。 客户端JavaScript响应处理函数会在处理完服务器返回的信息后再次发出请求重新建立连接。
简单来说就是服务端维持一个消息队列当收到一个请求就检查消息队列看有没有数据有就把数据取出来返回给客户端因为队列是先进先出所以顺序是对的没有数据就hold住这个请求一会众所周知TCP连接后还有一个响应时间如果在响应时间也就是还没响应时当你看开发者模式这时的status就不是状态码而是pending。
举例理解一下
requests.get(www.baidu.com,timeout(5,10))这里的代码是一个简单的请求设置的这个timeout是一个元组的形式第一个参数为连接超时第二个是响应超时这里我们设置10秒当10秒后没有响应这个连接也结束了这样就不会hold住。
实现
因为是简单实现一个demo所以其实里面有很多不完善的地方不要纠结细节。 该demo是实现一个聊天室 urls.py(这里是配置路由的就是接口)
from django.conf import settings
from django.contrib import admin
from django.urls import path, re_path
from django.views.static import servefrom app01.views import chaturlpatterns [path(longPoll/chat/, chat.longPoll_chat),path(send/msg/, chat.send_msg),path(get/msg/, chat.get_msg),
]效果
view.py(也就是视图函数啥框架都有的处理接口的那个)
注意这里用全局变量是不合理的就是demo而已深度建议用redis的发布与订阅实现一是减少开销不用给每个新用户建一个队列浪费空间二是redis比较快
import queuefrom django.shortcuts import render
from django.http import JsonResponse# 这里用python的队列来模拟。也可以使用redis的发布和订阅来实现则不需要建那么多队列所有访客都可以访问同一个消息队列
USER_QUEUE {} # {asd:queue.Queue(),qwe:queue.Queue()} 为每一个访客建一个队列def longPoll_chat(request): 展示聊天界面 uid request.GET.get(uid)USER_QUEUE[uid] queue.Queue() # 来了个彦祖为他创建一个队列return render(request, longPoll_chat.html, {uid: uid})def send_msg(request): 发送消息接口当用户在聊天室发一句话点击事件就会触发这个 text request.GET.get(text) # 获取文本for uid, q in USER_QUEUE.items(): # 遍历所有的队列q.put(text) # 为每一个队列添加消息为了让这个聊天室的每个用户消息同步return JsonResponse({msg: ok})def get_msg(request): 获取消息就是看这个聊天室里面有没有其他人发了消息有就接收以下 uid request.GET.get(uid)q USER_QUEUE[uid] # 获取自己的队列result {status: True, data: None}try:# 这里就是 hold 请求了监听队列10s如果有数据就能get到如果10s过了还没有就会触发 except queue.Empty不同的status发送到前端前端就可以判断了data q.get(timeout10) # 注意这里如果不设置timeout会一直等下去程序会卡在这里等的花都谢了result[data] dataexcept queue.Empty as e:result[status] Falsereturn JsonResponse(result)longPoll_chat.html这里就是前端部分了只是前后端不分离用了一些模板引擎的语法但是光看js怎么发送请求应该就懂了html怎么写不太重要自己修改即可
{% extends layout.html %}{% block css %}style.message {height: 300px;border: 1px solid #dddddd;width: 100%;}/style
{% endblock %}{% block content %}div classmessage idmessage/divdivinput typetext placeholder请输入 idtxtinput typebutton value发送 onclicksendMessage(); # 这里给这个按钮绑定点击事件/div
{% endblock %}{% block js %} // 这是模板引擎的语法就继承模板的那个挖洞有点类似vue的组件和插槽的感觉scriptUSER_UID {{ uid }}; // 这是模板引擎的语法就是接收后端发过来的东西有点类似vue的那个插值表达式const MAX_REQUESTS 5; // 最大请求次数主要是不想看他一直发你可以写不同的逻辑let requestCount 0; // 请求计数器function sendMessage() {let text $(#txt).val();// 获取文本jQuery没啥好说的了不是这篇文的重点后面有关jQuery的就不注释说明了$.ajax({url: /send/msg/,type: GET,data: {text: text},success: function (res) {console.log(请求发送成功, res)}})}function getMessage() {$.ajax({url: /get/msg/,type: GET,data: {uid: USER_UID,},dataType: JSON,success: function (res) {// 超时没有数据也就是status为False不是True自然是False就不写了// 有新数据暂时信息数据if (res.status) {//将内容拼成div标签并添加到message区域var tag $(div);tag.text(res.data) //div啊大大/div$(#message).append(tag);}// 请求次数requestCount;// 检查是否达到最大请求次数if (requestCount MAX_REQUESTS) {console.log(达到最大请求次数结束长轮询);return; // 结束长轮询}getMessage();//自己调用自己JS该模式实际不是递归不会栈溢出}})}$(function () {getMessage();})/script
{% endblock %}轮询与长轮询都是基于HTTP的两者本身存在着缺陷:轮询需要更快的处理速度长轮询则更要求处理并发的能力;两者都是“被动型服务器”的体现:服务器不会主动推送信息而是在客户端发送ajax请求后进行返回的响应。而理想的模型是在服务器端数据有了变化后可以主动推送给客户端,这种主动型服务器是解决这类问题的很好的方案。 注意 但是啊虽然websocket这种双向通信非常实用但是旧浏览器以及部分老设备的客户端不支持websocket协议所以一般大公司(eg:微信)是使用长轮询比较通用。虽然使用更多内存会占用服务器资源但是人家家大业大不怕人家追求稳定通用。 比较
长轮询和短轮询比起来明显减少了很多不必要的http请求次数相比之下节约了资源。长轮询的缺点在于连接挂起也会导致资源的浪费。
主动推送
长连接SSE
简介
Server-Sent EventsSSE 是一种用于实现服务器向客户端实时推送数据的Web技术。与传统的轮询和长轮询相比SSE提供了更高效和实时的数据推送机制。
SSE基于HTTP协议允许服务器将数据以事件流Event Stream的形式发送给客户端。客户端通过建立持久的HTTP连接并监听事件流可以实时接收服务器推送的数据。
SSE的主要特点包括
简单易用SSE使用基于文本的数据格式如纯文本、JSON等使得数据的发送和解析都相对简单。单向通信SSE支持服务器向客户端的单向通信服务器可以主动推送数据给客户端而客户端只能接收数据。实时性SSE建立长时间的连接使得服务器可以实时地将数据推送给客户端而无需客户端频繁地发起请求。
进行SSE实时数据推送时的注意点
异步处理 由于SSE是基于长连接的机制推送数据的过程是一个长时间的操作。为了不阻塞服务器线程推荐使用异步方式处理SSE请求。您可以在控制器方法中使用**Async注解或使用CompletableFuture**等异步编程方式。超时处理 SSE连接可能会因为网络中断、客户端关闭等原因而发生超时。为了避免无效的连接一直保持在服务器端您可以设置超时时间并处理连接超时的情况。可以使用SseEmitter对象的setTimeout()方法设置超时时间并通过onTimeout()方法处理连接超时的逻辑。异常处理 在实际应用中可能会出现一些异常情况如网络异常、推送数据失败等。您可以使用SseEmitter对象的completeWithError()方法将异常信息发送给客户端并在客户端通过eventSource.onerror事件进行处理。内存管理 使用SseEmitter时需要注意内存管理特别是在大量并发连接的情况下。当客户端断开连接时务必及时释放SseEmitter对象避免造成资源泄漏和内存溢出。并发性能 SSE的并发连接数可能会对服务器的性能造成影响。如果需要处理大量的并发连接可以考虑使用线程池或其他异步处理方式以充分利用服务器资源。客户端兼容性 虽然大多数现代浏览器都支持SSE但仍然有一些旧版本的浏览器不支持。在使用SSE时要确保您的目标客户端支持SSE或者提供备用的实时数据推送机制。 这些注意点将有助于您正确和高效地使用SseEmitter进行SSE实时数据推送。根据具体的应用需求您可以根据实际情况进行调整和优化。
实现
SSE的优点就是实现比较简单跟HTTP基本一样所以就不需要改动太多
view.py(直接在你原来用HTTP那个处理逻辑上修改就行了基本一样)
from django.http import HttpResponse, JsonResponse, StreamingHttpResponsedef test(request): 该实现我采用两种返回结果因为考虑到可能会有客户端不支持SSE前端判断支持之后再使用流式传输 data json.loads(request.body.decode())text_list data.get(text) # [{},{}]# 没有该标志采用JSON传输if request.GET.get(stream) is None:# 这就是你原来的处理逻辑了# 这里是个demo就简单做一个保存数据库你可以根据需求写你自己的逻辑id_list []for text in text_list:id User.objects.create(**text)id_list.append(id)time.sleep(60) # 这里模拟一下耗时操作你的逻辑可能就是这里导致处理特别慢循环多少次就要多少个一分钟了# 一次性返回假如要一小时来执行上面的逻辑用户傻傻等待一小时快要睡着了以为还没开始然后突然跟你说全部处理好了简直人麻了return JsonResponse({code: 200, data: id_list, msg: 保存成功})# 采用流式传输elif request.GET.get(stream) is True or request.GET.get(stream) true:def sse_stream(): 原来的处理逻辑 # 闭包可以获取外部数据# 这里是个demo就简单做一个保存数据库你可以根据需求写你自己的逻辑for text in text_list:id User.objects.create(**text)time.sleep(60) # 这里模拟一下耗时操作你的逻辑可能就是这里导致处理特别慢不管循环多少次至少一分钟用户就能得到响应而不是循环次数*一分钟后才得到响应data_dict {code: 200,message: 有一个成功了,data: id}# 返回部分数据给用户查看# 注意了SSE传输的数据格式必须是这样# data: 你的数据\n\n 必须是data开头\n\n结束sse_message data: {}\n\n.format(json.dumps(data_dict))yield sse_message # 这里就是不一样的地方了先返回一段去给用户看,直接就知道是哪个好了# return # 你的逻辑中那些try...exceptif...else之类的想要服务器主动中断链接的这里就直接return就行了# 这是第二个区别了这是在闭包之外也就是真正返回响应的地方这里把JsonResponse换成StreamingHttpResponse就可以进行流式传输# 注意content_type必须是text/event-streamreturn StreamingHttpResponse(sse_stream(), content_typetext/event-stream)else:# 带了标记但是不是true也不管他return responseJson(400, None, 流式传输参数错误)客户端重新连接策略:
yield retry: 5000\n\n # 5 seconds所有数据发送完成时 当服务器发送完所有数据并且生成器函数结束时连接将自动关闭。在这种情况下除非客户端尝试重新连接否则连接不会再次建立。
使用return在生成器函数中使用return将会结束该函数从而结束SSE连接。如果客户端被设置为在连接断开时自动重连这是默认行为它可能会尝试重新连接。您可以通过发送一个特定的retry值来控制或禁止这种行为。例如yield “retry: 0\n\n” 会告诉客户端在连接断开时不要重新连接。
yield retry: 0\n\n总的来说当生成器函数不再产生输出时SSE连接将结束无论是由于函数自然结束还是由于某个return语句。
前端js
GET
// 使用GET请求接收流式数据的类
class StreamDataFetcherGET {constructor(endpoint) {this.endpoint endpoint;}// 初始化并监听数据init() {// 创建一个新的EventSource实例连接到提供的endpointconst evtSource new EventSource(this.endpoint);// 当从服务器收到新的数据时evtSource.onmessage (event) {const data JSON.parse(event.data);this.renderData(data);};// 当与服务器的连接发生错误时evtSource.onerror (error) {console.error(EventSource failed:, error);evtSource.close();};}// 渲染接收到的数据renderData(data) {// 这里的代码取决于如何渲染数据到你的页面上console.log(data);}
}// 使用GET请求的示例
const fetcherGET new StreamDataFetcherGET(请求接口);
fetcherGET.init();还可以先判断是否客户端支持是否支持SSE(下面POST请求同理)示例如下
// 我后台配置了只有带上?streamtrue采用流式传输你先判断浏览器类型支持SSE不IE系列都不支持然后特别老的Edge和狗都不用的浏览器也不支持如果他不支持就不要走这个方法走你原来那个函数我就不cv了// 检查是否支持SSE
if (typeof EventSource ! undefined) {// 支持SSE所以请求流式数据const evtSource new EventSource(SSE的请求接口);evtSource.onmessage function(event) {const data JSON.parse(event.data);console.log(data);};evtSource.onerror function(error) {console.error(EventSource failed:, error);evtSource.close(); // 断开SSE连接};
} else {// 不支持SSE所以请求JSON数据fetch(JSON的请求接口).then(response response.json()).then(data {console.log(data);}).catch(error {console.error(Error fetching JSON data:, error);});
}
POST
对于POST请求EventSource不适用因为它仅支持GET请求。使用POST请求处理SSE需要一些额外的工作如下所示
// 使用POST请求接收流式数据的类
class StreamDataFetcherPOST {constructor(endpoint, postData) {this.endpoint endpoint;this.postData postData;}// 初始化并监听数据async init() {// 使用fetch进行POST请求const response await fetch(this.endpoint, {method: POST,headers: {Content-Type: application/json},body: JSON.stringify(this.postData)});const reader response.body.getReader();const decoder new TextDecoder();let buffer ;while (true) {const { done, value } await reader.read();if (done) break;buffer decoder.decode(value, { stream: true });while (buffer.includes(\n)) {const lineEnd buffer.indexOf(\n);const line buffer.slice(0, lineEnd).trim();buffer buffer.slice(lineEnd 1);this.renderData(JSON.parse(line));}}}// 渲染接收到的数据renderData(data) {// 这里的代码取决于如何渲染数据到你的页面上console.log(data);}
}// 使用POST请求的示例
const postData { key: value }; // 替换为你的POST数据
const fetcherPOST new StreamDataFetcherPOST(请求接口, postData);
fetcherPOST.init();不用类
async function createStreamFetcherPOST(endpoint, postData) {const response await fetch(endpoint, {method: POST,headers: {Content-Type: application/json},body: JSON.stringify(postData)});const reader response.body.getReader();const decoder new TextDecoder();let resultString ;while (true) {const { done, value } await reader.read();if (done) break;resultString decoder.decode(value, { stream: true });while (resultString.includes(\n\n)) {const lineEnd resultString.indexOf(\n\n);const line resultString.slice(0, lineEnd).trim();// 检查是否有retry: 0消息if (fullMessage retry: 0) {console.log(Received retry: 0. Stopping connection...);reader.cancel(); // 取消读取这将导致流结束并跳出循环return; // 结束处理函数}try {const payload JSON.parse(line.replace(/^data: /, ));renderData(payload);} catch (e) {console.error(Error parsing segment:, e);}resultString resultString.slice(lineEnd 2);}}function renderData(data) {console.log(data);if (payload.code 200) {// 处理数据简单就设个变量累加就知道数量不然渲染逻辑传参就在这} else {// 错误的简单就不累加咯}}
}// 使用POST请求的示例
const postData { key: value }; // 替换为您的POST数据
createStreamFetcherPOST(your_POST_endpoint_url_here, postData);
效果
这个EverntStream 就是用了SSE的
webSocket
注意其他都是基于HTTP协议而websocket是基于TCP协议
简介
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455并由RFC7936补充规范。WebSocket API也被W3C定为标准。 WebSocket使得客户端和服务器之间的数据交换变得更加简单允许服务端主动向客户端推送数据。在WebSocket API中浏览器和服务器只需要完成一次握手两者之间就直接可以创建持久性的连接并进行双向数据传输。
WebSocket的工作原理:
客户端发起WebSocket连接,通过HTTP协议发起Upgrade请求将连接升级为WebSocket连接。服务器端响应Upgrade请求,完成连接升级。建立WebSocket连接后,客户端和服务器端就可以通过这个连接通道自由地双向传输数据,无需等待对方的请求。
WebSocket的主要优点:
允许全双工通信: 客户端和服务器端都可以主动发送数据。更实时: 服务器有新数据可以立即主动推送给客户端。更轻量: 建立连接的开销小,通信高效,减少不必要的网络请求。利用HTTP协议做升级握手,默认端口是80和443,避免了跨域问题。支持扩展,可以扩展自定义的子协议。
WebSocket的主要缺点:
不如HTTP协议广泛应用,存在兼容性问题。需要浏览器和服务器端都支持WebSocket协议,增加了开发成本。有连接建立和关闭的开销,不适用于量小数据的交互。安全性需要额外考虑,通信内容是明文,需要加密。处于连接状态时,会占用服务器端资源。 简介就不多说了哈~大概图解一下就行了他这个连接过程要详细知道得单独写一篇直接进入实操。
实现 因为是基于django来实现的所以有些东西该配置还得配置不同框架这个配置确实就不一样了但是前端是一样的 安装库
这里可能会遇到一些问题因为channels需要和django版本对应上如有问题可以参考我的另一篇文章django使用channels实现webSocket启动失败求顺手一赞嘿嘿。
pip install channels在settings.py同级目录下新增asgi.py文件(django4自带了)和routing.py文件内容稍后说
目录结构如下
settings.py(配置文件)
INSTALLED_APPS [django.contrib.admin,django.contrib.auth,django.contrib.contenttypes,django.contrib.sessions,django.contrib.messages,django.contrib.staticfiles,channels, # 注册这个app01.apps.App01Config,
]WSGI_APPLICATION staffSystem_django.wsgi.application # 原本只有这个东西这个不去掉也可以
ASGI_APPLICATION staffSystem_django.asgi.application # 新增这个就是你的asgi目录所在地# channels 配置存在内存中
CHANNEL_LAYERS {default: {BACKEND: channels.layers.InMemoryChannelLayer,}
}
# channels 配置存在redis中
# CHANNEL_LAYERS {
# default: {
# BACKEND: channels_redis.core.RedisChannelLayer, # pip install channels-redis
# CONFIG: {
# hosts: [(127.0.0.1, 6379)]
# # hosts: [redis://127.0.0.1:6379/1]
# },
# }
# }这里建议 channels 配置存在redis中这只是demo才配在内存中在setting.py修改这个配置就行了其他不用变
routing.py配置使用websocket的路由
# Author: fbz
# File : routing.py
from django.urls import path
from app01.views import chatwebsocket_urlpatterns [path(rws/group/,chat.wsChat.as_asgi()),
]asgi.py配置asgi ASGI config for staffSystem_django project.It exposes the ASGI callable as a module-level variable named application.For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
import osfrom django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from . import routingos.environ.setdefault(DJANGO_SETTINGS_MODULE, staffSystem_django.settings)# application get_asgi_application() # 如果这样配置就收不到http请求了application ProtocolTypeRouter({http: get_asgi_application(), # http请求走这个websocket: URLRouter(routing.websocket_urlpatterns) # ws请求走这个
})urls.py
staffSystem_django URL ConfigurationThe urlpatterns list routes URLs to views. For more information please see:https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views1. Add an import: from my_app import views2. Add a URL to urlpatterns: path(, views.home, namehome)
Class-based views1. Add an import: from other_app.views import Home2. Add a URL to urlpatterns: path(, Home.as_view(), namehome)
Including another URLconf1. Import the include() function: from django.urls import include, path2. Add a URL to urlpatterns: path(blog/, include(blog.urls))from django.conf import settings
from django.contrib import admin
from django.urls import path, re_path
from django.views.static import servefrom app01.views import depart, user, account, admin, pretty, task, order, chart, upload, city, chaturlpatterns [path(ws/chat/, chat.ws_chat),
]配置完成启动django项目的效果
ws_chat.html前端
{% extends layout.html %}
{% block css %}style.message {height: 300px;border: 1px solid #dddddd;width: 100%;}/style
{% endblock %}{% block content %}div classmessage idmessage/divdivinput typetext placeholder请输入 idtxtinput typebutton value发送 onclicksendMessage();input typebutton value关闭连接 onclickcloseConn();/div
{% endblock %}{% block js %}script// socket new WebSocket(ws://127.0.0.1:8000/ws/123/);socket new WebSocket(ws:// window.location.host /ws/{{group_id}}/);// 创建好连接之后自动触发(服务端执行self.accept())socket.onopen function (event) {let tag document.createElement(div);tag.innerText [连接成功];document.getElementById(message).appendChild(tag);}// 当websocket接收到服务端发来的消息时自动会触发这个函数socket.onmessage function (event) {let tag document.createElement(div);tag.innerText event.data;document.getElementById(message).appendChild(tag);}// 服务端主动断开连接时自动会触发这个方法socket.onclose function (event) {let tag document.createElement(div);tag.innerText [断开连接];document.getElementById(message).appendChild(tag);}function sendMessage() {let tag document.getElementById(txt);socket.send(tag.value);}function closeConn() {socket.close(); //向服务端发送断开连接的请求}/script
{% endblock %}用法一
基本每一行都注释说明了真是极致详细
from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
from asgiref.sync import async_to_syncdef ws_chat(request):return render(request, ws_chat.html) # 显示界面class wsChat(WebsocketConsumer):def websocket_connect(self, message):# 有客户端来向后端发送ws连接的请求时自动触发。self.accept() # 服务端允许和客户端创建连接print(连接成功)# 服务端允许和客户端创建连接(握手)self.accept() # 同时请求WebSocket HANDSHAKING和WebSocket CONNECT分别是握手和连接def websocket_receive(self, message):# 浏览器基于ws向后端发送数据自动触发接收消息print(message)self.send(asdasd)# self.close() # 服务端主动断开连接text message[text] # {type: websocket.receive, text: asd}if text close:# 服务端主动断开连接self.close() # 同时也会执行客户端断开连接方法 WebSocket DISCONNECT(websocket_disconnect())return # 不再执行下面的代码如果断开连接还发送消息会报错# raise StopConsumer() #如果服务端断开连接时执行raise StopConsumer()那么不会执行websocket_disconnect()方法# print(接收到消息, text)self.send(f接收到消息{text}) # 服务端给客户端发送消息def websocket_disconnect(self, message):# 客户端与服务端端开连接时自动触发(客户端主动端开连接)print(断开连接)raise StopConsumer() # WebSocket DISCONNECT用法二
使用了组这也是django特有的像flask不是使用channels实现websocket的 他没有组这个概念。
修改上面的类
class wsChat(WebsocketConsumer):def websocket_connect(self, message):# 有客户端来向后端发送ws连接的请求时自动触发。print(连接成功)# 获取群号获取路由匹配中的group self.scope[url_route][kwargs].get(group)# 服务端允许和客户端创建连接(握手)self.accept() # 同时请求WebSocket HANDSHAKING和WebSocket CONNECT分别是握手和连接# 将这个客户端的连接对象加入到内存或redis中取决于setting.py中CHANNEL_LAYERS# async_to_sync将异步转为同步async_to_sync(self.channel_layer.group_add)(group, self.channel_name)def websocket_receive(self, message):# 浏览器基于ws向后端发送数据自动触发接收消息text message[text] # {type: websocket.receive, text: asd}if text close:# 服务端主动断开连接self.close() # 同时也会执行客户端断开连接方法 WebSocket DISCONNECT(websocket_disconnect())return # 不再执行下面的代码如果断开连接还发送消息会报错# raise StopConsumer() #如果服务端断开连接时执行raise StopConsumer()那么不会执行websocket_disconnect()方法# self.send(f接收到消息{text}) # 服务端给客户端发送消息# 获取群号获取路由匹配中的group self.scope[url_route][kwargs].get(group)# 通知组内的所有客户端执行 xx_oo 方法在此方法中自己可以去定义任意的功能async_to_sync(self.channel_layer.group_send)(group, {type: aa.bb, message: message}) # aa_bb,下划线变成点def aa_bb(self, event):text event[message][text]# 这是给组内的所有人发送消息在websocket_receive中的self.send(text)才是给当前这个人发送消息self.send(text)def websocket_disconnect(self, message):print(断开连接)# 获取群号获取路由匹配中的group self.scope[url_route][kwargs].get(group)async_to_sync(self.channel_layer.group_discard)(group, self.channel_name)# 客户端与服务端端开连接时自动触发(客户端主动端开连接)raise StopConsumer() # WebSocket DISCONNECT该类实现了一个功能就是只有同个组的成员才可以收到消息也就是我们的群聊功能只有当群号(组号)一样的聊天室才可以收到同一个群的其他成员发送的消息。
效果
注意注意看请求头必须携带这些信息才能建立一个ws连接
服务器需要检查Upgrade头信息,如果支持WebSocket,就返回101状态码和Upgrade头信息,表示切换协议。
WebSocket 协议在建立连接时的 Sec-WebSocket-Key 头信息和魔法字符串的相关验证过程。这主要是为了防止恶意的 WebSocket 请求。 具体过程是:
客户端发起 WebSocket 请求时,需要在请求头包含 Sec-WebSocket-Key 字段,内容为一个随机的字符串。服务器端收到请求后,会将客户端发来的 Sec-WebSocket-Key 后的值与一个特定的魔法字符串(magic_string) “258EAFA5-E914-47DA-95CA-C5AB0DC85B11” 拼接。对拼接后的字符串做 SHA-1 摘要,然后进行 BASE-64 编码,得到一个 hash 值。服务器端需要在响应头的 Sec-WebSocket-Accept 字段设置这个 hash 值。客户端收到响应后,也按相同的算法计算出一个 hash 值,与服务器返回的 Sec-WebSocket-Accept 值进行比较。如果两个 hash 值一致,则验证通过,确认服务器端支持 WebSocket 协议,然后建立连接。
这个验证过程可以防止普通的 HTTP 客户端与服务器建立 WebSocket 连接,保证了连接的安全性。所以它是 WebSocket 协议握手过程中的一个重要组成部分。 比较
SSE与WebSocket的比较 WebSocket是另一种用于实现实时双向通信的Web技术它与SSE在某些方面有所不同。下面是SSE和WebSocket之间的比较
数据推送方向 SSE是服务器向客户端的单向通信服务器可以主动推送数据给客户端。而WebSocket是双向通信允许服务器和客户端之间进行实时的双向数据交换。连接建立 SSE使用基于HTTP的长连接通过普通的HTTP请求和响应来建立连接从而实现数据的实时推送。WebSocket使用自定义的协议通过建立WebSocket连接来实现双向通信。兼容性由于SSE基于HTTP协议它可以在大多数现代浏览器中使用并且不需要额外的协议升级。WebSocket在绝大多数现代浏览器中也得到了支持但在某些特殊的网络环境下可能会遇到问题。适用场景 SSE适用于服务器向客户端实时推送数据的场景如股票价格更新、新闻实时推送等。WebSocket适用于需要实时双向通信的场景如聊天应用、多人协同编辑等。
根据具体的业务需求和场景选择SSE或WebSocket取决于您的实际需求。如果您只需要服务器向客户端单向推送数据并且希望保持简单易用和兼容性好那么SSE是一个不错的选择。如果您需要实现双向通信或者需要更高级的功能和控制那么WebSocket可能更适合您的需求。