簡體   English   中英

從 Django 通道內部聯系另一個 WebSocket 服務器

[英]Contacting another WebSocket server from inside Django Channels

我有兩台 websocket 服務器,分別稱為 Main 和 Worker,這是所需的工作流程:

  • 客戶端向 Main 發送消息
  • Main 向 Worker 發送消息
  • 工人回應主要
  • 主要回應客戶

這是可行的嗎? 我在 Channels 中找不到任何 WS 客戶端功能。 我天真地嘗試這樣做(在consumers.py中):

import websockets

class SampleConsumer(AsyncWebsocketConsumer):
    async def receive(self, text_data):
        async with websockets.connect(url) as worker_ws:
            await worker_ws.send(json.dumps({ 'to': 'Worker' }))
            result = json.loads(await worker_ws.recv())
        await self.send(text_data=json.dumps({ 'to': 'Client' })

但是,似乎with部分會阻塞(在收到來自 Worker 的響應之前,Main 似乎不接受任何進一步的消息)。 我懷疑這是因為websockets運行自己的循環,但我不確定。 (編輯:我比較id(asyncio.get_running_loop()) ,它似乎是同一個循環。我不知道它為什么會阻塞。)

響應{ "to": "Client" }不需要在這里,即使它使用不同的方法我也可以,只要它在收到來自 Worker 的響應時觸發。

有沒有辦法做到這一點,或者我吠錯了樹?

如果沒有辦法做到這一點,我正在考慮有一個線程(或進程?或單獨的應用程序?)與 Worker 通信,並使用channel_layer與 Main 通信。 這可行嗎? 如果我能得到確認(對於代碼示例更是如此),我將不勝感激。

編輯我想我知道發生了什么(盡管仍在調查),但是-我相信來自客戶端的一個連接實例化了一個消費者,雖然不同的實例都可以同時運行,但在一個消費者實例中,該實例似乎沒有允許第二種方法開始,直到一種方法完成。 這個對嗎? 現在看看將請求和等待響應代碼移動到線程中是否可行。

我在同一個 position 中,每當我從另一個 WebSocket 服務器收到消息時,我想在我的 Django 應用程序中處理消息。

我采用了使用WebSockets客戶端庫的想法,並使用 Django 論壇上的這篇文章中的manage.py命令使其作為一個單獨的進程運行。

您可以定義一個異步協程client(websocket_url)來監聽從 WebSocket 服務器接收到的消息。

import asyncio
import websockets


async def client(websocket_url):
    async for websocket in websockets.connect(uri):
        print("Connected to Websocket server")
        try:
            async for message in websocket:
            # Process message received on the connection.
                print(message)
        except websockets.ConnectionClosed:
            print("Connection lost! Retrying..")
            continue #continue will retry websocket connection by exponential back off 

在上面的代碼中, connect()充當無限異步迭代器。 更多關於這里

您可以在自定義管理命令 class 的handle()方法中運行上述協程。

運行wsclient.py

from django.core.management.base import BaseCommand

class Command(BaseCommand):

    def handle(self, *args, **options):
        URL = "ws://example.com/messages"
        print(f"Connecting to websocket server {URL}")
        asyncio.run(client(URL))

最后,運行 manage.py 命令。

python manage.py runwsclient

您還可以將處理程序傳遞給將處理消息的client(ws_url, msg_handler) ,以便處理邏輯將保留在客戶端之外。

2022 年 5 月 31 日更新

我創建了一個 django package 以將上述功能與最小設置集成: django-websocketclient

是的,Django Channels 不提供 websocket 客戶端,因為它主要用作服務器。 從您的代碼來看,您似乎並不真的需要 Main 和 Worker 之間的 websocket 通信,因為您只需啟動一個套接字,發送一條消息,接收響應並關閉套接字。 這是常規 HTTP 的經典用例,因此如果您真的不需要保持連接處於活動狀態,我建議您使用常規 HTTP 端點並使用 aioHTTP 作為客戶端。

但是,如果您確實需要客戶端,那么您應該在客戶端連接時打開一次套接字,並在客戶端斷開連接時關閉它。 你可以做這樣的事情。

import websockets

async def create_ws(on_create, on_message):
    uri = "wss://localhost:8765"
    async with websockets.connect(uri) as websocket:
        await on_create(websocket)
        while True:
            message = await websocket.recv()
            if message:
                await on_message(message)



class WebsocketClient:
    asyn def __init__(self, channel):
        self.channel = channel
        self.ws = None
        await creat_ws(self.on_message)
    
    async def on_create(self, was):
        self.ws = ws

    async def on_message(self, ws, message):
        await self.channel.send(text_data=json.dumps(message)
    
    async def send(self, message):
        self.ws.send(message)

    asunc def close(self):
        self.ws.close()

然后在您的消費者中,您可以按如下方式使用客戶端:

class SampleConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.ws_client = WebsocketClient(self)
    
    async def receive(self, text_data):
        await self.ws_client.send(text_data)
    
    async def disconnect(self, code):
        await self.ws_client.close()

看來我設法使用我發布的最新想法來做到這一點——啟動一個線程來處理與 Worker 的連接。 像這樣的東西:

class SampleConsumer(AsyncWebsocketConsumer):
    async def receive(self, text_data):
        threading.Thread(
            target=asyncio.run,
            args=(self.talk_to_worker(
                url,
                { 'to': 'Worker' },
            ),)
        ).start()

    async def talk_to_worker(self, url, message):
        async with websockets.connect(url) as worker_ws:
            await worker_ws.send(json.dumps(message))
            result = json.loads(await worker_ws.recv())
        await self.send(text_data=json.dumps({ 'to': 'Client' })

在每個方向上使用 HTTP 請求實際上可能更聰明(因為兩個端點都可以是 HTTP 服務器),但這似乎可行。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM