最近閒來無事,無心發現一個聊天室的前端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,便可顯示帳號名輸入頁面,可是輸入還不能跳轉,接下來添加聊天室頁面,添加以前先引入channels 和 Redis到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聊天面板源碼並和後端通訊結合,添加註冊登陸,加好友,羣組,初步完成一個美觀的聊天室