2017-01-27 1 views
1

Ich bin neu bei Stack Overflow (obwohl ich schon lange "Stalker" war!) Also bitte sei sanft mit mir!Python 3 websockets - Nachricht senden vor dem Schließen der Verbindung

Ich versuche Python zu lernen, insbesondere Asyncio mit Websockets.

Nachdem ich das Internet nach Beispielen/Tutorials durchforstet habe, habe ich die folgende kleine Chat-Anwendung zusammengestellt und könnte einige Ratschläge geben, bevor es sperriger wird (mehr Befehle usw.) und schwierig zu refaktorieren ist.

Meine Hauptfrage ist, warum (beim Senden des DISCONNECT-Befehls) benötigt es die asyncio.sleep (0), um die Trennungsverifizierung zu senden, BEVOR die Verbindung geschlossen wird?

Sonst bin ich auf der richtigen Spur mit der Struktur hier?

Ich fühle, dass es zu viel async/warte, aber ich kann mich nicht ganz darum kümmern warum.

Aus stundenlangen Tutorials und S/O-Posts zu schauen, scheint an dieser Stelle nicht gerade hilfreich zu sein, also dachte ich, ich würde direkt einen Expertenrat bekommen!

Hier gehen wir, einfache WS-Server, die auf "Nick", "msg" reagiert, "Test" & "trennen" -Befehle. Kein Präfix erforderlich, d. H. "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

Es gibt ein definiertes Protokoll zum Schließen einer WebSocket-Verbindung. https://tools.ietf.org/html/rfc6455#section-5.5.1 –

Antwort

1

Ihr Code verwendet unendliche Größe Queues, was bedeutet, .put().put_nowait() aufruft und sofort zurückgibt. (Wenn Sie diese Warteschlangen in Ihrem Code beibehalten möchten, sollten Sie in Erwägung ziehen, in der Warteschlange "None" als Signal zum Schließen einer Verbindung zu verwenden und client.websocket.close() in handle_outgoing_queue() zu verschieben).

Ein anderes Problem: Erwägen Sie, for x in seq: await co(x) durch await asyncio.wait([co(x) for x in seq]) zu ersetzen. Versuchen Sie es mit asyncio.sleep(1), um einen dramatischen Unterschied zu erleben.

Ich glaube, eine bessere Option wird alle outbox Queue s fallen und nur Relais auf die eingebaute asyncio-Warteschlange und ensure_future. Das Websocket-Paket enthält bereits Warteschlangen in seiner Implementierung.

+0

Entschuldigung, ich bekam nie eine Benachrichtigung über eine Antwort - angenommen, meine Frage sei verloren/vergessen worden! –

1

Ich möchte darauf hinweisen, dass der Autor von WebSockets in einem Beitrag angedeutet am 17. Juli 2017, die verwendet Rückkehr Keine WebSockets, wenn die Verbindung geschlossen wurde, aber das war irgendwann geändert. Stattdessen schlägt er vor, dass Sie versuchen und die Ausnahme behandeln. Der OP-Code zeigt sowohl eine Prüfung auf None als auch auf try/except. Die None-Prüfung ist unnötig ausführlich und anscheinend nicht einmal genau, da websocket.recv() bei der aktuellen Version beim Schließen des Clients nichts zurückgibt.

Die "Haupt" -Frage adressierend, sieht es wie eine Art Rennzustand aus. Denken Sie daran, dass es bei asyncio funktioniert, indem Sie herumgehen und alle erwarteten Elemente berühren, um sie anzustoßen. Wenn der Befehl 'Verbindung beenden' zu einem bestimmten Zeitpunkt vor der Löschung der Warteschlange verarbeitet wird, erhält der Client diese letzte Nachricht nie in der Warteschlange. Durch das Hinzufügen von async.sleep wird dem Round-Robin ein zusätzlicher Schritt hinzugefügt und die Aufgabe zum Leeren der Warteschlange wird wahrscheinlich vor die "enge Verbindung" gestellt.

Bei der Anzahl der erwarteten Ereignisse geht es darum, wie viele asynchrone Dinge passieren müssen, um das Ziel zu erreichen. Wenn Sie zu irgendeinem Zeitpunkt blockieren, stoppen Sie alle anderen Aufgaben, die Sie weiterführen möchten.

Verwandte Themen