Newbie-Fragen zum Datenempfang

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
fauxxami
User
Beiträge: 6
Registriert: Freitag 16. Oktober 2020, 11:06

Moin zusammen!

Ich bin noch relativ neu bei Python und vollkommen neu in diesem Forum und hoffe deshalb, dass man mir meine Anfänger-Fragen nachsieht. :) Ich beschäftige mich gerade mit den Grundlagen der Netzwerkkommunikation unter Python und habe einen ersten kleinen Serverdienst geschrieben, der mir einen rudimentären Zugang zum Betriebssystem bietet und Befehle wie z.B. 'ls' ausführt. Nicht spektakulär, aber ich will ja auch erst einmal verstehen. Bei der Programmierung des Clients stellen sich mir nun zwei Fragen:

(1) Wenn ich Antworten des Servers mit socket.recv() entgegennehme, muss ich dieser Methode zwingend eine Puffergröße übergeben. In der Python-Refernz heißt es dazu: "For best match with hardware and network realities, the value of bufsize should be a relatively small power of 2, for example, 4096." Woher weiß ich aber nun genau, welchen Wert ich da brauche?

(2) Verschiedene Betriebssystem-Befehle wie 'ls' oder 'pwd' erzeugen unterschiedliche Ausgaben. Aus der Sicht des Clients weiß ich vorher nicht, was da kommt. Bei 'pwd' kommt eine einzige Zeile, die mein Client auch vernünftig darstellt. Bei 'ls' hingegen erzeugt der Server zunächst eine Liste, die dann zeilenweise an den Client geschickt wird. Da klappt der Empfang nicht so, wie ich es mir erhoffe - einige Zeilen werden angezeigt, andere erst dann, wenn ich clientseitig erneut die Enter-Taste drücke. Danach ist mein Client allerdings "durcheinander", und ich auch.

Vielleicht kann mich jemand erhellen. Vielen Dank und freundliche Grüße

fauxxami
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@fauxxami: die Puffergröße ist relativ egal, weil es kommen eh die Bytes, die gerade anliegen. Also eine sehr große Puffergröße würde nie genutzt werden, bei einer zu kleinen Puffergröße ist der einzige Nachteil, dass es etwas langsamer wird, weil viele Systemaufrufe stattfinden.

Zu (2): Du brauchst ein Protokoll, an dem Du erkennst, wann eine Antwort fertig ist. Also im einfachsten Fall ein Header mit Länge in Bytes und falls kein weiterer Block mehr kommt, dann kann die Länge ja 0 sein.

Um konkret zu sagen, was Dein Code falsch macht, brauchen wir den Code.
fauxxami
User
Beiträge: 6
Registriert: Freitag 16. Oktober 2020, 11:06

Vielen Dank für die Antworten:
die Puffergröße ist relativ egal, weil es kommen eh die Bytes, die gerade anliegen.
Ok, das habe ich verstanden. Also kommen die Pakete höchstens verzögert, gehen aber nicht verloren. Ist auch eigentlich klar, ist ja TCP.
Du brauchst ein Protokoll, an dem Du erkennst, wann eine Antwort fertig ist. Also im einfachsten Fall ein Header mit Länge in Bytes und falls kein weiterer Block mehr kommt, dann kann die Länge ja 0 sein.
Ich glaube, mir ist gerade selber eine Lösung eingefallen. Bisher habe ich die Systemaufrufe mit dem Modul os realisiert, aber gerade gelesen, dass man das heute eigentlich besser mit subprocess macht. Und subprocess.check_output() liefert ja immer einen Byte-String, den ich so, wie er ist, über das Socket versenden kann - dann muss sich der Client nur noch darum kümmern, was drin ist. Aber dann weiß man jedenfalls, dass immer ein solcher Byte-String kommt.

Grundsätzlich allerdings wüsste ich gerne, ob ich auf der einen Seite zu jedem Aufruf von socket.send() einen entsprechenden Aufruf von socket.recv() brauche. Denn dann würde mir deine Aussage zum "Protokoll" sinnvoll erscheinen, weil ich ja sonst auf der Client-Seite gar nicht weiß, wie häufig ich socket.recv() aufrufen muss. Oder?
Um konkret zu sagen, was Dein Code falsch macht, brauchen wir den Code.
Poste ich gerne, sobald mir jemand etwas zu den vorstehenden Fragen geschrieben hat. Noch habe ich den Ehrgeiz, es "alleine" zu schaffen.
fauxxami
User
Beiträge: 6
Registriert: Freitag 16. Oktober 2020, 11:06

Hmmm, da das nun doch alles nicht so klappt, wie es soll, schicke ich doch schon mal den Client-Code:

Code: Alles auswählen

import socket

# Socket für IPv4 und TCP erzeugen:
clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 

# Socket mit dem Server-Socket verbinden:
clientSocket.connect(("server.local", 10000))

# Kommunikation mit dem Server beginnen:
try:
    
    # Begrüßungsmeldung des Servers empfangen und ausgeben:
    antwort = clientSocket.recv(1024)
    print(antwort.decode())
    
    # Endlosschleife für die Befehlseingabe:
    while(True):
        
        # Befehl eingeben und absenden:
        befehl = input("Anweisung: ")
        clientSocket.send(befehl.encode())
        
        # Bei Eingabe von 'bye' soll die Verbindung
        # beendet werden; Endlosschleife verlassen:
        if befehl == "bye":
            
            break
        
        else:
            
            # Alle anderen Anweisungen erzeugen einen Byte-String;
            # dieser wird empfangen und ausgegeben:
            antwort = clientSocket.recv(4096)
            print(antwort.decode())
        
finally:
    
    # Das Socket wird geschlossen:
    clientSocket.close()
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@fauxxami: TCP ist nicht paket- oder nachrichtenorientiert. `send()` und `recv()` gehören nicht 1:1 zusammen. `send()` muss auch gar nicht alles senden was man da übergibt! Du willst da `sendall()` verwenden. Und `recv()` kann zwischen einem Byte und der Anzahl die man da als Argument angegeben hat alles liefern. Im Extremfall muss man damit klar kommen können, das `recv()` immer nur ein einzelnes Byte liefert bei jedem Aufruf. Oder umgekehrt das bei einem `recv()`-Aufruf auch Daten kommen können die mehr als eine Nachricht ganz oder teilwesie enthalten können. Darum braucht man ein Protokoll, also eine Möglichkeit Nachrichtengrenzen zu erkennen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Sirius3 hat geschrieben: Freitag 16. Oktober 2020, 13:55 @fauxxami: die Puffergröße ist relativ egal, weil es kommen eh die Bytes, die gerade anliegen. Also eine sehr große Puffergröße würde nie genutzt werden, bei einer zu kleinen Puffergröße ist der einzige Nachteil, dass es etwas langsamer wird, weil viele Systemaufrufe stattfinden.
"Etwas langsamer" kann hier durchaus 10x langsamer sein, wenn IO das Bottleneck ist und du viele Daten hast. Praktischerweise gibt es auf sockets eine `makefile` Methode, die ein Objekt zurück gibt dass sich um Buffering kümmert. Dann kann man soviel Abfragen wie es gerade praktisch ist ohne die Konsequenz jedesmal einen Systemaufruf zu machen.
fauxxami
User
Beiträge: 6
Registriert: Freitag 16. Oktober 2020, 11:06

Ups, langsam, ich komme nicht mehr mit:
TCP ist nicht paket- oder nachrichtenorientiert.
Nicht paketorientiert? Das wäre mir jetzt neu und widerspricht allem, was ich bisher so gelernt habe. Wikipedia meint z.B.: "Das Protokoll ist ein zuverlässiges, verbindungsorientiertes, paketvermitteltes Transportprotokoll in Computernetzwerken." Aber wie auch immer: ich habe noch nicht verstanden, wo der Zusammenhang mit meinem Problem ist.
`send()` und `recv()` gehören nicht 1:1 zusammen. `send()` muss auch gar nicht alles senden was man da übergibt!
Jetzt wird es doch spannend. Ich dachte: ich konstruiere auf der einen Seite ein Byte-Objekt, verschicke es per send(), und dann empfange ich das genauso auf der anderen Seite mit revc(). Das ist wohl falsch, ich habe eben noch mal die Referenz dazu gelesen. An der Stelle ist Java dann wohl doch leichter zu verstehen.
Du willst da `sendall()` verwenden.
Ok. Wenn ich das will, dann will ich das.
Darum braucht man ein Protokoll, also eine Möglichkeit Nachrichtengrenzen zu erkennen.
Verstehe. Und dieses Protokoll denke ich mir selbst aus? Die sendende Instanz ermittelt z.B. die Größe des Byte-Objekts, teilt sie dem Empfänger mit und der prüft dann, ob er alles bekommen hat? Und wenn nicht, sagt er: schicke den Kram noch mal?
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Was spricht denn eigentlich gegen HTTP? Dann muss man sich um viele Detailfragen bezüglich der reinen Übertragung nicht mehr kümmern. Gerade wenn, wie hier, die Kenntnisse zum OSI-Modell eher begrenzt sind, würde ich fast immer zur höchstmöglichen Schicht raten...
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die Beschreibung als nicht Paketorientiert bezieht sich auf die Abstraktion die du benutzt. Das socket interface. Das garantiert nicht, das ein Datenpaket das du abgeschickt hast als Ganzes auch so ungeteilt ankommt. Sondern nur einen beliebigen Strom von Bytes, die keine Abgrenzung haben. Das darunter das Protokoll Pakete nutzt, ist dafür genauso irrelevant wie die Frage ob Licht, Elektronen oder elektromagnetische Wellen als Medium genutzt werden.

Und darum braucht es ein Protokoll. Das kann ganz einfach zeilenbasiert sein. Oder mit einem fixen Header und einer längenangabe wie HTTP. Und diverse andere Spielformen.

Was du nicht machen musst ist bei einer stabilen (!) Verbindung gegenprüfen, ob alles angekommen ist. Das garantiert TCP, oder du bekommst einen Fehler. Was du dann machst hängt aber im Grunde über diesem Protokoll. Zb neu Anfragen. Oder einfach das nächste Datum senden.
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@fauxxami: Okay, wir müssen hier auseinanderhalten wie TCP selbst arbeitet und was Du als Programmierer als API bekommst. An dem Wort „paketvermitteltes“ ist auf Wikipedia ja eine Fussnote mit dem Inhalt:

„Nicht zu verwechseln mit paketvermittelnd. Die Aufgabe von TCP ist es nicht, Pakete zu übertragen, sondern die Bytes eines Datenstroms. Die Paketvermittlung wird durch das Internetprotokoll (IP) bereitgestellt. Daher ist IP paketvermittelnd aber TCP paketvermittelt.“

Ist also kein Widerspruch zu dem was ich gesagt habe sondern sagt genau das gleiche.

Wenn eine Programmiersprache Socketprogrammierung einfacher macht, dann bastelt sie da noch was auf die BSD-Socket-API drauf, die letztlich heutzutage immer darunter liegt. Ist in Python letztlich nicht so viel anders, denn man kann sich ja vom Socket-Objekt ein Dateiobjekt geben lassen, das die üblichen `read()`- und `write()`-Methoden hat.

Das Protokoll denkst Du Dir selbst aus. Aber eigentlich nur wenn man langeweile hat, denn es gibt ja schon so viele Protokolle da draussen, mit Implementierungen.

Grösse von Nachrichten als erstes schicken ist eine von mehreren Varianten die möglich sind.

Der Empfänger bekommt immer alles was gesendet wurde, in der Reihenfolge in der es gesendet wurde. Dafür sorgt TCP schon.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
fauxxami
User
Beiträge: 6
Registriert: Freitag 16. Oktober 2020, 11:06

Vielen Dank für eure Beiträge, dadurch habe ich einiges gelernt. Und mein kleines, rudimentäres Anfangsprogramm läuft mit der Umstellung von send() auf sendall() ganz prima.
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@fauxxami: wenn Du an recv nichts geändert hast, dann läuft das nicht ganz prima, sondern einfach nur durch Zufall nicht katastrophal. Das Problem ist, dass solche Tests meist mit dem selben System unter idealen Bedingungen gemacht werden: keine Paketverluste, keine zwischengeschalteten Router, keine Zeitverzögerungen, ...
Benutzeravatar
sparrow
User
Beiträge: 4164
Registriert: Freitag 17. April 2009, 10:28

@fauxxami: Dann probier doch mal aus, was passiert, wenn deine "Willkommensnachricht" länger als 1024 Byte ist oder, wenn du nach 5 Byte einfach mal eine Minute nichts beim Sender.
Der Empfänger kann in deinem Programm nicht wissen, ob die Nachricht vollständig ist. "Funktioniert ganz prima" bedeutet in diesem Fall wirklich nur "Funktioniert, wenn man gerade Glück hat", und das ist doch nicht der Anspruch, den man hat - egal ob es ein Anfangsprogramm ist oder nicht.
Also entweder überlegst du dir ein Protokoll - was als Übung nicht schlecht ist - oder du musst etwas Eingebautes finden, was dir hilft.
fauxxami
User
Beiträge: 6
Registriert: Freitag 16. Oktober 2020, 11:06

"Funktioniert ganz prima" bedeutet in diesem Fall wirklich nur "Funktioniert, wenn man gerade Glück hat", und das ist doch nicht der Anspruch, den man hat - egal ob es ein Anfangsprogramm ist oder nicht.
Das ist völlig richtig - und deshalb bleibt es ja auch nicht bei der ersten Variante. Ich bin ja froh, mich ans Forum gewandt zu haben, weil mir eure Beiträge gezeigt haben, wo die Probleme liegen.
Also entweder überlegst du dir ein Protokoll - was als Übung nicht schlecht ist - oder du musst etwas Eingebautes finden, was dir hilft.
Was Eingebautes ist langweilig - ich überlege mir was Eigenes.
Antworten