2017-01-27 10 views
1

私はスタックオーバーフローの新機能です(長期的には "ストーカー"でしたが)!Python 3 websockets - 接続を閉じる前にメッセージを送信

私はPython、特にWebSocketを使用しているAsyncioを学ぼうとしています。

私は例/チュートリアルのためにウェブを精査しています。以下の小さなチャットアプリケーションをまとめました。そして、より多くのコマンドなどを取得する前にいくつかのアドバイスを使用してリファクタリングが難しくなりました。

私の主な質問は、(DISCONNECTコマンドを送信するときに)接続を閉じる前に切断確認メッセージを送信するためにasyncio.sleep(0)が必要なのはなぜですか?

これ以外の構造は、右のトラックにありますか?

私はあまりにも多くの非同期/待っていると感じますが、私はなぜ私の頭を包んでいないのですか。

この時点でチュートリアルやS/O投稿を数時間見習っていないので、専門家の助言を直接受けることができます。

"nick"、 "msg"、 "test" & "disconnect"コマンドに応答するシンプルなWSサーバです。プレフィックスは不要です(例:「nick Rachel」)。

import asyncio 
import websockets 
import sys 

class ChatServer: 

    def __init__(self): 
     print("Chat Server Starting..") 
     self.Clients = set() 
     if sys.platform == 'win32': 
      self.loop = asyncio.ProactorEventLoop() 
      asyncio.set_event_loop(self.loop) 
     else: 
      self.loop = asyncio.get_event_loop() 

    def run(self): 
     start_server = websockets.serve(self.listen, '0.0.0.0', 8080) 
     try: 
      self.loop.run_until_complete(start_server) 
      print("Chat Server Running!") 
      self.loop.run_forever() 
     except: 
      print("Chat Server Error!") 

    async def listen(self, websocket, path): 

     client = Client(websocket=websocket) 
     sender_task = asyncio.ensure_future(self.handle_outgoing_queue(client)) 

     self.Clients.add(client) 
     print("+ connection: " + str(len(self.Clients))) 

     while True: 
      try: 
       msg = await websocket.recv() 
       if msg is None: 
        break 

       await self.handle_message(client, msg) 

      except websockets.exceptions.ConnectionClosed: 
       break 

     self.Clients.remove(client) 
     print("- connection: " + str(len(self.Clients))) 

    async def handle_outgoing_queue(self, client): 
     while client.websocket.open: 
      msg = await client.outbox.get() 
      await client.websocket.send(msg) 


    async def handle_message(self, client, data): 

     strdata = data.split(" ") 
     _cmd = strdata[0].lower() 

     try: 
      # Check to see if the command exists. Otherwise, AttributeError is thrown. 
      func = getattr(self, "cmd_" + _cmd) 

      try: 
       await func(client, param, strdata) 
      except IndexError: 
       await client.send("Not enough parameters!") 

     except AttributeError: 
      await client.send("Command '%s' does not exist!" % (_cmd)) 

    # SERVER COMMANDS 

    async def cmd_nick(self, client, param, strdata): 
     # This command needs a parameter (with at least one character). If not supplied, IndexError is raised 
     # Is there a cleaner way of doing this? Otherwise it'll need to reside within all functions that require a param 
     test = param[1][0] 


     # If we've reached this point there's definitely a parameter supplied 
     client.Nick = param[1] 
     await client.send("Your nickname is now %s" % (client.Nick)) 

    async def cmd_msg(self, client, param, strdata): 
     # This command needs a parameter (with at least one character). If not supplied, IndexError is raised 
     # Is there a cleaner way of doing this? Otherwise it'll need to reside within all functions that require a param 
     test = param[1][0] 

     # If we've reached this point there's definitely a parameter supplied 
     message = strdata.split(" ",1)[1] 

     # Before we proceed, do we have a nickname? 
     if client.Nick == None: 
      await client.send("You must choose a nickname before sending messages!") 
      return 

     for each in self.Clients: 
      await each.send("%s says: %s" % (client.Nick, message)) 

    async def cmd_test(self, client, param, strdata): 
     # This command doesn't need a parameter, so simply let the client know they issued this command successfully. 
     await client.send("Test command reply!") 

    async def cmd_disconnect(self, client, param, strdata): 
     # This command doesn't need a parameter, so simply let the client know they issued this command successfully. 
     await client.send("DISCONNECTING") 
     await asyncio.sleep(0)  # If this isn't here we don't receive the "disconnecting" message - just an exception in "handle_outgoing_queue" ? 
     await client.websocket.close() 


class Client(): 
    def __init__(self, websocket=None): 
     self.websocket = websocket 
     self.IPAddress = websocket.remote_address[0] 
     self.Port = websocket.remote_address[1] 

     self.Nick = None 
     self.outbox = asyncio.Queue() 

    async def send(self, data): 
     await self.outbox.put(data) 

chat = ChatServer() 
chat.run() 
+0

WebSocket接続を閉じる方法について定義されたプロトコルがあります。 https://tools.ietf.org/html/rfc6455#section-5.5.1 –

答えて

1

あなたのコードは.put_nowait()を呼び出して、すぐに戻ります.put()意味無限のサイズQueuesを、使用しています。 (これらのキューをコード内に保持したい場合は、キュー内の「なし」を接続を閉じる信号として使用し、client.websocket.close()handle_outgoing_queue()に移動することを検討してください)。

もう1つの問題:for x in seq: await co(x)await asyncio.wait([co(x) for x in seq])に置き換えることを検討してください。劇的な違いを得るにはasyncio.sleep(1)で試してみてください。

より良いオプションは、すべての送信トレイQueueを削除し、組み込みのasyncioキューとensure_futureで中継することです。 websocketsパッケージには実装済みのキューがすでに含まれています。

+0

お詫び申し上げます。返信に関する通知は一切ありません。私の質問は紛失/忘れてしまったと思われます。 –

1

私は、ウェブソケットにはに慣れていた2017年7月17日のポストに示されているウェブソケットの著者は、接続が閉じられていてもいつでも変更されたことを指摘したいと思います。代わりに、彼はtryを使って例外を処理することを提案します。 OPコードには、Noneとtry/exceptの両方のチェックが表示されます。現在のバージョンでは、クライアントが終了したときにwebsocket.recv()が何も返さないので、Noneチェックは不必要に冗長で、明らかに正確ではありません。

「主な」質問に対処すると、競合状態のように見えます。 asyncioは、待っているすべての要素を巡り巡り、それらに沿って微調整することで、その作業を行います。あなたのキューがクリアされる前のある時点であなたの 'close connection'コマンドが処理されると、クライアントはキュー内の最後のメッセージを受け取ることはありません。 async.sleepを追加すると、ラウンドロビンに追加のステップが追加され、おそらくあなたのキューを空にするタスクが 'close connection'の前に置かれます。

これは、目標を達成するために必要な非同期なものの数です。いつでもブロックすると、他のすべてのタスクを続行します。

関連する問題