7.即时通讯
文章目录
- 即时通讯
- 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 eventlet
eventlet.monkey_patch()
import socketio
import eventlet.wsgi
sio = 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 os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from app1 import routings
os.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 chat
urlpatterns = [
path('admin/', admin.site.urls),
path("chatone", chat)
]
app1/views.py
from django.shortcuts import render
def chat(request):
return render(request, "chatting.html")
app1/routings.py
from django.urls import re_path
from app1 import consumers as consm1
websocket_urlpatterns = [
re_path(r'room/', consm1.ChatConsumer.as_asgi())
]
app1/consumers.py
from channels.exceptions import StopConsumer
from channels.generic.websocket import WebsocketConsumer
class 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连接时,前端会执行对应的 onclose
self.close()
# raise StopConsumer() # raise主动抛异常后,websocket_disconnect 就不在执行了,多用于`只处理服务端向客户端断开`的场景
return
send_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>