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<room_name>\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部分:

<div class="" v-if="cusReplyType=='需要文字回复'&&link_communicationFlag=='会话未结束'">
                        <!--  上半部分-->
                        <div class="" id="chat" ref="chat"
                             style="overflow: scroll;background-color: rgb(235,235,235);padding-top: 0.4rem">
                            <div class="you">
                                <div class="">
                                    <div class="you_pic">
                                        <van-image
                                                round
                                                width="3rem"
                                                height="3rem"
                                                src="https://img01.yzcdn.cn/vant/cat.jpeg"
                                        />

                                    </div>
                                    <div class="" style="font-size: 0.15rem">技术支持</div>
                                </div>
                                <div class="you_msg">
                                    <div class="tip-trangle-left"></div>
                                    <span style="text-align: left;font-size: 0.4rem;">您好,我是您的技术支持,请问有什么可以帮到您?</span>
                                </div>
                            </div>
                        </div>
                        <!--  下半部分-->
                        <div class=""
                             style="position: fixed;bottom:0rem;width: 100%;background-color: rgb(245,245,245);">
                            <van-row style="
display: flex;align-items:center;padding:0.3rem">
                                <van-col span="3">
                                    <van-uploader :after-read="afterRead">
                                        <van-icon name="photo-o"/>
                                    </van-uploader>
                                </van-col>
                                <van-col span="18"><input style="width: 90%;height: 1.7rem;" type="text"
                                                          id="chat_message" disabled
                                                          placeholder="请输入内容" v-model="words"></van-col>
                                <van-col span="3">
                                    <div class="">
                                        <van-button type="info" size="small" @click="sendMsg()">发送</van-button>
                                    </div>
                                </van-col>
                            </van-row>

                        </div>
                    </div>

js部分: 

<script src="../../static/jquery/jquery-3.6.0.js"></script>
<script src="../../static/js/vant.js"></script>
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(`<div class="you"><div><div class="you_pic"><div class="van-image van-image--round" style="width: 3rem; height: 3rem;"><img src="https://img01.yzcdn.cn/vant/cat.jpeg" class="van-image__img"></div></div> <div style="font-size: 0.15rem;">技术支持</div></div> <div class="you_msg"><div class="tip-trangle-left"></div> <span style="text-align: left; font-size: 0.4rem;">${$('#chat_message').val()}</span></div></div>`)

        } else {
            $('#chat').append(`<div class="me"><div class="me_msg"><div class="tip-trangle-right"></div> <span style="text-align: left; font-size: 0.4rem;">${$('#chat_message').val()}</span></div> <div class="me_pic"><div class="van-image van-image--round" style="width: 3rem; height: 3rem;"><img src="https://img01.yzcdn.cn/vant/cat.jpeg" class="van-image__img"></div></div></div>`)

        }


        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(`<div class="you" style="height: auto;"><div><div class="you_pic"><div class="van-image van-image--round" style="width: 3rem; height: 3rem;"><img src="https://img01.yzcdn.cn/vant/cat.jpeg" class="van-image__img"></div></div> <div style="font-size: 0.15rem;">技术支持</div></div> <div class="you_msg"><div class="tip-trangle-left"></div> <div class="van-image" style="height: 4rem;width: 4rem;"><img onclick="vant.ImagePreview(['${location.origin + '/' + JSON.parse(args.data).pic}']);" src="${location.origin + '/' + JSON.parse(args.data).pic}" class="van-image__img"></div></div></div>`)

                        } else {
                            $('#chat').append(`<div class="me" style="height: auto;"><div class="me_msg" style="height: 4rem;"><div class="tip-trangle-right"></div> <div class="van-image" style="height: 4rem;width: 4rem;"><img onclick="vant.ImagePreview(['${location.origin + '/' + JSON.parse(args.data).pic}']);" src="${location.origin + '/' + JSON.parse(args.data).pic}" class="van-image__img"></div></div> <div class="me_pic"><div class="van-image van-image--round" style="width: 3rem; height: 3rem;width: 3rem"><img src="https://img01.yzcdn.cn/vant/cat.jpeg" class="van-image__img"></div></div></div>`)

                        }

                    } else {
                        $('#chat').append(`<div class="me"><div class="me_msg"><div class="tip-trangle-right"></div> <span style="text-align: left; font-size: 0.4rem;">${JSON.parse(args.data).message}</span></div> <div class="me_pic"><div class="van-image van-image--round" style="width: 3rem; height: 3rem;"><img src="https://img01.yzcdn.cn/vant/cat.jpeg" class="van-image__img"></div></div></div>`)

                    }
                    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(`<div class="you" style="height: auto;"><div><div class="you_pic"><div class="van-image van-image--round" style="width: 3rem; height: 3rem;"><img src="https://img01.yzcdn.cn/vant/cat.jpeg" class="van-image__img"></div></div> <div style="font-size: 0.15rem;">技术支持</div></div> <div class="you_msg"><div class="tip-trangle-left"></div> <div class="van-image" style="height: 4rem;width: 4rem;"><img onclick="vant.ImagePreview(['${location.origin + '/' + JSON.parse(args.data).pic}']);" src="${location.origin + '/' + JSON.parse(args.data).pic}" class="van-image__img"></div></div></div>`)

                        } else {
                            $('#chat').append(`<div class="me" style="height: auto;"><div class="me_msg" style="height: 4rem;"><div class="tip-trangle-right"></div> <div class="van-image" style="height: 4rem;width: 4rem;"><img onclick="vant.ImagePreview(['${location.origin + '/' + JSON.parse(args.data).pic}']);" src="${location.origin + '/' + JSON.parse(args.data).pic}" class="van-image__img"></div></div> <div class="me_pic"><div class="van-image van-image--round" style="width: 3rem; height: 3rem;width: 3rem"><img src="https://img01.yzcdn.cn/vant/cat.jpeg" class="van-image__img"></div></div></div>`)

                        }
                        {#$('#chat').append(`<div class="me" style="height: auto;"><div class="me_msg" style="height: 4rem;"><div class="tip-trangle-right"></div> <div class="van-image" style="height: 4rem;width: 4rem;"><img onclick="vant.ImagePreview(['${location.origin + '/' + JSON.parse(args.data).pic}']);" src="${location.origin + '/' + JSON.parse(args.data).pic}" class="van-image__img"></div></div> <div class="me_pic"><div class="van-image van-image--round" style="width: 3rem; height: 3rem;width: 3rem"><img src="https://img01.yzcdn.cn/vant/cat.jpeg" class="van-image__img"></div></div></div>`)#}
                        {#$('#chat').append(`<div class="you" style="height: auto;"><div><div class="you_pic"><div class="van-image van-image--round" style="width: 3rem; height: 3rem;"><img src="https://img01.yzcdn.cn/vant/cat.jpeg" class="van-image__img"></div></div> <div style="font-size: 0.15rem;">技术支持</div></div> <div class="you_msg"><div class="tip-trangle-left"></div> <div class="van-image" style="height: 4rem;width: 4rem;"><img onclick="vant.ImagePreview(['${location.origin + '/' + JSON.parse(args.data).pic}']);" src="${location.origin + '/' + JSON.parse(args.data).pic}" class="van-image__img"></div></div></div>`)#}
                        {#$('#chat').append(`<div class="me" style="height: auto;"><div class="me_msg" style="height: 4rem;"><div class="tip-trangle-right"></div> <div class="van-image" style="height: 4rem;width: 4rem;"><img onclick="vant.ImagePreview(['${location.origin + '/' + JSON.parse(args.data).pic}']);" src="${location.origin + '/' + JSON.parse(args.data).pic}" class="van-image__img"></div></div> <div class="me_pic"><div class="van-image van-image--round" style="width: 3rem; height: 3rem;width: 3rem"><img src="https://img01.yzcdn.cn/vant/cat.jpeg" class="van-image__img"></div></div></div>`)#}

                    } else {

                        $('#chat').append(`<div class="you"><div><div class="you_pic"><div class="van-image van-image--round" style="width: 3rem; height: 3rem;"><img src="https://img01.yzcdn.cn/vant/cat.jpeg" class="van-image__img"></div></div> <div style="font-size: 0.15rem;">技术支持</div></div> <div class="you_msg"><div class="tip-trangle-left"></div> <span style="text-align: left; font-size: 0.4rem;">${JSON.parse(args.data).message}</span></div></div>`)
                        {#$('#chat').append(`<div class="me"><div class="me_msg"><div class="tip-trangle-right"></div> <span style="text-align: left; font-size: 0.4rem;">${JSON.parse(args.data).message}</span></div> <div class="me_pic"><div class="van-image van-image--round" style="width: 3rem; height: 3rem;"><img src="https://img01.yzcdn.cn/vant/cat.jpeg" class="van-image__img"></div></div></div>`)#}

                    }
                    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变了就只能重启电脑才能连了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值