6 Stimmen

Aus der Steckdose lesen: Ist garantiert, dass mindestens x Bytes gelesen werden?

Ich habe einen seltenen Fehler, der beim Lesen einer Steckdose aufzutreten scheint.

Es scheint, dass ich beim Lesen von Daten manchmal nur 1-3 Bytes eines Datenpakets erhalte, das größer als dieses ist.

Wie ich aus der Pipe-Programmierung gelernt habe, erhalte ich dort immer mindestens 512 Bytes, solange der Absender genügend Daten bereitstellt.

Außerdem überträgt mein Sender jedes Mal, wenn er etwas sendet, mindestens >= 4 Bytes - ich dachte also, dass am Anfang (!!) der Übertragung mindestens 4 Bytes auf einmal empfangen werden.

In 99,9% aller Fälle scheint meine Annahme zuzutreffen ... aber es gibt wirklich seltene Fälle, in denen weniger als 4 Bytes empfangen werden. Es erscheint mir lächerlich, warum das Netzwerksystem dies tun sollte?

Weiß jemand mehr?

Hier ist der von mir verwendete Lesecode:

mySock, addr = masterSock.accept()
mySock.settimeout(10.0)
result = mySock.recv(BUFSIZE)
# 4 bytes are needed here ...
...
# read remainder of datagram
...

Der Absender sendet das komplette Datagramm mit einem Aufruf von send.

Edit: das Ganze funktioniert auf localhost - es sind also keine komplizierten Netzwerkanwendungen (Router etc.) beteiligt. BUFSIZE ist mindestens 512 und der Absender sendet mindestens 4 Bytes.

0 Stimmen

Darf ich fragen, wie viele Bytes Sie senden und auf welchen Wert BUFIZE eingestellt ist? In den anderen Antworten wird erklärt, dass dies ein völlig normales Verhalten ist und Sie immer in einer Schleife lesen/schreiben sollten, bis Sie genug gelesen haben, aber vielleicht können Sie herausfinden, warum das in diesem Fall auftritt

1 Stimmen

Überprüfen Sie tatsächlich den Rückgabewert von send, um zu überprüfen, wie viele Bytes gesendet werden?

0 Stimmen

Hallo Robert, dieser Vorschlag ist gut. Jetzt weiß ich es. Könnte aber auch Probleme machen ...

16voto

Robert S. Barnes Punkte 38141

Ich nehme an, Sie verwenden TCP. TCP ist ein auf Datenströmen basierendes Protokoll, das keine Vorstellung von Paketen oder Nachrichtengrenzen hat.

Das bedeutet, dass Sie beim Lesen möglicherweise weniger Bytes erhalten, als Sie angefordert haben. Wenn Ihre Daten zum Beispiel 128k groß sind, erhalten Sie beim ersten Lesen nur 24k und müssen erneut lesen, um den Rest der Daten zu erhalten.

Für ein Beispiel in C:

int read_data(int sock, int size, unsigned char *buf) {
   int bytes_read = 0, len = 0;
   while (bytes_read < size && 
         ((len = recv(sock, buf + bytes_read,size-bytes_read, 0)) > 0)) {
       bytes_read += len;
   }
   if (len == 0 || len < 0) doerror();
   return bytes_read;
}

1 Stimmen

Denn es scheint mir, dass Sie nur die Überschrift gelesen haben. Ich habe einen sehr speziellen Fall beschrieben und Sie haben eine allgemeine Antwort gegeben. Um es in weniger Worten auszudrücken: Das war für mich nicht hilfreich!

2 Stimmen

1, um die unerklärte, ungerechtfertigte Herabstufung zu kompensieren, da die Antwort richtig ist (mag den OP glücklich machen oder nicht, aber an sich ist daran nichts auszusetzen).

0 Stimmen

@Alex: Wie würden Sie sich fühlen, wenn Sie mich nach Lungenkrebs fragen und ich Ihnen einen Vortrag über Vitamine halte?

9voto

Soweit ich weiß, ist dieses Verhalten durchaus vernünftig. Steckdosen können, und wahrscheinlich wird Ihre Daten bei der Übertragung fragmentieren. Sie sollten auf solche Fälle vorbereitet sein, indem Sie geeignete Pufferungstechniken anwenden.

Andererseits, wenn Sie die Daten auf dem localhost übertragen und Sie tatsächlich nur 4 Bytes erhalten, bedeutet dies wahrscheinlich, dass Sie irgendwo anders in Ihrem Code einen Fehler haben.

EDIT: Eine Idee - versuchen Sie, einen Packet Sniffer zu starten und zu sehen, wann das übertragene Paket voll ist oder nicht; dies könnte Ihnen einen Einblick geben, wann der Fehler in Ihrem Client oder in Ihrem Server liegt.

0 Stimmen

In der Tat läuft mein Code auf localhost -- was die Sache noch seltsamer macht!

0 Stimmen

Danke für die Idee mit dem Paketschnüffler. Aber ich fürchte, es hilft mir nicht viel, da das Problem nur in seltenen Fällen auftritt und ich es erst sehe, nachdem es passiert ist ... der Paket-Sniffer ist höchstwahrscheinlich zu spät ...

0 Stimmen

Ob die Daten fragmentiert sind oder nicht, hängt von vielen Faktoren ab. TCP versucht, ein komplettes Segment / MTU zu puffern, bevor es sendet, um die Bandbreite effizient zu nutzen. Die gängigste MTU für Ethernet liegt bei etwa 1500 Byte. Wenn Sie weniger als das übertragen, ist es unwahrscheinlich, dass die Fragmentierung Ihr Problem ist. Der Fehler liegt dann wahrscheinlich woanders.

5voto

mhawke Punkte 79590

Die einfache Antwort auf Ihre Frage "Von Socket lesen: Ist garantiert, dass mindestens x Bytes gelesen werden?", lautet keine . Schauen Sie sich die Doku-Strings für diese Socket-Methoden an:

>>> import socket
>>> s = socket.socket()
>>> print s.recv.__doc__
recv(buffersize[, flags]) -> data

Receive up to buffersize bytes from the socket.  For the optional flags
argument, see the Unix manual.  When no data is available, block until
at least one byte is available or until the remote end is closed.  When
the remote end is closed and all data is read, return the empty string.
>>> 
>>> print s.settimeout.__doc__
settimeout(timeout)

Set a timeout on socket operations.  'timeout' can be a float,
giving in seconds, or None.  Setting a timeout of None disables
the timeout feature and is equivalent to setblocking(1).
Setting a timeout of zero is the same as setblocking(0).
>>> 
>>> print s.setblocking.__doc__
setblocking(flag)

Set the socket to blocking (flag is true) or non-blocking (false).
setblocking(True) is equivalent to settimeout(None);
setblocking(False) is equivalent to settimeout(0.0).

Daraus wird deutlich, dass recv() ist nicht verpflichtet, so viele Bytes zurückzugeben, wie Sie angefordert haben. Da Sie außerdem die Funktion settimeout(10.0) ist es möglich, dass einige, aber nicht alle Daten kurz vor Ablauf der Gültigkeitsdauer der Datenbank empfangen werden. recv() . In diesem Fall recv() gibt zurück, was es gelesen hat - und das wird weniger sein, als Sie verlangt haben (aber eine Konsistenz < 4 Bytes scheint unwahrscheinlich).

Sie erwähnen datagram in Ihrer Frage, was bedeutet, dass Sie (verbindungslose) UDP-Sockets (und nicht TCP) verwenden. Die Unterscheidung ist hier beschrieben . Der gepostete Code zeigt die Socket-Erstellung nicht, so dass wir hier nur raten können, aber dieses Detail kann wichtig sein. Es könnte helfen, wenn Sie ein vollständigeres Beispiel Ihres Codes posten könnten.

Wenn das Problem reproduzierbar ist, könnten Sie die Zeitüberschreitung deaktivieren (was Sie übrigens nicht zu tun scheinen) und sehen, ob das Problem dadurch behoben wird.

0 Stimmen

Die Zeitüberschreitung ist hier nicht das Problem - auch das habe ich überprüft. Im Falle einer Zeitüberschreitung würde eine Ausnahme ausgelöst werden -- und somit würde eine andere Reaktion in meinem Programm erfolgen. Ich habe mein Programm nun dahingehend geändert, dass auch am Anfang eine Schleife beteiligt ist. Aber ich bin immer noch neugierig, welche der Antworten am hilfreichsten war. Ihre und die Kommentare von nos scheinen mir bisher am besten.

0 Stimmen

@Juergen: Eine Timeout-Ausnahme wird nur ausgelöst, wenn no Daten empfangen wurden. Die Bedingung, die ich beschreibe, ist, dass einige Daten, weniger als die, die von recv() kurz vor der Zeitüberschreitung empfangen wird. recv() kehrt zurück, wenn die Zeitüberschreitung abgelaufen ist, und liefert weniger Daten als angefordert. Wie ich schon sagte, ist es sehr unwahrscheinlich, dass dies durchgängig < 4 Byte ist, aber ich nehme an, dass es möglich ist. Das Lesen in einer Schleife ist der richtige Weg, um Ihr Problem zu lösen.

3voto

Jonathan Adelson Punkte 3125

So funktioniert TCP nun einmal. Sie werden nicht alle Daten auf einmal erhalten. Es gibt einfach zu viele Zeitprobleme zwischen Sender und Empfänger, einschließlich des Betriebssystems des Senders, der Netzwerkkarte, der Router, der Switches, der Leitungen selbst, der Netzwerkkarte des Empfängers, des Betriebssystems usw. Es gibt Puffer in der Hardware und im Betriebssystem.

Sie können nicht davon ausgehen, dass das TCP-Netzwerk dasselbe ist wie eine OS-Pipe. Bei der Pipe handelt es sich um Software, so dass bei den meisten Nachrichten keine Kosten für die Übermittlung der gesamten Nachricht auf einmal anfallen. Bei einem Netzwerk müssen Sie davon ausgehen, dass es Zeitprobleme gibt, selbst in einem einfachen Netzwerk.

Aus diesem Grund kann recv() nicht alle Daten auf einmal liefern, da sie möglicherweise nicht verfügbar sind, selbst wenn alles richtig funktioniert. Normalerweise werden Sie recv() aufrufen und die Ausgabe abfangen. Das sollte Ihnen sagen, wie viele Bytes Sie erhalten haben. Wenn es weniger sind, als Sie erwarten, müssen Sie recv() so lange aufrufen (wie vorgeschlagen), bis Sie die richtige Anzahl von Bytes erhalten. Beachten Sie, dass recv() in den meisten Fällen bei einem Fehler -1 zurückgibt, überprüfen Sie dies und schauen Sie in Ihrer Dokumentation nach ERRNO-Werten. Vor allem EAGAIN scheint den Leuten Probleme zu bereiten. Wenn ich mich recht entsinne, bedeutet dies, dass im Moment keine Daten verfügbar sind und Sie es noch einmal versuchen sollten.

Außerdem klingt Ihr Beitrag so, als ob Sie sicher sind, dass der Absender die gewünschten Daten sendet, aber überprüfen Sie der Vollständigkeit halber Folgendes: http://beej.us/guide/bgnet/output/html/multipage/advanced.html#sendall

Sie sollten am Ende von recv() etwas Ähnliches tun, um partielle Empfänge zu behandeln. Wenn Sie eine feste Paketgröße haben, sollten Sie lesen, bis Sie die erwartete Datenmenge erhalten. Wenn Sie eine variable Paketgröße haben, sollten Sie lesen, bis Sie den Header haben, der Ihnen sagt, wie viele Daten Sie senden(), und dann so viel mehr Daten lesen.

1voto

buster Punkte 901

Aus der Linux-Manpage von recv http://linux.about.com/library/cmd/blcmdl2_recv.htm :

Die Empfangsaufrufe geben normalerweise alle Daten zurück, und zwar bis zur angeforderten Menge, anstatt auf den den Empfang der gesamten angeforderten Menge zu warten.

Wenn Ihr Absender also noch Bytes sendet, gibt der Anruf nur das an, was bisher gesendet wurde.

0 Stimmen

Aber es ist einfach unvernünftig, 1-3 Bytes zu übertragen, wenn der Absender vielleicht 10 Bytes in einem Stück übertragen hat. einzeln frischer Ruf. Warum sollte eine Bibliothek dies tun?

1 Stimmen

Es handelt sich um einen Systemaufruf, der jeden möglichen Fall berücksichtigen muss. Man macht es falsch, wenn man sich darauf verlässt, dass er alle Daten, die man sendet, in einem Aufruf erhält. Was soll recv() tun, wenn Sie 1000 Bytes senden und der Puffer nur 500 Bytes hat? Was ist zu tun, wenn etwas aufgrund von Fehlern, Fragmentierung usw. erneut übertragen wird? Es ist üblich, in einen Puffer einzulesen, bis Sie eine abschließende Zeichenkette senden ( \0 ist üblich), so dass Sie wissen, dass eine Nachricht empfangen wurde.

0 Stimmen

Ich sage nicht, dass Sie vielleicht nicht Recht haben. Was ich sage, ist, dass es für mich keine vernünftige Erklärung gibt. Ihre neuen Beispiele im Kommentar führen in die falsche Richtung, da es sich um die ersten 4 Bytes in einem neuen Übertragungsblock handelt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X