iis+django+channels+daphne部署websocket通信包含一对一、群聊功能、上传图片

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


目录

前言

一、安装对应库

二、使用步骤

1.后端配置:

后端思路:

2.前端代码:

 前端思路:

3.一对一和群聊:

4.发送图片:

5.daphne的启动和部署:

http不带证书启动:

https带证书启动:

总结


前言

django下使用websocket实现即时通信并部署到iis服务器(无nginx)踩了许多坑,记录下。


一、安装对应库

python版本需要在3.7以上,博主使用的是python3.8。

pip install channels
版本如下
channels==3.0.1
daphne==3.0.2

二、使用步骤

1.后端配置:

settings.py:

INSTALLED_APPS = [
    'channels',
]

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels.layers.InMemoryChannelLayer",
        # 'CONFIG': {
        #     "hosts": [('127.0.0.1', 6379)],
        # },
    }
} #如需使用redis需要安装
ASGI_APPLICATION = 'your_project.asgi.application'  # 新添加的,就是将wsgi都改成asgi
asgi.py:

"""
ASGI config for mpSimba project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
"""
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mpSimba.settings')
django.setup()

from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from . import routing

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": URLRouter(routing.websocket_urlpatterns)
})

在settings.py同目录下创建routing.py和consumers.py。

routing.py:
#这就是asgi的路由

from django.urls import re_path
from mpSimba import consumers
websocket_urlpatterns = [
    re_path(r'wss/(?P\w+)/$', consumers.ChatConsumer.as_asgi()),
]
#对应url:var ws = new WebSocket(`ws://192.168.8.84:8100/wss/${comm_recId}/`);
#room_name是聊天室的名称
consumers.py:
#这是websocket的视图函数

import json
from connectModule import models
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
import logging

logg = logging.getLogger('log')


class ChatConsumer(WebsocketConsumer):
    def connect(self):
        try:
            self.room_name = self.scope['url_route']['kwargs']['room_name']
            self.room_group_name = 'chat_%s' % self.room_name
            # Join room group
            async_to_sync(self.channel_layer.group_add)(
                self.room_group_name,
                self.channel_name
            )

            self.accept()
        except Exception as e:
            print(e)
            logg.info(e)
    def disconnect(self, close_code):
        # logg.info('disconnect')
        # Leave room group
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name,
            self.channel_name
        )

    # Receive message from WebSocket
    def receive(self, text_data):
        # logg.info(time.time())
        try:
            text_data_json = json.loads(text_data)
            print(text_data)

            message = text_data_json['message']
            if message == 'ping':
                async_to_sync(self.channel_layer.group_send)(
                    self.room_group_name,
                    {
                        'type': 'chat_message',
                        'message': message,
                    }
                )
            elif message == 'pic':
                messageSender = text_data_json['messageSender']
                recId = text_data_json['recId']
                pic = text_data_json['pic']
                # Send message to room group
                async_to_sync(self.channel_layer.group_send)(
                    self.room_group_name,
                    {
                        'type': 'chat_message',
                        'message': message,
                        'messageSender': messageSender,
                        'recId': recId,
                        'pic': pic,
                    }
                )
            else:
                messageSender = text_data_json['messageSender']
                recId = text_data_json['recId']
                print(text_data)
                # Send message to room group
                async_to_sync(self.channel_layer.group_send)(
                    self.room_group_name,
                    {
                        'type': 'chat_message',
                        'message': message,
                        'messageSender': messageSender,
                        'recId': recId,
                    }
                )
        except Exception as e:
            logg.info(e)
            print(e)

    # Receive message from room group
    def chat_message(self, event):
        try:
            message = event['message']
            if event['message'] == 'ping':
                self.send(text_data=json.dumps({
                    'message': message
                }))
            elif event['message'] == 'pic':

                models.messageList.objects.create(recId=event['recId'], messageSender=event['messageSender'],
                                                  messageDescription=event['pic'], messageType=event['message'],
                                                  messageId=event['recId'], )
                # Send message to WebSocket
                self.send(text_data=json.dumps({

                    'message': message,
                    'pic': event['pic'],
                    'messageSender': event['messageSender'],

                }))
            else:
                models.messageList.objects.create(recId=event['recId'], messageSender=event['messageSender'],
                                                  messageDescription=event['message'], messageId=event['recId'], )
                # Send message to WebSocket
                self.send(text_data=json.dumps({
                    'message': message
                }))
           
        except Exception as e:
            print(e)

以上就是后端的配置了。

后端思路:

建立连接后获取到路由中的"room_name"参数作为group的名称,这就是一个聊天室

var ws = new WebSocket(`ws://192.168.8.84:8100/wss/70/`);

收到用户发送的数据会调用receive接口这个时候就要做个判断,如果是心跳,那么就不要发送回前端,如果是图片,或者文字,那么就做一下对应的处理 。一定要注意'type'='chat_message'不能漏。

 async_to_sync(self.channel_layer.group_send)(
                    self.room_group_name,
                    {
                        'type': 'chat_message',
                        'message': message,
                    })

如果需要做数据处理,那么就在 def chat_message:下做对应处理就好。

2.前端代码:

html部分:

技术支持
您好,我是您的技术支持,请问有什么可以帮到您?
发送

js部分: 



var heart_beat
//var ws = new WebSocket(`wss://mpalpha.chinasimba.com:8803/wss/70`); 实测wss可以带端口-_-
var ws = new WebSocket(`ws://127.0.0.1:8100/wss/70/`);
    var inpval = ''
    var messageSender = ''
    if (kf) {
        messageSender = 'kefu'
    } else {
        messageSender = 'cus'
    }
//心跳包内容
    var heartBeat = {
        'message': "ping",
        'timestamp': new Date().getTime()
    }
 // 1 握手环节验证成功后自动触发 onopen
    ws.onopen = function () {

        alert('握手成功!')
        heart_beat = setInterval(() => {
            {#ws.send(JSON.stringify({'message': 'ping'}))#}
            ws.send(JSON.stringify(heartBeat))
        }, 20000)
        $(`#chat_message`).prop('disabled', false);
    }

    // 2 给服务端发送消息 人为触发 send
    function sendMsg() {
        if (kf) {

            $('#chat').append(`
技术支持
${$('#chat_message').val()}
`) } else { $('#chat').append(`
${$('#chat_message').val()}
`) } ws.send(JSON.stringify({ 'message': $('#chat_message').val(), 'messageSender': messageSender, 'recId': comm_recId, })) inpval = $('#chat_message').val() $('#chat_message').val('') } // 3 服务端一旦有了消息 自动触发 onmessage ws.onmessage = function (args) { console.log('args:', (args)) if (JSON.parse(args.data).message != 'ping') { if (kf) { console.log('kf') if (inpval != JSON.parse(args.data).message) { if (JSON.parse(args.data).message == 'pic') { if (JSON.parse(args.data).messageSender == 'kefu') { $('#chat').append(`
技术支持
`) } else { $('#chat').append(`
`) } } else { $('#chat').append(`
${JSON.parse(args.data).message}
`) } inpval = '' } else { inpval = '' } } else { if (inpval != JSON.parse(args.data).message) { if (JSON.parse(args.data).message == 'pic') { if (JSON.parse(args.data).messageSender == 'kefu') { $('#chat').append(`
技术支持
`) } else { $('#chat').append(`
`) } {#$('#chat').append(`
`)#} {#$('#chat').append(`
技术支持
`)#} {#$('#chat').append(`
`)#} } else { $('#chat').append(`
技术支持
${JSON.parse(args.data).message}
`) {#$('#chat').append(`
${JSON.parse(args.data).message}
`)#} } inpval = '' } else { inpval = '' } } } }; // 4 断开链接之后 自动触发 onclose ws.onclose = function () { {#ws.close()#} console.log('断开连接。ws连接状态:' + ws.readyState); clearInterval(heart_beat) {#ws = new WebSocket('wss://mpalpha.chinasimba.com:8803/wss/${comm_recId}/');#} const heart_again = setInterval(() => { ws = new WebSocket(`wss://mpalpha.chinasimba.com:8803/wss/${comm_recId}/`); {#ws = new WebSocket(`ws://127.0.0.1:8100/wss/${comm_recId}/`);#} }, 30000) {#ws = new WebSocket(`ws://127.0.0.1:8100/wss/${comm_recId}/`);#} };
 前端思路:

前端的主要思路就是建立websocket连接,然后定时发送心跳包维持websocket,时间大概20秒一次就足够了。当发送消息后调用ws.sent(),接收后端传来的websocket的时候会有标识比如发送者,根据标识区分是谁发送的信息并渲染到页面上就可以。

3.一对一和群聊:

上面的代码主要还是通过群聊的方式,双方在一个房间内聊天,消息的话根据发送方不同来做处理。只需要定义一个room_name作为group_room就可以了,问题是一对一该如何实现呢?

博主的思路是:用户A和用户B在实时通讯的时候,将url中的room_name改成A的id_b的id,按照大小来排列拼接。例如a.id=563543,b.id=432534,那么ws的url就是ws://127.0.0.1:8000/wss/432534_563543,按照大小排列的方式就可以保证ab同时在同一个room内。当然我的想法仅供参考,有更好的方案评论区指教。

4.发送图片:

先调用上传图片接口,上传成功后前端再调用ws.send()执行,做个标识例如"type"="pic"后端判断后返回图片url。前端拿到后渲染即可。具体可以去上面代码里面找。

5.daphne的启动和部署:

在编译器上会自动帮你启动。出现这个ASGI/Channels就是成功了。

重点来了:那么如何在服务器上启动呢,博主要在iis服务器上启动websocket服务,但是由于iis只支持wsgi协议,不支持asgi,导致无法支持websocket。所以只能使用命令行启动,可以直接在项目根目录用CMD打开或者写一个.bat批处理文件来启动它。刚刚安装channels的时候,已经顺便安装好了daphne服务器,这是channels官方推荐的服务器。我们只需要启动daphne服务即可。

http不带证书启动:
@echo off
daphne -p 8700 mpSimba.asgi:application
pause
# 使用批处理文件可以按我这样写,如果手动CMD打开那只要中间哪一行即可。意思是在127.0.0.1:8700端口启动服务
https带证书启动:

如果你的站点的https的,需要你下载证书并且配置,一定要检查cmd的环境是否正确,如果服务器上环境错了,会报错,最好是进入你项目的虚拟环境中。网上也有许多文章可以借鉴。

@echo off
D:\wwwroot\xxx\venv\Scripts\daphne.exe -e ssl:8803:privateKey=xxx.com.key:certKey=xxx.com.crt your_project.asgi:application
pause

#或
@echo off
daphne -e ssl:8803:privateKey=xxx.com.key:certKey=xxx.com.crt your_project.asgi:application
pause


总结

nginx代理的部署方案太多了,就不写了。在做需求的时候,实际上卡的最久的就是服务器上的部署问题,要确定服务器能不能支持asgi启动,如果不行,就使用命令行手动启动,服务器的运行环境一定要确认清楚。如果想要提高并发能力,就把consumers.py中的同步方法去掉,使用异步写法。连接服务端websocket的时候,不要挂代理,IP变了就只能重启电脑才能连了。

你可能感兴趣的:(websocket,django,python)