Django用websocket实现聊天室之筑基篇

最近闲来无事,无意发现一个聊天室的前端UI,看着挺好看的但是没有聊天室的通信代码,于是想给它安装电池(通信部分),先看UI:

 开始通信部分的工作:

 使用的组件:

  Django1.11.13

  channels 2.3.1

  redis

  jQuery

Django实现聊天室一般有实现轮训(比较老,效率低)、websocket等;这里用websocket,实现websocket有多种途径,一般有:channels和dwebsocket等,dwebsocket使用简单但是看了下官网好像只提供了差不多Django1.8版本以前的用法(添加MIDDLEWARE_CLASSES = ['dwebsocket.middleware.WebSocketMiddleware']),而Django1.11.13废弃了MIDDLEWARE_CLASSES,使用MIDDLEWARE,具体迁移方法未做深究,这里就直接使用channels

channels官方文档:https://channels.readthedocs.io/en/latest/

准备阶段

1.安装channels
sudo pip install -U channels

检测下 channels是否安装成功

$  python3 -c 'import channels; print(channels.__version__)'
2.3.1


2.如果没安装redis,先安装redis

(1)Ubuntu安装redis 使用命令sudo apt-get install redis-server
  whereis redis 查看redis的安装位置
  ps -aux | grep redis 查看redis服务的进程运行
  netstat -nlt | grep 6379根据redis运行的端口号查看redis服务器状态,端口号前是redis服务监听的IP(默认只有本机IP 127.0.0.1)


3.安装channels_redis
sudo pip install channels_redis

4.确保channels可以与Redis通信。打开Django shell并运行以下命令:

$ python3 manage.py shell
>>> import channels.layers
>>> channel_layer = channels.layers.get_channel_layer()
>>> from asgiref.sync import async_to_sync
>>> async_to_sync(channel_layer.send)('test_channel', {'type': 'hello'})
>>> async_to_sync(channel_layer.receive)('test_channel')
{'type': 'hello'}

接下来创建一个Django项目和一个app,我创建的项目名chatroom,app名chatPage

 目录结构:

chatroom
├── chatPage
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── models.py
│   ├── urls.py
│   ├── views.py
├── chatroom
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── db.sqlite3
└── manage.py

然后直接按channels官网流程走一遍,先把通信调通:

setting.py中注册chatPage,顺便把channels 也注册了

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels',
    'chatPage',
]

项目根目录添加chatPage  应用的路由

from django.contrib import admin
from django.conf.urls import url ,include 

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^chatPage/' ,include("chatPage.urls",namespace="chatPage")),
    
]

chatPage目录下新建目录templates,并在templates目录中创建一个名为chat.html的文件,作为登陆首页,将以下代码加入chat.html

<!-- chat/templates/chat/index.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Chat Rooms</title>
</head>
<body>
    What chat room would you like to enter?<br/>
    <input id="room-name-input" type="text" size="100"/><br/>
    <input id="room-name-submit" type="button" value="Enter"/>

    <script>
        document.querySelector('#room-name-input').focus();
        document.querySelector('#room-name-input').onkeyup = function(e) {
            if (e.keyCode === 13) {  // enter, return
                document.querySelector('#room-name-submit').click();
            }
        };

        document.querySelector('#room-name-submit').onclick = function(e) {
            var roomName = document.querySelector('#room-name-input').value;
            window.location.pathname = '/chat/' + roomName + '/';
        };
    </script>
</body>
</html>

chatPage目录下view.py写视图函数

def index(request):
    #return HttpResponse("helloworld")
    response= render_to_response('chat.html') return render(request, 'chat.html', {}) return response 

 chatPage目录下urls.py添加视图路由

from django.conf.urls import url

from . import views

app_name='chatPage'

urlpatterns = [
    url(r'^chat$', views.index, name= 'chat'),

]

现在运行 python manage.py runserver

浏览器输入http://localhost:8000/chatPage/chat,即可显示账号名输入页面,但是输入还不能跳转,接下来添加聊天室页面,添加之前先引入channelsRedis到Django项目:

setting.py中添加下面的代码:

ASGI_APPLICATION = 'chatroom.routing.application'   #websocket扩展

#在本地6379端口启动redis :redis-server
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [('127.0.0.1', 6379)],
        },
    },
}
 
 
 
 
 

chatPage目录下templates目录中创建一个名为chatroom.html的文件,作为登陆首页,将以下代码加入chatroom.html

 
<!DOCTYPE html>
<html> <head> <meta charset="utf-8"/> <title>Chat Room</title> </head> <body> <textarea id="chat-log" cols="100" rows="20"></textarea><br/> <input id="chat-message-input" type="text" size="100"/><br/> <input id="chat-message-submit" type="button" value="Send"/> </body> <script> var roomName = {{ room_name_json }}; //console.log(window.location.host); // 打开一个WebSocket: var chatSocket = new WebSocket( "ws://" + window.location.host + "/ws/chatPage/" + roomName +"/"); // 响应onmessage事件:  chatSocket.onmessage = function(e) { var data = JSON.parse(e.data); console.log(data); var message = data['message']; document.querySelector('#chat-log').value += (message + '\n'); }; chatSocket.onclose = function(e) { console.error('Chat socket closed unexpectedly'); }; document.querySelector('#chat-message-input').focus(); document.querySelector('#chat-message-input').onkeyup = function(e) { if (e.keyCode === 13) { // enter, return  document.querySelector('#chat-message-submit').click(); } }; document.querySelector('#chat-message-submit').onclick = function(e) { var messageInputDom = document.querySelector('#chat-message-input'); var message = messageInputDom.value; // 给服务器发送消息  chatSocket.send(JSON.stringify({ 'message': message })); messageInputDom.value = ''; }; </script> </html>
 

写视图函数

 
def chatroom(request, room_name):
    print(room_name)
    return render(request, 'chatroom.html', {
        'room_name_json': mark_safe(json.dumps(room_name))
    })
 

添加路由

 
from django.conf.urls import url

from . import views

app_name='chatPage'

urlpatterns = [
    url(r'^chat$', views.index, name= 'chat'),

    url(r'^chatroomPage/(.*)/', views.chatroom),

]
ASGI_APPLICATION = 'chatroom.routing.application' 中的chatroom为工程名
然后需要建立属于websocket的路由文件,在和工程同名的目录chatroom下新建routing.py文件,并写入下面的代码:
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import chatPage.routing

application = ProtocolTypeRouter({
    # (http->django views is added by default)
    'websocket': AuthMiddlewareStack(
        URLRouter(
            chatPage.routing.websocket_urlpatterns
        )
    ),
})
ProtocolTypeRouter将首先检查连接的类型。如果是websocket,连接会交给AuthMiddlewareStack,
AuthMiddlewareStack会对当前身份验证的用户填充连接的scope,然后连接将被给到URLRouter.根据提供的url模式,URLRouter将检查连接的http路径,将其路由到指定的特定的consumer.URLRouter中的chatPage为自己建的APP名
然后在应用chatPage目录下新建routing.py文件,写入下面的代码,添加websocket访问的路由
from django.conf.urls import url

from . import consumers

websocket_urlpatterns = [
    url(r'^ws/chatPage/(?P<room_name>[^/]+)/$', consumers.ChatConsumer),
]
 

然后同级目录下新建名为consumers.py的文件,官网是先介绍同步的websocket的写法,这里直接一步到位,实现异步的websocket方式,写入下面的代码:

#-*-coding:utf-8-*-
from channels.generic.websocket import AsyncWebsocketConsumer
import json

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        print(self.scope)
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name ='chat_%s' % self.room_name

        # Join room group
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )

        await self.accept()

    async def disconnect(self, close_code):
        # Leave room group
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    # Receive message from WebSocket
    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']

        # Send message to room group
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message
            }
        )

    # Receive message from room group
    async def chat_message(self, event):
        message = event['message']

        # Send message to WebSocket
        await self.send(text_data=json.dumps({
            'message': message
        }))

函数说明:
self.scope[‘url_route’][‘kwargs’][‘room_name’]
从给 consumer 打开 WebSocket 连接的 chat/routes.py 中的 URL 路由中获取 "room_name" 参数。
每个 consumer 都有一个 scope, 其中包含有关其连接的信息, 特别是来自 URL 路由和当前经过身份验证的用户 (如果有的话) 中的任何位置或关键字参数。


self.room_group_name = ‘chat_%s’ % self.room_name
直接从用户指定的房间名称构造一个 Channels group 名称, 无需任何引用或转义。
组名可能只包含字母、数字、连字符和句点。因此, 此示例代码将在具有其他字符的房间名称上发生失败。


async_to_sync(self.channel_layer.group_add)(…)
加入一个 group。
async_to_sync(…) wrapper 是必需的, 因为 ChatConsumer 是同步 WebsocketConsumer, 但它调用的是异步 channel layer 方法。(所有 channel layer 方法都是异步的)
group 名称仅限于 ASCII 字母、连字符和句点。由于此代码直接从房间名称构造 group 名称, 因此如果房间名称中包含的其他无效的字符, 代码运行则会失败。


self.accept()
接收 WebSocket 连接。
如果你在 connect() 方法中不调用 accept(), 则连接将被拒绝并关闭。例如,您可能希望拒绝连接, 因为请求的用户未被授权执行请求的操作。
如果你选择接收连接, 建议 accept() 作为在 connect() 方法中的最后一个操作。


async_to_sync(self.channel_layer.group_discard)(…)
离开一个 group。
将 event 发送到一个 group。
event 具有一个特殊的键 'type' 对应接收 event 的 consumers 调用的方法的名称。

至此一个基本的聊天室通信部分就基本完成了,运行python manage.py runserver

浏览器打开http://localhost:8000/chatPage/chat   键入名称,回车,即可到聊天界面,此时发送的消息只能自己看到,还不能达到多人聊天,原因是不在同一个group中,现在
在consumers.py修改这句,self.room_group_name = 'chat_Group'  #'chat_%s' % self.room_name,这里我们设置了一个固定的房间名作为Group name,所有的消息都会发送到这个Group里边,当然你也可以通过参数的方式将房间名传进来作为Group name,从而建立多个Group,这样可以实现仅同房间内的消息互通

当我们启用了channel layer之后,所有与consumer之间的通信将会变成异步的,所以必须使用async_to_sync

一个链接(channel)创建时,通过group_add将channel添加到Group中,链接关闭通过group_discard将channel从Group中剔除,收到消息时可以调用group_send方法将消息发送到Group,这个Group内所有的channel都可以收的到

group_send中的type指定了消息处理的函数,这里会将消息转给chat_message函数去处理

现在再次在多个浏览器上打开聊天页面输入消息,发现彼此已经能够看到了

至此一个基本的聊天室已经基本完成,到这里通信部分就算告一段落了,下一篇帖子将会修改UI聊天面板源码并和后端通信结合,添加注册登录,加好友,群组,初步完成一个美观的聊天室

 
 
 
 

猜你喜欢

转载自www.cnblogs.com/pozhu15/p/11874164.html