Python sockets // NAT und Portweiterleitung

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
py4fun
User
Beiträge: 16
Registriert: Donnerstag 3. Januar 2013, 20:07

Hallo,
ich habe eine Frage, welche im Rahmen der Socket-Programmierung in Python aufgetreten ist.
Diese bezieht sich jedoch nicht lediglich auf Python, doch ich hoffe, dass ihr mir trotzdem weiterhelfen könnt.
Ich versuche meine Frage anhand eines kleinen, äußerst simplen Beispieles zu erklären.

Nehmen wir an der Server (s. unten) wird auf einem Rechner gestartet, welcher hinter einem ( NAT - ) Router sitzt. Dieser lauscht nun nach eingehenden Verbindungen auf dem Port 55555. Damit der Server auch Verbindungen annehmen kann, welche außerhalb seines Lan - / WLAN Netzwerkes liegen, ist auf dem Router Portweiterleitung eingerichtet. Alle eingehenden TCP Verbindungen auf Port 55555 werden also an den Rechner weitergeleitet, auf welchem der Server läuft. Nun kommen wir zu dem Client (s. unten). Diesen starten wir auf einem Rechner, welcher sich hinter einem anderen ( NAT - ) Router befindet. Dieser sendet eine Verbindungsanfrage an den Server. Dazu wird als IP-Adresse die WAN-IP des Routers benutzt, hinter welchem der Server sitzt. Als Port wird (natürlich) auch 55555 genutzt. Der Router sendet die Verbindungsanfrage an den Server weiter, welcher diese akzeptiert. Der Client sendet daraufhin, wie man unten sehen kann, eine Anfrage an den Server und möchte eine MP3 Datei vom Server herunterladen. Die Anfrage landet wieder beim Router, hinter dem der Server sitzt und dieser leitet erneut die Anfrage weiter. Da die MP3 Datei zu groß ist um in einem Datenpacket transferiert zu werden / empfangen zu werden, wird sie in einzelne Parts 'geteilt', welche dann nacheinander zum Client gesendet werden. Die einzelne Packete wandern nacheinander über den Router des Clients, da dies ja die 'Absendeadresse' der Anfrage ist. Der Router des Clients 'merkte' sich die IP-Adresse, des PC's in seinem Lan - / WLAN Netzwerk, welcher die Anfrage an den Server - bzw. an die IP-Adresse des Routers, hinter welchem der Server sitzt - gesendet hat und leitet dann grundsätzlich die Antworten des Servers an den PC, auf welchem der Client läuft, weiter. Und HIER schneidet jetzt meine Frage ein. Ich habe doch eigentlich nur eine Anfrage an den Server gesendet und zwar die 'Downloadanfrage'. Werden trotzdem alle nun einkehrenden Datenpackete an dem Router, hinter dem der Client-PC sitzt, noch an den Rechner weitergeleitet, auf dem der Client läuft, oder nur das erste eingehende Packet? Und wieso?
Ich weiß, dass hört sich seltsam an, doch ich hoffe, ihr versteht meine Frage und könnt diese beantworten. Falls ihr noch Rückfragen habt, so stellt diese doch einfach :)

SERVER

Code: Alles auswählen

# -*- coding: utf-8 -*-
## Server Test
import socket, time

ip = "" # IP-Adresse

tcp = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tcp.bind ((ip, 55555))
tcp.listen (1)
comm, addr = tcp.accept()

path = comm.recv (100)

file = open (path, "rb") 
file_content = file.read() # Datei wird ausgelesen
file.close()

comm.send (file_content)
time.sleep (3) # kurze Zeitverzögerung
comm.send ("READY") # Wenn alles abgeschickt wurde, wird dem Client vermittelt, dass der Datentransfer abgeschlossen ist
CLIENT

Code: Alles auswählen

# -*- coding: utf-8 -*-
## Client Test
import socket
ip = "" # IP-Adresse des Servers

comm = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
comm.connect ((ip, 55555))

downloadpath = "/home/username/ieine/mp3datei.mp3"

comm.send (downloadpath)

file = open ("test.mp3", "w") # Datei wird erstellt/bestehende überschrieben
file.close()

while True:
    data = comm.recv (50000) # 50000 ist nur eine Beispielgröße
    if data != "READY": # Wurden alle Stücke der mp3-Datei gesendet, so sendet der Server 'READY' und das Programm beendet
        file = open ("test.mp3", "ab") # Datei wird Stück für Stück vervollständigt
        file.write (data)
        file.close()
    else:
        print data
        break
Danke jetzt schonmal
py4fun
BlackJack

@py4fun: Du hast eine TCP-*Verbindung* aufgebaut. Solange die besteht weiss der Router auf Empfängerseite natürlich welcher Rechner dort diese Verbindung aufgebaut und und leitet alle zu dieser Verbindung gehörenden Daten auch an diesen Rechner weiter. Dass das letztendlich in Datenpaketen passiert ist eine Ebene unter der TCP-Verbindung, denn auch wenn *Du* die Daten in kleineren Häppchen auf die Reise schickst haben diese „Pakete” und ihre Grösse nichts mit den tatsächlich übertragenen Paketen und deren Grösse zu tun. TCP ist ein Protokoll das einen *Datenstrom* bereit stellt. Eben aus dem Grund ist Dein Programm auch fehlerhaft. Auf dem Server ist nicht garantiert, dass mit dem `recv(100)` auch tatsächlich der gesamte Pfad geliefert wird, selbst wenn der komplett gesendet wurde. Und auf der Clientseite ist nicht garantiert das mit dem `send()` auch wirklich der komplette Pfad gesendet wurde. Ausserdem ist der Empfang der Zeichenkette 'READY' keine Garantie, dass die nicht mitten *in* den Daten stand (MP3s können auch Metadaten enthalten) noch ist garantiert, dass das abschliessende 'READY' in *einem* `recv()`-Aufruf geliefert, noch dass es für sich alleine kommt und nicht noch Daten davor stehen. Das *kann* alles so funktionieren, da verlässt Du Dich dann aber sehr auf Dein Glück.

Nebenbei: Das englische `ready` bedeutet fertig im Sinne von bereit und nicht im Sinne von abgeschlossen. Ready ist also eher eine Vokabel die *vor* einer Übertragung oder an deren Anfang benutzt wird, und nicht *nach* einer Übertragung. Das wäre eher 'FINISHED' oder etwas ähnliches. Wobei man IMHO hier gar kein Signal braucht, wenn der Server einfach nach der Übertragung die Verbindung schliesst. *Das* sagt nämlich ziemlich deutlich, dass man die Übertragung beendet hat. :-)
py4fun
User
Beiträge: 16
Registriert: Donnerstag 3. Januar 2013, 20:07

Ok, erstmal danke für die schnelle Antwort!
BlackJack hat geschrieben:Nebenbei: Das englische `ready` bedeutet fertig im Sinne von bereit und nicht im Sinne von abgeschlossen. Ready ist also eher eine Vokabel die *vor* einer Übertragung oder an deren Anfang benutzt wird, und nicht *nach* einer Übertragung. Das wäre eher 'FINISHED' oder etwas ähnliches
Ja okay, da habe ich vllt. einen String ungünstig benannt, aber dieses Script wurde auch nicht perfektioniert, sondern nur kurz verfasst. Ich werde beim nächsten mal auf soetwas achten.
BlackJack hat geschrieben:Dass das letztendlich in Datenpaketen passiert ist eine Ebene unter der TCP-Verbindung, denn auch wenn *Du* die Daten in kleineren Häppchen auf die Reise schickst haben diese „Pakete” und ihre Grösse nichts mit den tatsächlich übertragenen Paketen und deren Grösse zu tun. TCP ist ein Protokoll das einen *Datenstrom* bereit stellt.
Könntest du mir das eventl. noch mal etwas genauer erklären?
BlackJack hat geschrieben:Auf dem Server ist nicht garantiert, dass mit dem `recv(100)` auch tatsächlich der gesamte Pfad geliefert wird, selbst wenn der komplett gesendet wurde. Und auf der Clientseite ist nicht garantiert das mit dem `send()` auch wirklich der komplette Pfad gesendet wurde.
Wieso denn das? In diesem Beispiel doch schon.
BlackJack hat geschrieben: Ausserdem ist der Empfang der Zeichenkette 'READY' keine Garantie, dass die nicht mitten *in* den Daten stand (MP3s können auch Metadaten enthalten)
Das ist natürlich richtig, dass MP3 Dateien auch Metadaten enthalten können. aber wie kann man denn sonst den beendet Datentransfer kennzeichnen? Wie du unten sagst kann man die Verbindung schließen (comm.close()), aber was ist, wenn man danach noch weitere Dateien herunterladen möchte?
BlackJack hat geschrieben:noch ist garantiert, dass das abschliessende 'READY' in *einem* `recv()`-Aufruf geliefert, noch dass es für sich alleine kommt und nicht noch Daten davor stehen
mmh, selbst durch die Zeitverzögerung nicht?

Da es ja genug zu 'bemängeln' gab, fände ich es nett von dir, wenn du diese kleinen Skripte nach deinen Vorstellungen optimieren könntest!

Danke
BlackJack

@py4fun: Ich möchte jetzt hier eigentlich nicht TCP erklären. Dazu sollte es im Netz und Büchern genug Erklärungen geben.

Nein, in Deinem Beispiel wird bei `recv()` und `send()` nichts garantiert was nicht in der Dokumentation steht. Dass das zufällig funktioniert, und anscheinend auch wiederholbar ist, heisst nicht das das Programm nicht kaputt ist solange es nicht mit allem klar kommt was so laut Spezifikation der Socket-API passieren kann. Und früher oder später in der Ralität dann auch passieren wird.

Wenn Du mehr als eine Übertragung über eine TCP-Verbindung abwickeln willst, dann musst Du ein vorhandenes Protokoll verwenden oder ein eigenes entwerfen. Die beiden üblichen Methoden sind entweder eindeutige Endkennzeichen für die Daten, die dann natürlich innerhalb der Daten selbst nicht vorkommen dürfen, oder das man vor den eigentlichen Daten die Information überträgt wie viele Daten folgen werden. Man kann auch beide Verfahren kombinieren. Zum Beispiel in dem man die Metadaten mit Endkennzeichen versieht und darin die Länge der Nutzdaten kodiert. Beispiel ist HTTP dessen Header durch eine Leerzeile abgeschlossn werden und innerhalb dieser Header ist die Länge der Nutzdaten anegeben, die nach dem Header folgt. HTTP würde sich auch prima für den Beispielfall eignen. Einen einfachen HTTP-Server kann man sich auch sehr leicht aus der Standardbibliothek zusammenbasteln.

Die Zeitverzögerung ist wieder so ein Fall von: Das kann gut gehen, tut es vielleicht auf oft wenn man es zum Beispiel in einem wenig ausgelasteten lokalen Netz macht, aber es ist nun mal keine zuverlässige, robuste Lösung, sondern schlicht kaputt.
py4fun
User
Beiträge: 16
Registriert: Donnerstag 3. Januar 2013, 20:07

mmh okay danke,
da scheine ich noch mehr zu TCP und Netzwerkprogrammierung lernen zu müssen, als ich dachte. :/
py4fun
User
Beiträge: 16
Registriert: Donnerstag 3. Januar 2013, 20:07

an BlackJack :
Wenn mein Beispiel falsch ist, so ist doch auch dieses hier falsch, oder?
http://openbook.galileocomputing.de/pyt ... 82e23d6267
Hier wird doch dann auch einfach davon ausgegangen, dass die Daten alle gesendet wurden und die gesamte Nachricht mit recv (1024) geliefert wird.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Ach, das ist nur ein weiterer der vielen Fehler in diesem "Meisterwerk".
Das Leben ist wie ein Tennisball.
BlackJack

@py4fun: Socket-Programmierung sieht auf den ersten Blick so schön einfach aus, und insbesondere wenn man nur lokal auf dem selben Rechner oder einem schwach ausgelasteten lokalen Netzwerk arbeitet funktionieren diese falschen Beispiele scheinbar auch. Deshalb denken viele Autoren offenbar, man könnte da mal eben so ein simples Beispiel hinklatschen.

Dieser Text aus der Python-Dokumentation zeigt wie man es richtig macht: http://docs.python.org/2/howto/sockets.html
py4fun
User
Beiträge: 16
Registriert: Donnerstag 3. Januar 2013, 20:07

Kein Wunder, dass ich vieles falsch gemacht habe, da ich das meiste der Socket-Programmierung von dieser Webseite genommen habe :roll:
Da muss ich mich wohl noch mal hinsetzen und mich damit beschäftigen.
Danke für den Link :)
BlackJack

@py4fun: Ob Du Dich damit auseinandersetzen musst, hängt davon ab was Du machen willst. Üblicherweise möchte man sich nicht auf diese unteren Ebene bewegen. Es gibt ja schon für alles Mögliche Protokolle und Implementierungen davon. Für das einfache anfragen und übertragen von Dateien würde sich wie gesagt HTTP eignen.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

py4fun hat geschrieben:Kein Wunder, dass ich vieles falsch gemacht habe, da ich das meiste der Socket-Programmierung von dieser Webseite genommen habe :roll:
Von der Seite solltest du gar nichts "nehmen", denn das was du da lernst ist eher wie ein schlechter Trip.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Antworten