目录
引入
在我们日常开发Web应用程序时,通常依赖HTTP协议来实现客户端与服务器之间的通信。在这种模式下,浏览器发起请求,服务器处理并响应这些请求。尽管这种请求-响应模型适用于大多数应用场景,但在某些情况下,比如直播间的实时聊天功能,它显得力不从心。具体来说,在一个直播间中,当一位用户发送消息后,其他观众需要即时看到这条信息,这就要求服务器能够主动向客户端推送更新,而不是被动地等待客户端发起请求。
针对上述情况,传统上可以通过两种基于HTTP的方法来模拟服务器端的推送能力:轮询(Polling)和长轮询(Long Polling)。然而,这两种方法效率较低,因为它们涉及到频繁的HTTP连接建立和断开过程,并且难以保证消息的即时性。
为了更高效、更直接地解决这个问题,WebSocket协议提供了一个理想的解决方案。WebSocket支持全双工通信,允许服务器主动向客户端推送数据,一旦连接建立,双方即可自由地进行双向数据交换,无需每次交互都重新建立连接。这使得WebSocket成为构建如实时聊天室、在线游戏或协作工具等需要低延迟、持续连接的应用的理想选择。
因此,对于类似直播间聊天框这样的场景,采用WebSocket不仅能显著提升用户体验,通过确保所有参与者能几乎同时接收到最新的消息,而且在性能上也更加高效,减少了不必要的网络负载和延迟。随着现代Web应用对实时交互需求的增长,WebSocket已经成为实现这些高级功能不可或缺的技术之一。下面将介绍这三种方法:
基础页面
先定义一个基本的页面,可在页面发送数据给后台:
urls.py:
"""
URL configuration for poll project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path("admin/", admin.site.urls),
path("home/", views.home),
path("send/msg", views.send_msg),
]
Home.html:
页面是通过 CDN 的方式加载 jquery ,当然也可以像之前那样,先下载下来,保存到静态文件中,然后引入;页面通过 Ajax 请求,将输入框输入的数据通过 GET 请求发送到特定路由,发送 GET 请求比较容易,发送 POST 请求则还得解决 CSRFtoken。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.message{
width: 200px;
height: 300px;
border: 1px,solid #ddddd;
}
</style>
</head>
<body>
<div class="message" ></div>
<div class="input">
<input type="text" placeholder="请输入" id="txt">
<input type="button" value="发送" onclick="sendMessage()">
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script>
function sendMessage(){
var text = $("#txt").val();
{#Ajax,使页面不刷新的情况下,将数据发送给后台#}
$.ajax({
{#发送url#}
url:'/send/msg',
type:'GET',
{#发送数据#}
data:{
text:text
},
{#请求发送成功后,会自动执行一下这个函数#}
success:function (res){
console.log("请求发送成功",res)
}
})
}
</script>
</body>
</html>
views.py:
创建路由后,通过 request.GET 来接收,创建一个列表充当临时数据库。
from django.http import HttpResponse
from django.shortcuts import render
# 充当数据库
DB = []
# Create your views here.
def home(request):
return render(request, 'Home.html')
# 接收页面发送的数据
def send_msg(request):
print("接收到客户端的请求:",end="")
print(request.GET)
text = request.GET['text']
DB.append(text)
return HttpResponse("OK")
轮询:
轮询的大致流程是这样的,对于不同的用户,当某一个用户发送了数据给后台,后台将进行接收,同时,前端会一直发送一个请求(一般间隔1s),用于获取后端接收到的数据,获取到就显示在前端页面,没获取到就继续发送请求。
轮询的实现使用的 Ajax + setInterval
对于输入框的数据,使用 Ajax 在页面不刷新的情况下,将数据发给后台:
function sendMessage(){
var text = $("#txt").val();
{#Ajax,使页面不刷新的情况下,将数据发送给后台#}
$.ajax({
{#发送url#}
url:'/send/msg',
type:'GET',
{#发送数据#}
data:{
text:text
},
{#请求发送成功后,会自动执行一下这个函数#}
success:function (res){
console.log("请求发送成功",res)
}
})
}
在 urls.py 中,定义路径:
path("send/msg", views.send_msg),
在视图函数中,接收到数据,存入数据库中(这里用列表代替数据库)
DB = []
# 接收页面发送的数据
def send_msg(request):
print("接收到客户端的请求:",end="")
print(request.GET)
text = request.GET['text']
DB.append(text)
return HttpResponse("OK")
此时,再发送请求到后端,请求数据,并且,请求数据只能是不重复的数据,即:假说第一次发送存储了一个“黑马”,其他用户发送请求,得到的是“黑马”;第二次发送了“程序员”,其他用户第二次发送请求,得到的应该是“程序员”,而不应该是“黑马”和“程序员”,只要请求的时候带一个最大索引参数即可。
其他用户发送请求:
{#每隔2s向后台发送请求#}
setInterval(function (){
{#发送请求获取数据#}
$.ajax({
url:'/get/msg',
type:'GET',
data:{
index:max_index
},
success:function (dataDict){
console.log("获取到数据:",dataDict);
{#将这个JSON字符串转换为JavaScript对象或数组#}
{#var dataDict = JSON.parse(res)#}
max_index = dataDict.index
$.each(dataDict.data,function (index,item){
console.log(index,item)
{#将内容拼接成div标签,并添加到message区域#}
{#document.createElement("div") 基于DOM#}
{#基于jquery#}
var tag = $("<div>");
tag.text(item)
$("#message").append(tag);
})
}
})
},2000)
这里每隔 2 s ,发送一次请求,能保证当 DB 列表有新值后,最慢也会在 2 s 后更新页面,这就是所谓的轮询,此时获取数据的视图函数:
def get_msg(request):
index = int(request.GET.get('index'))
print(index)
context = {
"data":DB[index:],
"index":len(DB)
}
return JsonResponse(context) #返回Json格式数据,序列化
视图函数将 data 和 index 以字典形式(index 为DB的长度,用于不重复索引),用JsonResponse 返回给前端,前端用 dataDict 接收,更新 max_index 为 index,再通过 jquery 构造 div ,添加到前端页面中,实现不同用户展示数据,从而实现轮询。
长轮询:
对于前面介绍的轮询,虽然是可行的,但是每隔 1 s 发送一次请求,会占用大量资源,导致服务器卡顿等。这时就可以使用长轮询,相较于轮询,长轮询的区别是前端发送的请求到后端,如果没有得到响应,不会立刻消失,而会等待几十秒,若在这几十秒内获取到了数据,则返回给前端,前端继续发请求;若没获取到数据,且等待响应超时,则取消前端请求与后台的连接,并重新再发送一个请求。
由于基本逻辑与轮询无异,这里展示源码及部分解释:
urls.py:
path("lang/", views.lang),
path("send/message", views.lang_send),
path("get/message", views.lang_get),
lang.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.message{
width: 200px;
height: 300px;
border: 1px solid #ddddd;
}
</style>
</head>
<body>
<div class="message" id="message"></div>
<div class="input">
<input type="text" placeholder="请输入" id="txt">
<input type="button" value="发送" onclick="sendMessage()">
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script>
var USER_UID = "{{ uid }}"
function sendMessage(){
{#.val()是取值#}
var text = $("#txt").val();
$.ajax({
url:'/send/message',
type:'GET',
data:{
text:text,
},
success:function (res){
console.log("lang的请求发送成功",res)
}
})
}
function getMessage(){
$.ajax({
url:'/get/message',
type:'GET',
data:{
uid:USER_UID
},
dataType:"JSON",
success: function (res){
console.log("get的请求发送成功",res)
if(res.status){
var tag = $("<div>");
tag.text(res.data);
$("#message").append(tag);
}
{#不管是得到了数据或者请求超时,都递归调用#}
getMessage();
}
})
}
$(function (){
getMessage();
})
</script>
</body>
</html>
views.py:
from django.http import HttpResponse,JsonResponse
from django.shortcuts import render
import json
import queue
from shapely.lib import length
# 充当数据库
DB = []
USER_QUEUE = {}
max_num = 0
# Create your views here.
def home(request):
return render(request, 'Home.html')
# 接收页面发送的数据
def send_msg(request):
print("接收到客户端的请求:",end="")
print(request.GET)
text = request.GET['text']
DB.append(text)
return HttpResponse("OK")
def get_msg(request):
index = int(request.GET.get('index'))
print(index)
context = {
"data":DB[index:],
"index":len(DB)
}
return JsonResponse(context) #返回Json格式数据,序列化
def lang(request):
uid = request.GET.get('uid')
print("主页uid为:",uid)
USER_QUEUE[uid] = queue.Queue()
return render(request,"lang.html",{"uid":uid})
def lang_send(request):
text = request.GET.get('text')
print("接收到了:",text)
for uid,q in USER_QUEUE.items():
q.put(text)
return HttpResponse("OK")
def lang_get(request):
uid = request.GET.get('uid')
q = USER_QUEUE[uid] # 获取本用户对应的队列
result = {'status':True,'data':None}
try:
data = q.get(timeout=10)
result['data'] = data
except queue.Empty as e:
result['status'] = False
return JsonResponse(result)
下文将介绍另一种协议--WebSocket原理及其使用案例。
感谢您的三连!!!