Seite 1 von 1

ThreadedTCPServer, asynchrones senden

Verfasst: Montag 5. November 2007, 17:26
von keppla
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?

Verfasst: Montag 5. November 2007, 17:52
von rayo
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

Verfasst: Mittwoch 7. November 2007, 14:05
von keppla
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?

Verfasst: Mittwoch 7. November 2007, 15:11
von veers
Schau dir doch mal asyncore / asynchat an ;)

Verfasst: Mittwoch 7. November 2007, 15:20
von keppla
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)

Verfasst: Mittwoch 7. November 2007, 16:04
von veers
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

Verfasst: Mittwoch 7. November 2007, 17:07
von keppla
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.

Verfasst: Mittwoch 7. November 2007, 17:55
von rayo
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

Verfasst: Freitag 9. November 2007, 12:24
von keppla
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 :)

Verfasst: Freitag 9. November 2007, 16:37
von rayo
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

Verfasst: Freitag 16. November 2007, 10:53
von keppla
Ok, danke für die Antworten.