1.Django + Channels 提供 websocket 服务
1.1.确保你的环境
windows
环境 python 3.7
目前不支持 3.8
类Unix
操作系统除外
pip install django == 2.2 # 以上 目前 Django3.0
pip install channels # 安装最新版本
1.2.建立Django项目导入channels
1.2.1.导入channels
settings.py
中写入如下
INSTALLED_APPS = [
‘channels‘
]
1.2.2.创建ASGI
服务
新建my_asgi.py
文件
from channels.routing import ProtocolTypeRouter
application = ProtocolTypeRouter({
})
1.2.3.配置ASGI
服务
让Django
支持多种通讯协议 , settings.py
中写入如下
ASGI_APPLICATION = ‘NewWebsocket.my_asgi.application‘
1.3.创建app
提供websocket
服务
1.3.1.创建App
python manage.py startapp wsserver
1.3.2.创建wsserver
中的服务逻辑
新建service.py
文件
导入websocket
服务创造对象(基类 base 父类)
from channels.generic.websocket import WebsocketConsumer
创建websocket
逻辑服务
class NWService(WebsocketConsumer):
"""
在这里重写 3 个方法
"""
重写方法如下:
1.连接时 connect
当客户端发连接时调用此方法
def connect(self):
"""
客户端建立连接时,如果需要保持客户端和服务器的链接
self.accept()
:return: None
"""
self.accept()
2.接收消息 receive
当客户端发起消息时(客户端调用send方法时)
def receive(self, text_data=None, bytes_data=None):
"""
客户端发起消息后 服务器收到消息时
:param text_data: 收到的字符串儿数据
:param bytes_data: 收到的字节数据二进制数据流
:return: None
"""
# 业务逻辑,websocket 转发
self.send(text_data)
3.断开时 disconnect
当客户端中断连接时调用此方法(客户端调用close方法时)
def disconnect(self, code):
"""
客户端断开连接
:param code:断开的错误码
:return: None
"""
self.close()
disconnect
时,需要执行self.close()
才能关闭失效的连接(不是强制必须关闭,但是建议关闭)
WebsocketConsumer
提供的方法如下:
1.建立连接 accept
建立保持连接 , 当connect
是一定要执行accept
方法
2.发送消息 send
服务器通过客户端的链接发送数据
1.4.开始对外提供Websocket
服务
对外提供websocket
服务 也就是在对外提供ASGI
接口
1.4.1.wsserver
中的urls.py
为websocket
服务增加url
from django.urls import path
from wsserver.service import NWService
ws_url = [
path("ws/",NWService)
]
1.4.2.将websocket-url
增加到Django
服务中
my_asgi.py
from channels.routing import ProtocolTypeRouter,URLRouter
from wsserver import urls
application = ProtocolTypeRouter({
"websocket":URLRouter(urls.ws_url)
})
2.创建websocket
客户端
2.1.基于JavaScript
创建客户端连接
创建一个干净的HTML
文件写入
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
自己写websocket!
</body>
<script type="application/javascript">
var ws = new WebSocket("ws://127.0.0.1:8000/ws/")
</script>
</html>
2.2.Websocket
客户端常用事件及方法
2.2.1.客户端常用事件
1.onopen
事件,当websocket
客户端创建完成时并且连接成功 触发onopen
ws.onopen = function (res) {
ws.send("hello service");
}
2.onmessage
事件,收取消息事件,当Websocket
客户端收到消息时触发
ws.onmessage = function (res) {
// res.data 就是收到的消息内容字符串
console.log(res);
}
3.onerror
事件,当websocket
客户端连接发生异常或错误时
ws.onerror = function (res) {
console.log(res);
console.log("连接错误")
}
4.onclose
事件,当websocket
客户端关闭连接时
ws.onclose = function (res) {
console.log(res);
console.log("已关闭websocket连接")
}
2.2.2.客户端方法
1.send
发送数据
ws.send(‘JSON数据‘)
2.close
关闭连接
ws.close() // 此时触发 onclose 事件
3.聊天服务器
3.1.公共消息传递
3.1.1.保存客户端到服务器的链接
# 当客户端连接上服务器之后 将此连接存放在列表中 或 字典中
client_list = [] # 公共变量
def connect(self):
"""
客户端建立连接时,如果需要保持客户端和服务器的链接
self.accept()
:return: None
"""
print(self.scope)
client_list.append(self) # 将客户端的连接地址存储在列表中
self.accept()
3.1.2.传递公共消息
# 将消息通过 遍历`websocket`客户端连接 的方式 逐一发送数据
def receive(self, text_data=None, bytes_data=None):
"""
客户端发起消息后 服务器收到消息时
:param text_data: 收到的字符串儿数据
:param bytes_data: 收到的字节数据二进制数据流
:return: None
"""
# 业务逻辑,websocket 转发
print(text_data)
# 通过遍历其他客户端连接实现发送数据
for client in client_list:
client.send(text_data)
# self.send(text_data)
3.1.3.客户端下线
def disconnect(self, code):
"""
客户端断开连接
:param code:断开的错误码
:return: None
"""
print("客户端断开连接")
# 已经关闭的客户端连接 并没有在列表中释放
# 遍历客户端列表时 有可能遇到无效的连接
client_list.remove(self)
self.close()
3.2.点对点消息传递
3.2.1.创建连接
# 将客户端连接存入字典,将Key设置为昵称
client_dict = {}
class NWService(WebsocketConsumer):
"""
在这里重写 3 个方法
"""
def connect(self):
"""
客户端建立连接时,如果需要保持客户端和服务器的链接
self.accept()
:return: None
"""
# 获取用户的昵称
# ws://127.0.0.1:8000/ws/<username>/
username = self.scope.get("url_route").get("kwargs").get("username")
client_dict[username] = self
self.accept()
3.2.2.点对点消息传递
def receive(self, text_data=None, bytes_data=None):
"""
客户端发起消息后 服务器收到消息时
:param text_data: 收到的字符串儿数据
:param bytes_data: 收到的字节数据二进制数据流
:return: None
"""
# text_data 是一个字典
text_data = {
"sender":"发送方昵称",
"receiver":"接收方昵称",
"message":"消息内容"
}
# 获取接收方的昵称
receiver_name = text_data.get("receiver")
# 通过接收方昵称 获得 接收方的客户端连接
receiver_client = client_dict.get(receiver_name)
# 通过接受方的连接 发送数据
receiver_client.send(json.dumps(text_data))
3.2.3.点对点断开连接
def disconnect(self, code):
"""
客户端断开连接
:param code:断开的错误码
:return: None
"""
print("客户端断开连接")
username = self.scope.get("url_route").get("kwargs").get("username")
client_dict.pop(username)
self.close()
3.3.多媒体消息
3.3.1.上传任务
# 上传任务是通过 HTTP 协议上传
# 建议上传任务和消息传递分开
# 1.收到上传后的多媒体文件
# 2.将多媒体文件保存并存入数据库
# 3.将存储后的信息传递给客户端
3.3.2.客户端转发
# 1.客户端收到上传任务成功后的Response
# 2.客户端将Response中的消息数据转发给`Websocket`服务
3.3.3.websocket
服务转发
# 服务器实现点对点或公共消息传递
3.3.4.客户端收到多媒体消息
# 1.客户端判断是否收到多媒体消息
# 2.多媒体消息路径拼接
# 3.实现多媒体消息展示 audio video image
4.未读机制
4.1.Redis
存储未读消息数量
{
receiver:{
sender:1
sender1:2
sender2:5
}
}
4.2.离线消息存储触发
# 当客户端不在线或者是触发send方法失败时
def receive(self, text_data=None, bytes_data=None):
"""
客户端发起消息后 服务器收到消息时
:param text_data: 收到的字符串儿数据
:param bytes_data: 收到的字节数据二进制数据流
:return: None
"""
text_data = {
"sender":"发送方昵称",
"receiver":"接收方昵称",
"message":"消息内容"
}
receiver_name = text_data.get("receiver")
receiver_client = client_dict.get(receiver_name)
# 判断客户端是否在线
if not receiver_client:
# 开启离线消息
# 写入离线Redis数据库
return None
# send方法调用失败时 触发
try:
receiver_client.send(json.dumps(text_data))
except:
# 开启离线或未读消息
# 写入未读Redis数据库
return None