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