文章目錄
- 前言
- 一、redis的安裝以及配置
- 二、channels的安裝配置、函式詳解
- 1.channels的配置、函式的使用
- 三、通過channels實作異步點對點、群聊思路
前言
本篇博文是通過python的django一系列出的,如果遇到一些知識盲區可以訪問該系列下的其他博文,這次的使用會涉及到django視圖函式CBV的一些知識,也可能需要postman介面測驗工具,以及虛擬環境搭建,
關于虛擬環境的搭建、以及django視圖函式CBV的講解、postman介面測驗
訪問鏈接:https://blog.csdn.net/weixin_45859193/article/details/115408555
一、redis的安裝以及配置
這里因為一般redis的安裝都是直接確定確定的點的安裝,所以如果需要安裝的程序可以
訪問鏈接:https://pythonav.com/wiki/detail/10/82/
那么在安裝之前,安裝過的通過redis-cli -v命令查看redis版本(版本必須大于5.0以上,不然配置channels_redis時可能會導致報錯),
安裝完成后我們新建一個django專案(python manage.py startapp 專案名稱(這里用blog))并且django的版本也需要3.0以上的版本,
此時我們的redis配置settings.py如下:
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379", # 安裝redis的主機的 IP 和 埠
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {
"max_connections": 1000,
"encoding": 'utf-8'
},
"PASSWORD": "xxx" # redis密碼有密碼就設定
}
}
}
那么自此redis的配置就完成了,
二、channels的安裝配置、函式詳解
安裝channels很簡單,只需要通過pip install channels即可,不過要想channles可以使用redis,則需要在安裝一個channels_redispip install channels_redis,安裝完成后,我們需要配置一下awsgi(如果有的話則不需要添加),
創建wsgi.py(settings.py同目錄下)如下:
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "根專案.settings.py")
application = get_wsgi_application()
之后掛載awsgi到settings.py如下:
ASGI_APPLICATION = '根目錄.routing.application'
此時asgi已經配置完畢,
然后我們需要配置channels使其與redis連接settings.py如下:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'channels'
]
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
# "hosts": ["redis://:密碼@127.0.0.1:6379/0"], # 有密碼的這樣寫
"hosts": ["redis://127.0.0.1:6379/0"], # 沒密碼的這樣寫
},
},
}
配置完成后我們需要測驗一下,看看channels是否與redis關聯起來了,此時打開控制臺輸入:
python 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'}
如果沒有報錯那么我們將可以繼續進行下一步,如果報錯了的可以查看一下是否為redis的版本太低,或redis配置未生效等其他原因,
在新建的專案中創建auth/auth.py用作中間件用戶認證操作如下:
from channels.db import database_sync_to_async
from django.contrib.auth.models import AnonymousUser
from api.models import UserInfo
@database_sync_to_async
def get_user(token):
user_object = UserInfo.objects.filter(token=token).first()
# 判斷是否token不正確或用戶不存在
try:
if not user_object:
return AnonymousUser()
return user_object
except UserInfo.DoesNotExist:
return AnonymousUser()
class UserInfoAuthMiddleware:
"""
Token authorization middleware for Django Channels 2
"""
def __init__(self, inner):
# Store the ASGI application we were passed
self.inner = inner
async def __call__(self, scope, receive, send):
# 通過前端發送過來的請求頭上的authorization獲取token來獲取用戶物件
token = dict(scope['headers']).get(b'authorization', None)
if not token:
scope['user'] = AnonymousUser()
else:
scope['user'] = await get_user(token.decode())
return await self.inner(scope, receive, send)
此時中間件的處理已經完成了,這里的(@database_sync_to_async用于異步執行資料庫操作所定義的函式上)那么我們就可以寫關鍵的用于實作點對點或者群聊的代碼了,
在新建專案中創建視圖函式consumer.py如下:
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
# from django.db.models import Q
from api import models
import json
# 異步的方式
class ChatConsumer(AsyncWebsocketConsumer):
"""處理通知應用中的WebSocket請求"""
async def connect(self):
# 用戶
self.user = self.scope['user']
# 用戶id
self.room_name = self.user.id
# 通過用戶的id創建的聊天組
self.room_group_name = 'chat_%s' % self.room_name
"""建立連接"""
if self.user == "AnonymousUser":
# 未登錄用戶拒絕連接
await self.close()
else:
# 創建一個組
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
# 回呼
await self.accept()
async def receive(self, text_data):
"""將接收到的訊息回傳給前端"""
text_data_json = json.loads(text_data)
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat.message',
'message': text_data_json
}
)
async def disconnect(self, code):
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
async def chat_message(self, event):
message = event['message']
await self.send(text_data=json.dumps({
'message': message
}))
class SendConsumer(AsyncWebsocketConsumer):
async def connect(self):
# 用戶
self.user = self.scope['user']
self.room_name = self.scope['url_route']['kwargs']['room_name']
# 聊天組
self.room_group_name = 'chat_%s' % self.room_name
print(self.room_group_name)
"""建立連接"""
if self.user == "AnonymousUser":
# 未登錄用戶拒絕連接
await self.close()
else:
# 創建一個組
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.get_unread()
await self.accept()
async def receive(self, text_data):
"""將接收到的訊息回傳給前端"""
text_data_json = json.loads(text_data)
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat.message',
'message': text_data_json
}
)
async def disconnect(self, code):
"""斷開連接"""
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
async def chat_message(self, event):
message = event['message']
await self.get_news(message)
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message
}))
@database_sync_to_async
def get_news(self, msg):
"異步資料庫操作"
pass
@database_sync_to_async
def get_unread(self):
"異步資料庫操作"
pass
看到這里如果大家沒看過官網或者其他相關博客的情況下應該會有點懵,不過沒關系,我們現在通過一點一點的分析來告訴大家每個函式的意義以及使用方法(這里是先告訴使用的發法,具體實作思路等配置完成后講述),
1.channels的配置、函式的使用
首頁對于channels官網的講述中能明白,是類似django的視圖類的,且大多數的方法都是通過異步去完成的,
如果你明白django的restframework框架的話,那么你就會明白該視圖類(繼承的AsyncWebsocketConsumer類下的父類AsyncConsumer類的dispatch函式,來實作類似重寫as_view()方法)的操作,
重寫之后會將我們之前通常會使用的request替換為scope函式,并且通過請求獲取到的資料也按照channles的設計規則獲取,如果這里你明白的話那么應該能猜到后續在通過設定url的時候,視圖類需要引入的方法了吧,
所以我們現在在之前創建的專案的基礎上添加一個路由分發的功能routing.py如下:
from blog.consumer import ChatConsumer, SendConsumer
from django.conf.urls import url
websocket_urlpatterns = [
url(r'^chat-channel/(?P<room_name>\w+)/$', SendConsumer.as_asgi()),
url(r'^chat-channel$', ChatConsumer.as_asgi()),
]
可以看到我們設定了2個websocket路由,這里是我看官網實體也是這么做的,具體為什么要這么做我們后面會講,
此時我們可以看到我們寫的2個視圖類被匯入,且呼叫了as_asgi()方法(類似django視圖類的as_view()方法),
一、視圖函式下的connect函式
- connect函式:顧名思義就是訪問路由后連接websocket時觸發的函式
- close()函式:斷開連接
- channel_layer.group_add()函式:創建一個聊天組,傳入2個引數(
self.room_group_name(“chat”+獲取到用戶的id來實作組名唯一),self.channel_name(channels為我們定義的隨機名字)) - accept()函式則為成功后的回呼(
如果沒有執行到則會自動斷開連接)
二、視圖函式下的receive函式
- receive函式:多用于
保存、接收前端發來的訊息,并(配合chat_message函式回傳),需要傳引數text_data(前端發送過來的引數), - close()函式:斷開連接
- channel_layer.group_send函式:向聊天組所有成員發送訊息,傳入2個引數(
type(“處理方法的函式chat_message”),message(json格式化的資料))
三、視圖函式下的chat_message函式
- chat_message函式:一般用于處理receive函式發過來的資料判斷是否進行保存或者發送
- send()函式:將處理過的資料發送回前端
四、視圖函式下的disconnect函式
- disconnect函式:用于當前端呼叫websocket斷開時執行的操作
- channel_layer.group_discard()函式:用于將指定用戶組斷開,傳入2個引數(
組名,self.channel_name)
好了,channels中我們要使用的函式的功能也講完了,那么我們繼續配置我們的channels搭建websocket吧,
創建routing.py并配置Websocket連接(settings.py同目錄下)如下:
from channels.routing import ProtocolTypeRouter, URLRouter
from blog.auth.auth import UserInfoAuthMiddleware
import blog.routing
# 配置websocket連接方法
application = ProtocolTypeRouter({
'websocket': UserInfoAuthMiddleware(
URLRouter(
blog.routing.websocket_urlpatterns
)
)
})
此時運行我們的django專案(python manage.py runserver 127.0.0.1:8000),成功后我們將會看到如圖所示:

那么我們的通過channels配置的websocket服務器就搭建成功了,
三、通過channels實作異步點對點、群聊思路
結合我們的上述代碼,對于中間件的功能是通過scope[“headers”](類似request.Meta)獲取請求頭上的authorization的token,用于獲取當前用戶是否登錄,來獲取用戶資訊,如果沒有則回傳django自帶的AnonymousUser(匿名用戶),有的話則回傳用戶的物件,并賦值給scope.user(類似request.user),
那么中間件走完后我們前面留下的疑問,為什么定義2個路由呢?
這里我們先從我們自定義的第一個視圖ChatConsumer類開始說起
ChatConsumer類如下:
class ChatConsumer(AsyncWebsocketConsumer):
"""處理通知應用中的WebSocket請求"""
async def connect(self):
# 用戶
self.user = self.scope['user']
self.room_name = self.user.id
# 聊天組
self.room_group_name = 'chat_%s' % self.room_name
"""建立連接"""
if self.user == "AnonymousUser":
# 未登錄用戶拒絕連接
await self.close()
else:
# 創建一個組
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def receive(self, text_data):
"""將接收到的訊息回傳給前端"""
text_data_json = json.loads(text_data)
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat.message',
'message': text_data_json
}
)
async def disconnect(self, code):
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
async def chat_message(self, event):
message = event['message']
await self.send(text_data=json.dumps({
'message': message
}))
從connect函式來說我們首先判斷了用戶是否登錄,來限制了未登錄或非法登錄發來請求的操作,然后通過用戶唯一的id來建立起了一個聊天組,之后的操作就是將前端發來的資料通過在相同組的形式進行廣播通知,通知每一個連接了該組的成員,
所以我們簡稱這個視圖類為(接收、發送類,用于在當前用戶登錄、上線后立馬進行連接的操作),
那對于我們第二個自定義的視圖函式SendConsumer類就很好理解了:
SendConsumer類如下:
class SendConsumer(AsyncWebsocketConsumer):
async def connect(self):
# 用戶
self.user = self.scope['user']
self.room_name = self.scope['url_route']['kwargs']['room_name']
# 聊天組
self.room_group_name = 'chat_%s' % self.room_name
print(self.room_group_name)
"""建立連接"""
if self.user == "AnonymousUser":
# 未登錄用戶拒絕連接
await self.close()
else:
# 創建一個組
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.get_unread()
await self.accept()
async def receive(self, text_data):
"""將接收到的訊息回傳給前端"""
text_data_json = json.loads(text_data)
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat.message',
'message': text_data_json
}
)
async def disconnect(self, code):
"""斷開連接"""
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
async def chat_message(self, event):
message = event['message']
await self.get_news(message)
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message
}))
@database_sync_to_async
def get_news(self, msg):
pass
@database_sync_to_async
def get_unread(self):
pass
為了讓大家更明白我們把第二個路由分發的代碼展示出來
url(r'^chat-channel/(?P<room_name>\w+)/$', SendConsumer.as_asgi()),
可以看到我們第二個路由明顯需要在路由上添加一個名為room_name的字串,而我們通過(self.scope[‘url_route’][‘kwargs’][‘room_name’]獲取到的引數其實就是我們路由上定義的正則),而這個值就是我們在點對點聊天時,聊天物件的id,然后通過這個id去連接聊天物件id的組(如果聊天物件不在線的情況下可以添加到資料庫中,等用戶上線后可以提示未讀資訊、資訊個數等操作),
所以我們簡稱這個視圖類為(接收、保存類,用于在當前用戶點擊其他用戶物件時開啟,回傳時關閉的介面,且必須要保證當前用戶的第一個介面是否開啟,如果沒有則不能訪問),
而這2個資料庫操作就是為我們第二次連接時的互動操作
- 1.get_news()函式操作:用于聊天物件發送聊天資訊時保存到資料庫(
兩者之前互相聯系著),未讀數可以通過當前這2位用戶上一個聊天資訊的值(如果是自己發的聊天資訊可以不用處理,或者可以前端進行操作) - 2.get_unread()函式操作:用于在用戶點擊聊天用戶后清除未讀數的操作
那么通過這些操作配合前端就可以很輕松的實作點對點聊天的功能了,
點對點的功能完成了,那么群聊的功能就不難想了,也是這2個介面,然后我們多建立一個資料庫表結構,記錄創建群聊表結構的id,用戶的id,然后其他用戶通過連接該用戶創建的群聊表的id來進入同一個組進行聊天,群主就也可以設定為創建表者了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/293565.html
標籤:其他
上一篇:nginx快速入門
