Django-websocket+newsocket

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