Probleme mit TCP-Sockets

NJay

Moderator
Mitarbeiter
Team
Mitglied seit
19 März 2012
Beiträge
5.351
Danke
304
#1
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!
 

Amari

Administrator
Mitarbeiter
Team
Mitglied seit
16 April 2006
Beiträge
6.516
Danke
412
#2
Ist das nicht 1:1 von hier abgeschrieben? Mal abgesehen von der Zeile mit socket.accept(), die hast du mit in die Schleife getan.
TcpCommunication - Python Wiki

Funktioniert bei dir das verlinkte Beispiel?
 

NJay

Moderator
Mitarbeiter
Team
Mitglied seit
19 März 2012
Beiträge
5.351
Danke
304
#3
Abgeschrieben ist es nicht, habe mir das selbst so über diverse Dokus zusammengereimt. :D

Ich werde das Beispiel mal eins zu eins kopieren und versuchen.
 

kingspride

Moderator
Mitarbeiter
Team
Mitglied seit
30 Juni 2010
Beiträge
16.657
Danke
1.022
#4
Huhu, hab das mal hier nachgespielt, und mir andere beispiele angeschaut.

Du brauchst noch 2 Dinge:
1. Du musst die Daten, die der Client beim ersten Mal verbinden sendet, vollständig akzeptieren, sonst gibt es einen CONNECTION_RESET (kann man in der Browser Entwicklerkonsole sehen)
2. Du solltest die Rückverbindung zum Client nach senden deiner Daten schließen, mit conn.close()

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)

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

while True:
    connection,address = http_s.accept()
    connection.recv(1024)
    test123 = "HTTP/1.1 200 OK\n" + "Content-Type:text/html\n" + "Content-Length:" + str(len(webpage)) + "\n" + "\n" + webpage + "\n"
    connection.send(test123.encode('UTF-8'))
    connection.close()
 

NJay

Moderator
Mitarbeiter
Team
Mitglied seit
19 März 2012
Beiträge
5.351
Danke
304
#5
Du meinst mir fehlt das .recv(1024)? Das hatte ich auch mal drin, hat aber keinen Unterschied gemacht.

conn.close() möchte ich an dieser stelle nicht machen, die Verbindung soll ja für weitere Anfragen offen bleiben.
 

kingspride

Moderator
Mitarbeiter
Team
Mitglied seit
30 Juni 2010
Beiträge
16.657
Danke
1.022
#6
HTTP funktioniert so aber nicht...
Da wird vom Client ne Verbindung aufgebaut, der Server antwortet, und dann wird das wieder geschlossen.

Außerdem schließt das conn.close hier nur die Verbindung zum Client, das Programm bleibt aber laufen und erwartet weitere Verbindungen bis du es abschießt.

auf meiner windows kiste hat das so funktioniert, mit python3. webbrowser macht anfrage, bekommt website geliefert, beendet laden. per reload kann ich die webseite dann beliebig oft neu anfordern...
 

kingspride

Moderator
Mitarbeiter
Team
Mitglied seit
30 Juni 2010
Beiträge
16.657
Danke
1.022
#7
nachtrag: das funktioniert auf ubuntu 18.04 budgie mit python 2 ebenfalls
 

NJay

Moderator
Mitarbeiter
Team
Mitglied seit
19 März 2012
Beiträge
5.351
Danke
304
#8
HTTP funktioniert so aber nicht...
Da wird vom Client ne Verbindung aufgebaut, der Server antwortet, und dann wird das wieder geschlossen.

Außerdem schließt das conn.close hier nur die Verbindung zum Client, das Programm bleibt aber laufen und erwartet weitere Verbindungen bis du es abschießt.
Das sich das Programm nicht schließt ist mir auch klar. Soweit ich weiß kann man eine TCP Session schon offen lassen?
 

kingspride

Moderator
Mitarbeiter
Team
Mitglied seit
30 Juni 2010
Beiträge
16.657
Danke
1.022
#9
kann man, ja.
vielleicht solltest du noch ein bisschen mehr Kontext liefern :)
Sonst wird das hier glaub ich Kaffeesatzleserei.

Die meisten machen dann aber einfach in der while True noch eine zweite while True, in der dann recieve und send ablaufen, sodass die verbindung offen bleibt.

siehe auch:
EDIT: oha, wir haben embedded git? nice
 

kingspride

Moderator
Mitarbeiter
Team
Mitglied seit
30 Juni 2010
Beiträge
16.657
Danke
1.022
#10
achso
mit
Python:
while True:
    letter = conn.recv(1)
    if letter == "\r":
        break
    data += letter
kann man übrigens beliebig lange auf einen Terminator warten falls das gebraucht wird
 

NJay

Moderator
Mitarbeiter
Team
Mitglied seit
19 März 2012
Beiträge
5.351
Danke
304
#11
Vielen Dank. Hatte die letzten Tage keine Zeit mich mit dem Thema auseinanderzusetzen. Morgen sollte ich dazu kommen. Ich melde mich, wenns was neues gibt.
 

NJay

Moderator
Mitarbeiter
Team
Mitglied seit
19 März 2012
Beiträge
5.351
Danke
304
#12
@kingspride

Ich habe mir deinen Code mal angeschaut und mit meinem verglichen. Mir ist nur ein nennenswerter Unterschied aufgefallen. Du hast zur Bestimmung der Content-length len() benutzt, ich sys.getsizeof(). Ich habe diese Zeile geändert und jetzt läufts... Also zumindestens im Test file. Im richtigen Code geht es nur mit der Änderung noch nicht. Da muss ich noch etwas debuggen. Trotzdem schon mal Danke für die Hilfe.
 

kingspride

Moderator
Mitarbeiter
Team
Mitglied seit
30 Juni 2010
Beiträge
16.657
Danke
1.022
#13
len und sizeof von "Hello World" zu machen, während man eigentlich ein komplettes HTML Dokument schickt, hat mein Browser mit einem Verbindungsabbruch und der Meldung, dass Content-Length nicht mit der übermittelten Länge übereinstimme, quittiert.
Daher habe ich das noch zusätzlich auf den kompletten Inhalt ausgeweitet ( len(webpage) ). Also der relevante Teil ohne den HTTP header.
 

Amari

Administrator
Mitarbeiter
Team
Mitglied seit
16 April 2006
Beiträge
6.516
Danke
412
#14
Zudem sind das auch zwei komplett verschiedene Funktionen. sys.getsizeof() gibt dir nicht die Anzahl der Elemente aus sondern den belegten Speicher für dieses Objekt.
 

NJay

Moderator
Mitarbeiter
Team
Mitglied seit
19 März 2012
Beiträge
5.351
Danke
304
#15
Zudem sind das auch zwei komplett verschiedene Funktionen. sys.getsizeof() gibt dir nicht die Anzahl der Elemente aus sondern den belegten Speicher für dieses Objekt.
Das ist mir bewusst.
Ich bin davon ausgegangen, dass eben die größe in Bytes relevant ist und nicht die Anzahl der Zeichen.
 

NJay

Moderator
Mitarbeiter
Team
Mitglied seit
19 März 2012
Beiträge
5.351
Danke
304
#18
Ich konnte das Problem nun endgültig lösen.

1.) Ich hatte in meinem richtigen projekt (hatte ich hier nicht gepostet) einen Try catch Block, der alle Exceptions gefangen hat. Deshalb ist mir nicht aufgefallen, dass das Einlesen eines Files statt der letzten Zeile des Files fälschlicherweise das EOF eingelesen hat.

2.) Vor einem connection.close() muss ein connection.shutdown() aufgerufen werden. connection.close() löscht zwar den Sockel, beendet aber nicht die laufende TCP-Verbindung. Somit hat der Browser nicht mitbekommen, dass die Verbindung nicht mehr existiert und versucht bei erneutem Aufrufen des Servers, die alte connection zu benutzen. Die Notwendigkeit des shutdowns steht auch Fett in der Doku, ich habs einfach bisher übersehen gehabt.

Vielen Dank für eure Hilfe.
 

kingspride

Moderator
Mitarbeiter
Team
Mitglied seit
30 Juni 2010
Beiträge
16.657
Danke
1.022
#19
.... bei sowas frag ich mich immer, warum close dann nicht einfach selbstständig shutdown ruft... wenn es doch eh "zwingend" erforderlich ist .. ?
 

Amari

Administrator
Mitarbeiter
Team
Mitglied seit
16 April 2006
Beiträge
6.516
Danke
412
#20
.... bei sowas frag ich mich immer, warum close dann nicht einfach selbstständig shutdown ruft... wenn es doch eh "zwingend" erforderlich ist .. ?
Macht aber irgendwo schon Sinn. Alleine schon um Fehler gezielter abzufangen. In der Doku steht zudem das du bei dem Shutdown mit angeben kannst in welche Richtung du den socket zu machst. Je nach Anwendung kannst du also sehr sauber eine Verbindung beenden. Und da es zur lowlevel Programmierung gedacht ist, ist das schon in Ordnung so. Wem das zu kompliziert ist benutzt flask oder aiohttp
 

NJay

Moderator
Mitarbeiter
Team
Mitglied seit
19 März 2012
Beiträge
5.351
Danke
304
#21
So Low-Level bhabe ich mich auch nicht freiwillig bewegt, die Aufgabe der Uni hat Sockets mit TCP und UDP (jeweils einmal) vorgeschrieben.

Und hätte ich die Doku besser gelesen, hätte ich auch nicht so viele Probleme gehabt. :D
 
Oben