Python Probleme mit TCP-Sockets

NJay

Rear Admiral
Registriert
Aug. 2013
Beiträge
5.491
Hallo,

wir sollen für die Uni ein Verteiltes System in einer Sprache unserer Wahl schreiben. Ein Teil davon ist es, einen HTTP-Server nur mit Sockets zu schreiben, ich darf also keine HTTP-Bibliotheken benutzen. Leider bereitet mir das ganze große Probleme. Da es in meinem großen Projekt überhaupt nicht funktioniert hat, habe ich ein kleines Dummyprojekt erstellt, doch auch da funktioniert es nicht so wie ich es mir vorstelle. Als OS kommt Linux (Arch) zum Einsatz. Es muss ein TCP-Socket sein, UDP-Sockets werden im Projekt an anderer Stelle benutzt und funktionieren bei mir auch wunderbar.

Dummyprojekt:

Python:
import socket
import sys

ip = sys.argv[1]
port_http = int(sys.argv[2])

http_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # Internet and TCP

http_s.bind((ip, port_http))

http_s.listen(10)

while True:
    conn, addr = http_s.accept()

    test123 = "HTTP/1.1 200 OK\n" + "Content-Type:text/html\n" + "Content-Length:" + str(sys.getsizeof("Hello World")) + "\n" + "\n" + "<html><body>" + "Hello World" + "</body></html>\n"

    conn.send(test123.encode('UTF-8'))

Was ich mir vorstelle was das Programm macht: Auf einem von mir bestimmter IP (127.0.0.1) und einem Port (z.B. 2001) wird ein Socket erstellt. Dieser wird gebunden und das Programm geht in die while True Schleife über. Nun warted es auf eine Connection, akzeptiert diese und schickt ein HTTP 200 OK mit Hello World und schließt die connection. Dann wartet es auf eine erneute Anfrage.

Problem: Es wird erst eine Antwort geschickt, wenn ich das Python programm beende. Meine Vermutung ist, dass die Daten im Buffer des Sockets landen und deshalb erst geschickt werden, wenn der Socket geschlossen wird. Also setze ich testweise die Buffergröße auf 1 Byte:

Python:
conn.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1)

Doch das funktioniert nicht... Dann habe ich mir gedacht, ich schicke einfach leere Daten hinterher (Leerzeichen), sodass der Buffer zwangsläufig volläuft und gesendet werden muss:

Python:
conn, addr = http_s.accept()
conn.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1024)
test123 = "HTTP/1.1 200 OK\n" + "Content-Type:text/html\n" + "Content-Length:" + str(sys.getsizeof("Hello World")) + "\n" + "\n" + "<html><body>" + "Hello World" + "</body></html>\n"

conn.send(test123.encode('UTF-8'))
test = ""
for i in range(0, 1025):
    test = test + " "
sys.getsizeof(test)
conn.send(test.encode('UTF-8'))

Das ganze funktioniert manchmal, also vermutlich nur, wenn ich mit meinen Leerzeichen ufälligerweise genau die verbleibende Buffergröße treffe...

Wie mache ich das ganze richtig? Wie kann ich einfach Daten über eine TCP Verbindung schicken, wann ich will?

Vielen Dank!
 
Hancock schrieb:
Mal versucht: https://docs.python.org/2/library/socket.html

socket.close()
Close the socket. All future operations on the socket object will fail. The remote end will receive no more data (after queued data is flushed). Sockets are automatically closed when they are garbage-collected.

Habe ich versucht. Zwei Probleme:

1.) Irgendwie funktioniert das nicht immer.
2.) Die Session soll nicht direkt geclosed werden. Falls weitere Anfragen des selben Clients kommen, so sollen sie über die bereits bestehende Session ausgeliefert werden.
 
Dann verwende mal shutdown vorher, aber falls du mehrere Anfragen verarbeiten willst, brauchst du ein Connection: Keep-Alive und ein Content-Length Header. EDIT: Content-Length hast du schon. EDIT: Ahhm, Content-length ist die Anzahl der Bytes nach dem \r\n\r\n vom Header. EDIT3: Line-ending vom HTTP ist \r\n (nicht nur \n).

Auch wichtig: Prüfe, ob send auch alles gesendet hat. (siehe z.B. https://docs.python.org/2/howto/sockets.html#socket-howto)

Was über die Leitung geht, kannst du z.B. mit Wireshark (oder im Browser mit F12) prüfen, dann kannst du sehen, wo es hängt.
 
dein content-length ist falsch:
Code:
In [1]: import sys                                                            

In [2]: sys.getsizeof("Hello World")                                          
Out[2]: 60

In [3]: len("<html><body>Hello World</body></html>")                          
Out[3]: 37

der browser wartet darauf, dass die restlichen 60-37=23 bytes auch noch irgendwann mal kommen.

ausserdem solltest du socket.SO_REUSEADDR benutzen, sonst ist der port nach beenden des programms unnötig lange blockiert. so gehts:

Python:
import socket
import sys

ip = sys.argv[1]
port_http = int(sys.argv[2])

http_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # Internet and TCP
http_s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
http_s.bind((ip, port_http))
http_s.listen(10)

while True:
    conn, addr = http_s.accept()

    content = "<html><body>Hello World</body></html>"

    response = "HTTP/1.1 200 OK\r\n" \
               "Content-Type:text/html\r\n" \
               "Content-Length:%s\r\n" \
               "\r\n" \
               "%s" % (len(content), content)

    conn.sendall(response.encode("UTF-8"))

edit: ich würde sendall() statt send() verwenden, sonst musst du noch jedes mal prüfen ob auch alles gesendet wurde.

edit2: CRLF muss laut standard genutzt werden.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: abcddcba
Vielen Dank, ich werde mir eure Anmerkungen später einmal ansehen.
 
Rückmeldung:

Die Lösung war sehr einfach, ich hätte die Doku einfach besser lesen müssen. Vor dem connection.close() muss noch ein connection.shutdown() ausgeführt werden, da der socket sonst zwar gelöscht wird, aber kein ordentlicher TCP-Verbindungsabbau stattfindet.
 
Zurück
Oben