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

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

 

 开始通讯部分的工做:前端

 使用的组件:python

  Django1.11.13web

  channels 2.3.1redis

  redissql

  jQueryshell

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

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

准备阶段

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)

(2)编译安装

下载地址:http://redis.io/download,下载最新文档版本。

本教程使用的最新文档版本为 2.8.17,下载并安装:

$ wget http://download.redis.io/releases/redis-2.8.17.tar.gz $ tar xzf redis-2.8.17.tar.gz $ cd redis-2.8.17 $ make

make完后 redis-2.8.17目录下会出现编译后的redis服务程序redis-server,还有用于测试的客户端程序redis-cli,两个程序位于安装目录 src 目录下:

下面启动redis服务.

$ cd src
$ ./redis-server

注意这种方式启动redis 使用的是默认配置。也能够经过启动参数告诉redis使用指定配置文件使用下面命令启动。

$ cd src
$ ./redis-server redis.conf

redis.conf是一个默认的配置文件。咱们能够根据须要使用本身的配置文件。


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聊天面板源码并和后端通讯结合,添加注册登陆,加好友,群组,初步完成一个美观的聊天室

相关文章
相关标签/搜索