Forum: PC-Programmierung Python Socket Server


von Rabe Socke (Gast)


Angehängte Dateien:

Lesenswert?

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
von Bernd K. (prof7bit)


Lesenswert?

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
von R. M. (rmax)


Lesenswert?

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
von Hans (Gast)


Lesenswert?

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.

von Rabe Socke (Gast)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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
von Rolf M. (rmagnus)


Lesenswert?

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.

von Rabe Socke (Gast)


Lesenswert?

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.

von R. M. (rmax)


Lesenswert?

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.

von Rabe Socke (Gast)


Lesenswert?

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?

von Sven B. (scummos)


Lesenswert?

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
von R. M. (rmax)


Lesenswert?

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.

von Sven B. (scummos)


Lesenswert?

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

von Rabe Socke (Gast)


Lesenswert?

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?

von Otto Mans (Gast)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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.

von R. M. (rmax)


Lesenswert?

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.

von Otto Mans (Gast)


Lesenswert?

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

von Rabe Socke (Gast)


Lesenswert?

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.

von imonbln (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.