WebSocket是一种在单个TCP连接上进行全双工通信的协议。它由IETF在2011年定为标准RFC 6455,并由RFC7936补充规范,同时WebSocket API也被W3C定为标准。
1、定义与原理
WebSocket是独立的、创建在TCP上的协议,它使用HTTP/1.1协议的101状态码进行握手。为了创建WebSocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为"握手"(handshaking)。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。
2、特点
- 双向通信:WebSocket支持客户端和服务器之间的实时双向通信,而传统的HTTP协议是单向请求-响应模式。
- 实时性强:由于协议是全双工的,服务器可以随时主动给客户端下发数据,相对于HTTP请求需要等待客户端发起请求服务端才能响应,WebSocket的延迟明显更少。
- 保持连接状态:WebSocket需要先创建连接,因此是一种有状态的协议,之后通信时可以省略部分状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)。
- 控制开销小:在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。相对于HTTP请求每次都要携带完整的头部,WebSocket的开销显著减少了。
- 支持二进制:WebSocket定义了二进制帧,可以更轻松地处理二进制内容。
- 支持扩展:WebSocket定义了扩展,用户可以扩展协议、实现部分自定义的子协议,如部分浏览器支持压缩等。
- 更好的压缩效果:相对于HTTP压缩,WebSocket在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率。
3.案列
(1)先创建一个django项目(cmd创建或者直接pychram点击创建)
这里我们需要添加channels插件
pip install channels==3.0
在创建一个app用来访问页面,顺便创建需要用到的文件
(2)settings.py (配置一下环境)
在apps里面注册一下
在添加以下配置
python
ASGI_APPLICATION = "websocket.asgi.application"//"websocket"是我的项目名
#channels自带的存储
CHANNEL_LAYERS = {
"default":{
"BACKEND": "channels.layers.InMemoryChannelLayer",
}
}
运行检查一下,配置成功的话会有ASGI/Channels的标识(不对的话就切换成3.0的channels版本)
(3)项目目录下 asgi.py
这里主要是用来区分是http协议还是websocket协议
python
import os
from channels.routing import ProtocolTypeRouter,URLRouter
from django.core.asgi import get_asgi_application
from websocket import routings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'study_websocket.settings')
#application = get_asgi_application()
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket":URLRouter(routings.websocket_urlpatterns),
})
(4) 项目目录routings.py
python
from django.urls import re_path
from app import consumers
websocket_urlpatterns = [
re_path(r'room/(?P<group>\w+)/$',consumers.ChatConsumer.as_asgi()),
]
(5)创建一个路由来访问聊天页面
python
from django.contrib import admin
from django.urls import path
from app import views
urlpatterns = [
path('admin/', admin.site.urls),
path('',views.index),
]
python
from django.shortcuts import render
# Create your views here.
def index(request):
num = request.GET.get('num')
return render(request,"home.html",{"num":num})
tempaltes文件创建一个home.html
python
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.message {
height: 500px;
border: 1px solid #dddd;
width: 100%;
}
</style>
</head>
<body>
<div class="message" id="message"></div>
<div>
<input type="text" placeholder="请输入" id="txt">
<input type="button" value="发送" onclick="sendMessage()">
<input type="button" value="关闭" onclick="closeCount()">
</div>
</body>
<script>
//创建连接
socket = new WebSocket("ws://127.0.0.1:8000/room/{{ num }}/");
//创建好连接之后自动触发(服务端执行self.accept())
socket.onopen = function (event){
let tag= document.createElement("div")
tag.innerText = "[连接成功]";
document.getElementById("message").appendChild(tag);
}
//当websocket接收到服务端发来的消息时,自动触发这个函数
socket.onmessage = function (event){
console.log(event.data)
let tag= document.createElement("div")
tag.innerText = event.data;
document.getElementById("message").appendChild(tag);
}
function sendMessage(){
let tag=document.getElementById("txt");
socket.send(tag.value);
}
function closeCount(){
socket.close()//关闭
}
</script>
</html>
(6)consumers.py
python
from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
from asgiref.sync import async_to_sync
class ChatConsumer(WebsocketConsumer):
def websocket_connect(self, message):
#有客户端来向后台发送websocket连接请求时,自动触发
#服务端允许客户端创建连接
print("有人连接")
self.accept()
#获取群号
group = self.scope['url_route']['kwargs'].get("group")
#将这个客户端连接对象加入到内存
#channel_layer.group_add这个方法默认是异步的所有需要async_to_sync转成同步
async_to_sync(self.channel_layer.group_add)(group,self.channel_name)
#浏览器基于websocket向后端发送数据,自动触发接收消息
def websocket_receive(self, message):
#获取群号
group = self.scope['url_route']['kwargs'].get("group")
#通知组内所有客户端,执行xx_oo方法,在此方法中可以自己定义任意功能
async_to_sync(self.channel_layer.group_send)(group,{"type":"xx_oo","message":message})
def xx_oo(self, event):
text = event["message"]["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()
效果:
同一个num名下才能收到消息
这里只实现了核心功能,具体的样式和效果可以各自发挥,适应各自的场景中。