ThreadedTCPServer, asynchrones senden

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
Benutzeravatar
keppla
User
Beiträge: 483
Registriert: Montag 31. Oktober 2005, 00:12

Hallo Forum.

Zunächst die Umgebung: Ich habe einen ThreadedTCPServer mit einem eigenen Protokol (im prinzip was jasonartiges).
Eine Clientconnection wird aufrecht erhalten, d.H. sie wird nicht nur für eine Anfrage wie bei HTTP(1.0) genutzt.
In einem weiteren Thread läuft ein Spiel.
Die Workerthreads parsen die eingehenden Daten und legen Nachrichten auf einen Queue im Spiel.
Das Spiel arbeitet seinen Queue ab, und legt Nachrichten auf die Queues der Workerthreads, die diese dann kodiert raussenden.

Hier erstmal Stop, wenn diese Architektur Müll ist, bitte nicht weiterlesen und sie stattdessen sofort zerreissen.

So, mein Problem ist nun, dass der Workerthread nicht weis, ob er als nächstes empfängt oder sendet. Zz. durchläuft er eine Schleife, in der er zuerst ein non-blocking-read auf dem socket macht, dann ein non-blocking get auf den queue. (hätte ich zwei Sockets, wäre vmtl select mein freund, aber ich hab ja nur einen). Das sieht nach "idle wait aus".
In einer Alternativlösung hab ich dem Workerthread einen zweiten Thread gegeben, der workerthread hat gelesen, der andere geschrieben. Das sieht nach Threadoverkill aus.

Das ganze ist so unausprechlich hässlich, aber mir fällt da nichts besseres ein, u.A. auch aufgrund mangeldem Wissen über Netzwerkdinge (ich finde da irgendwie nur c-tutorials, nie was abstrakteres).

Wie sähe ein besseres Design aus?
rayo
User
Beiträge: 773
Registriert: Mittwoch 5. November 2003, 18:06
Wohnort: Schweiz
Kontaktdaten:

Hi

Also ein select würde sich auch hier anbieten.

Pseudocode:

Code: Alles auswählen

while 1:
    lesbare_sockets = [socket]
    schreibbare_sockets = []
    if queue_len > 0: #wichtig, da sonst select immer gleich den schreibbaren socket zurückgibt, auch wenn keine daten anliegen
        schreibbare_sockets.append(socket)
    r,w,e = select(lesbare_sockets, schreibbare_sockets, [socket], 0.01)
    if r:
        print 'daten können gelesen werden'
    if w:
        print 'daten können gesendet werden'
So würde ich das lösen.

*edit* vielleicht kannst du auch von hier abgucken: http://www.python-forum.de/post-76767.html

Gruss
Benutzeravatar
keppla
User
Beiträge: 483
Registriert: Montag 31. Oktober 2005, 00:12

Danke erstmal für die Antwort.
rayo hat geschrieben:Also ein select würde sich auch hier anbieten.
Um das Problem, herauszukriegen, ob ich lesen oder schreiben kann, zu lösen, ja. Das Problem, was ich sehe (was aber möglicherweise nicht existiert), ist, dass ich eine Schleife habe, die die ganze Zeit den Queue fragt, ob er denn was hat. Das sieht so auf den ersten Blick etwas ineffizient aus.
In deinem Beispiel hast du einen select-timeout von 0.01, d.H. es werden maximal 100 Nachrichten(mengen) pro sekunde gesendet.
*edit* vielleicht kannst du auch von hier abgucken: http://www.python-forum.de/post-76767.html
Wenn ich das richtig verstehe, wird die on_write immer aufgerufen, wenn was geschrieben werden könnte. was ja vermutlich so gut wie immer ist, da ich eher selten was auf dem queue hab. Das sieht für mich erstmal ineffizient aus, oder bilde ich das mir nur ein?
Gibt es irgendwelchen empfehlenswerten Lesestoff für Netzwerk/Socket-sachen, die nicht auf "so öffne ich sockets" rauslaufen, sondern eher so "best practices" sind?
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

Schau dir doch mal asyncore / asynchat an ;)
[url=http://29a.ch/]My Website - 29a.ch[/url]
"If privacy is outlawed, only outlaws will have privacy." - Phil Zimmermann
Benutzeravatar
keppla
User
Beiträge: 483
Registriert: Montag 31. Oktober 2005, 00:12

veers hat geschrieben:Schau dir doch mal asyncore / asynchat an ;)
Hab ich, sehe aber nicht direkt, wo das hilfreich sein kann. Ayncore scheint davon auszugehen, dass ich immer schreiben will, wenns möglich ist (handle_write).
Darf ich, umgekehrt, aus den Tipps schliessen, dass eine Schleife

Code: Alles auswählen

while True:
   r, w, e = select.select([self.sock],[self.sock], [], 0)
   if r:
      msg = parse(self.sock.read(1024))
      game.queue.put(msg)
   if w:
      try:
          msg = self.queue.get(timeout=0)
      except Empty:
          pass
      else:
          self.sock.write(str(msg))

keine soo schlechte idee ist? (ist ziemlich genau das, was ich zZ habe, von namen und sendbuffering mal abgesehen)
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

keppla hat geschrieben:
veers hat geschrieben:Schau dir doch mal asyncore / asynchat an ;)
Hab ich, sehe aber nicht direkt, wo das hilfreich sein kann. Ayncore scheint davon auszugehen, dass ich immer schreiben will, wenns möglich ist (handle_write).

http://docs.python.org/dev/library/asyn ... r.writable
[url=http://29a.ch/]My Website - 29a.ch[/url]
"If privacy is outlawed, only outlaws will have privacy." - Phil Zimmermann
Benutzeravatar
keppla
User
Beiträge: 483
Registriert: Montag 31. Oktober 2005, 00:12

veers hat geschrieben:
keppla hat geschrieben:
veers hat geschrieben:Schau dir doch mal asyncore / asynchat an ;)
Hab ich, sehe aber nicht direkt, wo das hilfreich sein kann. Ayncore scheint davon auszugehen, dass ich immer schreiben will, wenns möglich ist (handle_write).

http://docs.python.org/dev/library/asyn ... r.writable
Ja, und? Ist es wünschenswert, dass x mal die sekunde eine Funktion queue.empty() aufgerufen wird?
Wenn ich diese select-sache richtig verstanden habe (was ich ja scheinbar nicht habe), dient die doch dazu, auf eine prozessorzeitschonende Art den thread immer nur dann wiederzubeleben, wenn es was zu tun gibt (wenn sie returned). Eben das wäre aber nicht der Fall, wenn ich die ganze zeit den queue frag, ob es was zu tun gibt, und den select sofort returnen lasse.
rayo
User
Beiträge: 773
Registriert: Mittwoch 5. November 2003, 18:06
Wohnort: Schweiz
Kontaktdaten:

Hi

Es ist wünschenswert dass select nur auf schreiben prüft wenn auch nur daten anliegen. darum writable, ist bei asyncore und bei meinem wrapper gleich.

Gruss
Benutzeravatar
keppla
User
Beiträge: 483
Registriert: Montag 31. Oktober 2005, 00:12

rayo hat geschrieben:Es ist wünschenswert dass select nur auf schreiben prüft wenn auch nur Daten anliegen. darum writable, ist bei asyncore und bei meinem wrapper gleich.
Bitte entschuldigt meine Langsamkeit, was das Verstehen angeht.
Es ist also Lasttechnisch OK, wenn man viele Threads hat, die in einer Endlosschleife laufen, und dauernd was tun (queue.empty()), es ist aber schlecht, wenn in einem select mehr sockets als nötig werden?
Das ganze sieht für mich so nach "idle wait" aus.

Danke für eure Geduld :)
rayo
User
Beiträge: 773
Registriert: Mittwoch 5. November 2003, 18:06
Wohnort: Schweiz
Kontaktdaten:

Hi

Du hast für die Netzwerkkommunikation folgendes:
1 Thread, der ein select ausführt, egal wie viele sockets

Hier dein Beispiel ein wenig geändert. Die Unterschiede:
  • * der Socket wird nur auf writable geprüft wenn auch Daten vorhanen sind zum schreiben
    * bei queue.get() braucht es kein nonblocked get mehr weil wenn er dort hin kommt immer Daten in der Queue sind
    * select nicht sofort beenden lassen (timeout so 0.01s)

Code: Alles auswählen

while True:
   w_socks = []
   if self.queue.qsize() > 0:
      w_socks = [self.sock]
   r, w, e = select.select([self.sock],w_socks, [], 0.01)
   if r:
      msg = parse(self.sock.read(1024))
      game.queue.put(msg)
   if w:
      msg = self.queue.get()
      self.sock.write(str(msg))
Wenn keine Daten vorhanden sind, macht er im 10ms Tackt die Überprüfung ob neue Daten anliegen oder nicht, die 10ms Wartezeit verbringt er mit der Überprüfung auf neue eingehende Daten.

Soweit ich weiss, ist das üblich bei non-blocking sockets.

Gruss
Benutzeravatar
keppla
User
Beiträge: 483
Registriert: Montag 31. Oktober 2005, 00:12

Ok, danke für die Antworten.
Antworten