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

Django-websocket+newsocket

上一篇:jsp实现大文件上传下载


下一篇:js 的防抖与节流