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?
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.
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.
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.
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".
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.