文章目录
- 即时通讯
- 1.类型
- 2.需求场景
- 3.传统的推送实现
- 4.websocket
- 5.websocket优点
- 6.websocket库Socket.IO
- 1.安装
- 2.创建服务
- 3.常见方法
- 7.Django+channel
- 1.安装
- 2.配置
即时通讯
定义: 即时通讯(Instant Messaging)
1.类型
-
在线push适用于web页面和APP
- 使用websocket
- 使用Socket.IO
- 自己封装socket
-
离线push:APP
- ios:APNs
- andorid:FCM/第三方服务(网易云信/融云/环信/LeanCloud)
2.需求场景
- 即时聊天
- 用户A关注了用户B,系统需要向用户B推送提示消息
- 用户下了订单,需要在运营管理后台向运营人员推送新订单通知
3.传统的推送实现
HTTP/1.x 不支持服务器主动推送,HTTP/2支持服务器主动推送,但HTTP/2 还未全面实施
-
轮询:
- 定义:浏览器每隔一段时间就向浏览器发送http请求,不论是否有数据更新都直接响应,
- 缺点:效率低下,浪费资源
-
长轮训(基于长连接Comet):
- 定义:服务端收到客户端的请求不会直接响应,而是先将这个请求挂起来,然后判断服务端数据是否有更新,如果有更新,则响应,如果没有数据,则达到一定时间限制才返回
- 缺点:依然需要反复发出请求效率低下,浪费资源
4.websocket
WebSocket协议定义:
-
是基于html5(HyperText Markup Language)定义的协议
-
单个TCP连接上进行全双工通信的协议
-
Websocket 通过 HTTP/1.1 协议的101状态码进行握手
-
ws或wss的统一资源标志符
ws://example.com/wsapi wss://secure.example.com/
-
端口:80/443
-
报文
# 客户端 GET / HTTP/1.1 Upgrade: websocket # 必须设置Websocket,表示希望升级到Websocket协议 Connection: Upgrade # 客户端希望连接升级 Host: example.com Origin: http://example.com # Sec-WebSocket-Key与magic_string进行hmac1加密,再进行base64加密 Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ== # 随机的字符串 Sec-WebSocket-Version: 13# 服务端 HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s= Sec-WebSocket-Location: ws://example.com/
5.websocket优点
- 较少的控制开销
- 更强的实时性
- 保持连接状态
- 可以支持扩展
- 更好压缩效果
- 没有的压缩效果
- 没有同源限制
- 可以发送文本
6.websocket库Socket.IO
1.安装
pip install python-socketio
2.创建服务
方式一:协程方式访问
import eventleteventlet.monkey_patch()import socketio
import eventlet.wsgisio = socketio.Server(async_mode='eventlet')
app = socketio.Middleware(sio)
eventlet.wsgi.server(eventlet.listen(('', 8000)), app)
为什么不使用多进程或多线程
- 受限于服务能性能有限创建的进程数和线程数,导致并发连接客户端不会很高,
3.常见方法
-
@sio.on(‘connect’),connect 为特殊事件,当客户端连接后自动执行
@sio.on('connect') def on_connect(sid, environ):"""与客户端建立好连接后被执行:param sid: string sid是socketio为当前连接客户端生成的识别id:param environ: dict 在连接握手时客户端发送的握手数据(HTTP报文解析之后的字典)"""pass
-
@sio.on(‘disconnect’),disconnect 为特殊事件,当客户端断开连接后自动执行
@sio.on('disconnect') def on_disconnect(sid):"""与客户端断开连接后被执行:param sid: string sid是断开连接的客户端id"""pass
-
@sio.on(‘自定义事件’) ,connect,disconnect与自定义事件处理方法的函数传入参数不同
# 以字符串的形式表示一个自定义事件,事件的定义由前后端约定 @sio.on('my custom event') def my_custom_event(sid, data):"""自定义事件消息的处理方法:param sid: string sid是发送此事件消息的客户端id:param data: data是客户端发送的消息数据"""pass
-
sio.emit(),:发送事件消息
#群发特定事件消息 sio.emit('my event', {'data': 'foobar'}) #给指定用户特定事件消息发送 sio.emit('my event', {'data': 'foobar'}, room=user_sid) #群发所有人 sio.send({'data': 'foobar'}) # 群发给指定房间的人 sio.send({'data': 'foobar'}, room=user_sid)
-
sio.rooms(sid):查询sid客户端所在的所有房间
-
sio.enter_room(sid, room_name):将连接的客户端添加到一个room
-
sio.leave_room(sid, room_name):将客户端从一个room中移除
7.Django+channel
1.安装
pip install channels==4.2.0
pip install daphne==4.1.2
2.配置
asgi.py
import osfrom django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouterfrom app1 import routingsos.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoProject.settings')application = ProtocolTypeRouter({'http': get_asgi_application(), # 支持http请求'websocket': URLRouter(routings.websocket_urlpatterns), # 支持websocket请求
})
settings.py
INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','daphne', # 添加daphne'django.contrib.staticfiles','corsheaders','channels',# 添加channels'app1']
WSGI_APPLICATION = 'djangoProject.wsgi.application'
ASGI_APPLICATION = "djangoProject.asgi.application"
app1/urls.py
from django.contrib import admin
from django.urls import path
from app1.views import chaturlpatterns = [path('admin/', admin.site.urls),path("chatone", chat)
]
app1/views.py
from django.shortcuts import renderdef chat(request):return render(request, "chatting.html")
app1/routings.py
from django.urls import re_path
from app1 import consumers as consm1websocket_urlpatterns = [re_path(r'room/', consm1.ChatConsumer.as_asgi())
]
app1/consumers.py
from channels.exceptions import StopConsumer
from channels.generic.websocket import WebsocketConsumerclass ChatConsumer(WebsocketConsumer):def websocket_connect(self, message):"""客户端向服务端发送websocket连接的请求时自动触发。"""print("1 > 客户端和服务端开始建立连接")self.accept()def websocket_receive(self, message):"""客户端基于websocket向服务端发送数据时,自动触发接收消息。"""print(f"2 > 服务端接收客户端的消息, message is {message}")recv_data = message["text"]if recv_data == "exit": # 服务端主动关闭websocket连接时,前端会执行对应的 oncloseself.close()# raise StopConsumer() # raise主动抛异常后,websocket_disconnect 就不在执行了,多用于`只处理服务端向客户端断开`的场景returnsend_data = f"服务端主动推送消息:{recv_data}"self.send(text_data=send_data)def websocket_disconnect(self, message):"""客户端与服务端断开websocket连接时自动触发(不管是客户端向服务端断开还是服务端向客户端断开都会执行)"""print("3 > 客户端和服务端断开连接")self.close()raise StopConsumer()
chatting.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><style>.message {height: 300px;border: 1px solid #dddddd;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="closeConn()">
</div><script>// 实例化websocket对象,并客户端主动以websocket方式连接服务端wbsocket = new WebSocket("ws://127.0.0.1:1010/room/123/");// 创建好websocket连接成功后自动触发(服务端执行self.accept()后)wbsocket.onopen = function (event) {var tag = document.createElement("div");tag.innerText = '[连接成功!]';document.getElementById("message").appendChild(tag);};// 创建连接失败后自动触发wbsocket.onerror = function (event) {var tag = document.createElement("div");tag.innerText = '[连接失败!]';document.getElementById("message").appendChild(tag);};// 当websocket接收到服务器发来的消息时会自动触发wbsocket.onmessage = function (event) {var tag = document.createElement("div");tag.innerText = event.data;document.getElementById("message").appendChild(tag);};// 当服务端主动断开客户端时自动触发(服务端执行self.close()后)wbsocket.onclose = function (event) {var tag = document.createElement("div");tag.innerText = '[连接已断开!]';document.getElementById("message").appendChild(tag);};// 页面上客户端点击向服务端"关闭连接"时触发function closeConn() {wbsocket.close(); // 客户端主动断开连接,服务端会执行 websocket_disconnect()var tag = document.createElement("div");tag.innerText = '[连接已断开啦!]';document.getElementById("message").appendChild(tag);}// 页面上客户端点击向服务端"发送消息"时触发function sendMessage() {var info = document.getElementById("txt");wbsocket.send(info.value); // 客户端给服务端发数据}</script></body>
</html>