Hallo, Ich will gerade ein (zum üben erstmal Textbasiertes) Onlinespiel in Python programmieren. Dafür brauche Ich meines wissens nach einen Socketserver. Mit dem angehängten Code "Server.py" wird der Server erstellt, "Client.py" ist der Client. Leider empfängt der Server wenn das Clientprogramm fertig ist in einer Dauerschleife eine Nachricht der Länge 33 die anscheinend nicht mit UTF-8 encoding angezeigt werden kann da der Output leer ist. Weiß einer was man da machen kann?
:
Verschoben durch Moderator
Eigentlich müsste revc() bei einem blockierenden Socket sofort mit der Länge 0 zurückkommen sobald die Gegenseite den Socket schließt. Mach beim Client mal ordentlich shutdown() und close() bevor das Programm endet.
:
Bearbeitet durch User
Bernd K. schrieb: > Eigentlich müsste revc() bei einem blockierenden Socket sofort mit der > Länge 0 zurückkommen sobald die Gegenseite den Socket schließt. Das tut es auch, das Skript fängt den Fall aber nicht ab.
1 | if not input_from_client_bytes: break |
Das hinter dem recv() eingefügt, schafft Abhilfe. > Mach beim Client mal ordentlich shutdown() und close() bevor das > Programm endet. Das macht der Kernel am Ende des Programms automatisch. @Rabe sys.getsizeof() gibt nicht die Länge des Strings, sondern die interne Größe des String-Objekts zurück. Ein Nullstring belegt in Python also 33 Bytes.
:
Bearbeitet durch User
Genau das passiert auch, aber: import sys >>> sys.getsizeof(b"") 33 wird hingegen mit len(): >>> len(b"") 0 dann sollte es auch funktionieren. P.S. wenn die Länge 0 ist sollte man die Schleife beenden.
Also len() statt getSizeOf? Probiere ich gleich, Danke. Aber warum soll ich die Schleife beenden? Der Server soll ja weiter auf neue Nachrichten vom Client warten.
Rabe Socke schrieb: > Aber warum soll ich die Schleife beenden? Der Server soll ja weiter auf > neue Nachrichten vom Client warten. Du beendest die Schleife in diesem Thread und damit endet dieser eine Thread, gibt ja auch nichts mehr zu machen denn dieser socket ist tot weil der client weg ist. Die Schleife ums accept() bleibt laufen und startet bei jeder neu hereinkommenden Verbindung wieder jeweils einen neuen Thread, pro client ein Thread.
:
Bearbeitet durch User
Rabe Socke schrieb: > Also len() statt getSizeOf? > Probiere ich gleich, Danke. > Aber warum soll ich die Schleife beenden? Der Server soll ja weiter auf > neue Nachrichten vom Client warten. Wenn der Client die Verbindung geschlossen hat, kann er dort keine Daten mehr schicken. Er muss erst wieder eine neue Verbindung aufbauen.
Soll ich nach jeder Message eine neue Verbindung machen? Jeder Thread ist ja für einen Client da. Wäre es nicht besser den Thread bis der Client nicht mehr verbunden sein will auf den Client warten zu lassen? Ansonsten muss er sich wenn es dann ein Live-Spiel werden soll ja mehr mal pro Sekunde neu anmelden... was bringt das? Der Client schließt die Verbindung ja nicht nach jeder Nachricht.
Rabe Socke schrieb: > Soll ich nach jeder Message eine neue Verbindung machen? Nein. > Jeder Thread ist ja für einen Client da. Wäre es nicht besser > den Thread bis der Client nicht mehr verbunden sein will auf > den Client warten zu lassen? Klar, und nach Einfügen meiner Abbruchbedingung arbeitet Dein Server ja auch genau so. > Der Client schließt die Verbindung ja nicht nach jeder Nachricht. Doch, der Client, den Du oben gepostet hast, tut genau das. Er fragt einmal den User, schickt den Input an den Server weiter, beendet sich dann und schließt dabei implizit die Verbindung. Wenn Du um Input und Output ebenfalls eine Schleife legst (Abbruchbedingung bei EOF von stdin nicht vergessen) kannst Du innerhalb einer Verbindung auch mehrere Nachrichten nacheinander von Client an den Server schicken.
Ich habe in dem clientThread im Server ja eine Dauerschleife, das heißt wenn der Client gerade nichts schickt ist Input_from_client_bytes gleich 0 und mit deinem Code würde der Server die Verbindung schließen. Das will ich aber nicht. Im Moment habe ich es so dass er den Input nur verarbeitet wenn Len größer als 0 ist. Was wäre das Problem damit?
Die Wahrheit ist: TCP ist ein Stream-Protokoll. Du kannst da nicht "ein paar" Daten drüberschicken. Du kannst nicht am einen Ende "Hallo" reinschreiben und am anderen Ende sagen "lies die nächste Nachricht" und dann kommt "Hallo" raus. Wenn du dreimal "Hallo" reinschreibst und am anderen Ende sagst "lies bis zu 4096 Bytes" kann dabei "H" raus kommen oder "HalloHal" oder auch "HalloHalloHallo". TCP macht keine für dich sichtbare Paketierung. Wenn du über TCP Nachrichten als, naja, getrennte Nachrichten verschicken willst, musst du dir ein Protokoll überlegen, bei dem jede Nachricht z.B. erstmal mit ihrer Länge anfängt, und dahinter kommen die Daten, und der Client liest so lange Daten bis er die komplette Nachricht hat. Nicht bis der Buffer zufällig leer ist. Das funktioniert nämlich nur bei Trivialbeispielen auf localhost. read(4096) liest auch keine 4096 Bytes, sondern halt "mindestens 1, soviele wie grad da sind, aber höchstens 4096".
:
Bearbeitet durch User
Rabe Socke schrieb: > Ich habe in dem clientThread im Server ja eine Dauerschleife, das heißt > wenn der Client gerade nichts schickt ist Input_from_client_bytes gleich > 0 und mit deinem Code würde der Server die Verbindung schließen. Nein, solange der Client nichts schickt, blockiert der Server-Thread bei conn.recv(MAX_BUFFER_SIZE). Dieser Aufruf kehrt erst dann zurück, wenn eine dieser beiden Bedingungen erfüllt ist: 1. Der Client hat was geschickt, dann liefert recv() die empfangenen Daten. 2. Der Client hat die Verbindung geschlossen, dann liefert recv() einen Nullstring. > Im Moment habe ich es so dass er den Input nur verarbeitet wenn > Len größer als 0 ist. Wie gesagt wird Len nur dann 0 wenn der Client die Verbindung beendet hat. Ab dem Moment ist aber der Socket unbrauchbar, deshalb muss an dieser Stelle die Schleife beendet und der Socket geschlossen werden. > Was wäre das Problem damit? Es führt zu der Endlosschleife, die Du im Ausgangspost beschreibst. Übrigens gilt das Gesagte nur für blockierende Sockets. Ein nichtblockierender Socket liefert tatsächlich immer dann 0 wenn recv() aufgerufen wird, ohne dass der Partner etwas geschickt hat. Aber auch solche Sockets sollte man nicht in einer Endlosschleife behandeln, wie Du es im Sinn hast, denn die würde ständig einen CPU-Kern voll auslasten, auch wenn nichts reinkommt. Stattdessen verwendet man in so einem Fall select() oder einen seiner modernen Nachfolger. @Sven: Was Du schreibst ist völlig richtig, geht aber komplett am Problem vorbei.
R. M. schrieb: > @Sven: Was Du schreibst ist völlig richtig, geht aber komplett am > Problem vorbei. Hmja, aber es wird direkt danach das nächste Problem sein, und ich denke es macht die Sache einfacher, wenn man es in das Design der Lösung direkt mit einbezieht :D
Guter Einwand, aber komischerweise funktioniert es gerade genau wie es soll. Kommt das Problem mit den einzelnen Paketen erst später wenn ich mehrere Pakete kurz hintereinander schicke?
Ich würde vorschlagen zu prüfen, ob ein existierendes Framework/Protokoll/Libraries zur Kommunikation nicht eine bessere Lösung wären anstatt das Rad neu zu erfinden und die Fehler, die viele andere bereits gemacht haben, zu wiederholen - zumindestens wenn es nicht primär darum gehen soll Socketprogrammierung zu lernen/verstehen. Scheint mir hier deutlich zielführender zu sein.
Otto Mans schrieb: > Ich würde vorschlagen zu prüfen, ob ein existierendes > Framework/Protokoll/Libraries zur Kommunikation nicht eine bessere > Lösung wären Ich würde ZeroMQ als ganz heißen Kandidaten für solcherlei (und auch allerlei mehr Anwendungen) vorschlagen.
Rabe Socke schrieb: > Kommt das Problem mit den einzelnen Paketen erst später wenn ich > mehrere Pakete kurz hintereinander schicke? Es gibt verschiedene Szenarien, die das verursachen können, z.B. * Es werden Nachrichten so schnell hintereinander verschickt, dass mehrere davon in einem TCP-Paket landen, bevor es abgeschickt wird (siehe Nagle-Algorithmus). In dem Fall erhält der Empfänger in einem recv() mehrere Nachrichten. * Es werden Nachrichten verschickt, die so groß sind, dass sie nicht in ein Paket passen, dann erhält der Empfänger u.U. mit einem recv() eine unvollständige Nachricht. * Der Empfänger ist eine Weile anderweitig beschäftigt und in der Zeit gehen mehrere Nachrichten ein, die zusammen größer sind, als sein Receive-Buffer. Dann sind in einem recv() mehrere Nachrichten drin, die letzte ist aber abgeschnitten und der Rest davon kommt erst mit dem nächsten recv(). Die Länge einer Nachricht vorneweg zu schicken ist übrigens nur eine von mehrern Möglichkeiten, um das in den Griff zu bekommen und bietet sich vor allem bei Binärprotokollen an, wenn diese nicht eh schon feste Paketlängen haben. Viele Internet-Protokolle sind aber textbasiert (z.B. HTTP und SMTP) und verwenden Marker wie Zeilenumbrüche, Leerzeilen oder Zeilen, die nur einen Punkt enthalten, um Datenfelder, Nachrichtenteile und ganze Nachrichten voneinander zu trennen, ohne vorab die Länge mitteilen zu müssen. @Otto @Bernd Grundsätzlich habt Ihr Recht, dass es besser ist, auf bestehende Protokolle zurückzugreifen, aber für das Verständnis von Sockets und Netzwerkkommunikation halte ich es auch nicht für falsch, das mal selber durchgespielt zu haben. Wenn man das dann verstanden hat, ist auch das Hintergrundwissen da, um das richtige Protokoll für den jeweiligen Anwendungsfall auswählen zu können.
Bernd K. schrieb: > Ich würde ZeroMQ als ganz heißen Kandidaten für solcherlei (und auch > allerlei mehr Anwendungen) vorschlagen. Ohne näheres über das Spiel zu wissen würde ich mich soweit nicht vorwagen. Kann sein, kann Murks sein...
Grundsätzlich ist das ganze Spiel ja eine Übung und ich will die Hintergründe verstehen, das Spiel an sich ist nicht wirklich interessant. Danke auf jeden Fall für die Hilfe, ich schau mir das alles mal an.
Warum den Ganzen Socketserver selbst implementieren, da gibt es was Fertiges in Python gibt: https://docs.python.org/3/library/socketserver.html Damit sollte sich dein Problem schnell und einfach lösen lassen.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.