Wie groß sollte mein Recv-Puffer sein, wenn ich recv in der Socket-Bibliothek aufrufe?

Lesezeit: 8 Minuten

Benutzeravatar von Adhanlon
adhanlon

Ich habe ein paar Fragen zur Socket-Bibliothek in C. Hier ist ein Codeausschnitt, auf den ich mich in meinen Fragen beziehen werde.

char recv_buffer[3000];
recv(socket, recv_buffer, 3000, 0);
  1. Wie entscheide ich, wie groß recv_buffer werden soll? Ich verwende 3000, aber es ist willkürlich.
  2. was passiert wenn recv() erhält ein Paket, das größer als mein Puffer ist?
  3. Wie kann ich wissen, ob ich die gesamte Nachricht erhalten habe, ohne recv erneut aufzurufen, und sie ewig warten lassen, wenn nichts zu empfangen ist?
  4. Gibt es eine Möglichkeit, einen Puffer ohne festen Speicherplatz einzurichten, sodass ich ihn weiter erweitern kann, ohne befürchten zu müssen, dass der Speicherplatz knapp wird? vielleicht mit strcat um die neuesten zu verketten recv() Antwort auf den Puffer?

Ich weiß, es sind viele Fragen in einer, aber ich würde mich sehr über Antworten freuen.

Benutzeravatar von caf
Café

Die Antworten auf diese Fragen variieren je nachdem, ob Sie einen Stream-Socket verwenden (SOCK_STREAM) oder ein Datagramm-Socket (SOCK_DGRAM) – Innerhalb von TCP/IP entspricht ersteres TCP und letzteres UDP.

Woher wissen Sie, wie groß der übergebene Puffer sein soll? recv()?

  • SOCK_STREAM: Es spielt nicht wirklich eine Rolle. Wenn es sich bei Ihrem Protokoll um ein transaktionales / interaktives Protokoll handelt, wählen Sie einfach eine Größe aus, die die größte einzelne Nachricht / den größten einzelnen Befehl enthalten kann, den Sie vernünftigerweise erwarten würden (3000 ist wahrscheinlich in Ordnung). Wenn Ihr Protokoll Massendaten überträgt, können größere Puffer effizienter sein – eine gute Faustregel ist ungefähr die gleiche wie die Kernel-Empfangspuffergröße des Sockets (häufig etwa 256 KB).

  • SOCK_DGRAM: Verwenden Sie einen Puffer, der groß genug ist, um das größte Paket aufzunehmen, das Ihr Protokoll auf Anwendungsebene jemals sendet. Wenn Sie UDP verwenden, sollte Ihr Protokoll auf Anwendungsebene im Allgemeinen keine Pakete senden, die größer als etwa 1400 Bytes sind, da sie sicherlich fragmentiert und neu zusammengesetzt werden müssen.

Was passiert wenn recv bekommt ein Paket größer als der Puffer?

  • SOCK_STREAM: Die Frage ist nicht wirklich sinnvoll, da Stream-Sockets kein Konzept von Paketen haben – sie sind nur ein kontinuierlicher Strom von Bytes. Wenn mehr Bytes zum Lesen verfügbar sind, als Ihr Puffer Platz bietet, werden sie vom Betriebssystem in die Warteschlange gestellt und stehen für Ihren nächsten Aufruf zur Verfügung recv.

  • SOCK_DGRAM: Die überzähligen Bytes werden verworfen.

Woher weiß ich, ob ich die gesamte Nachricht erhalten habe?

  • SOCK_STREAM: Sie müssen eine Möglichkeit zur Bestimmung des Nachrichtenendes in Ihr Protokoll auf Anwendungsebene einbauen. Üblicherweise ist dies entweder ein Längenpräfix (das jede Nachricht mit der Länge der Nachricht beginnt) oder ein Nachrichtenende-Trennzeichen (das beispielsweise nur ein Zeilenumbruch in einem textbasierten Protokoll sein kann). Eine dritte, weniger genutzte Option besteht darin, für jede Nachricht eine feste Größe vorzuschreiben. Kombinationen dieser Optionen sind ebenfalls möglich – beispielsweise ein Header mit fester Größe, der einen Längenwert enthält.

  • SOCK_DGRAM: Eine Single recv call gibt immer ein einzelnes Datagramm zurück.

Gibt es eine Möglichkeit, einen Puffer ohne festen Speicherplatz einzurichten, sodass ich ihn weiter erweitern kann, ohne befürchten zu müssen, dass der Speicherplatz knapp wird?

Nein. Sie können jedoch versuchen, die Größe des Puffers mit zu ändern realloc() (wenn es ursprünglich mit zugewiesen wurde malloc() oder calloc()das ist).

  • Ich habe ein “/r/n/r/n” am Ende einer Nachricht in dem von mir verwendeten Protokoll. Und ich habe eine Do-While-Schleife, in der ich recv aufrufe. Ich platziere die Nachricht am Anfang von recv_buffer. und meine while-Anweisung sieht so aus while((!(strstr(recv_buffer, “\r\n\r\n”)); Meine Frage ist, ist es möglich, dass ein recv “\r\n” bekommt und in der next recv bekommt “\r\n”, damit meine While-Bedingung niemals wahr wird?

    – adhanlon

    20. Mai 2010 um 16:05 Uhr


  • Ja, so ist es. Sie können dieses Problem lösen, indem Sie eine Schleife ausführen, wenn Sie keine vollständige Nachricht haben, und die Bytes von der nächsten füllen recv in den Puffer nach der Teilnachricht. Sie sollten nicht verwenden strstr() auf dem rohen Puffer, der von gefüllt wird recv() – Es gibt keine Garantie dafür, dass es einen Null-Terminator enthält, also könnte es dazu führen strstr() zerstören.

    – Café

    20. Mai 2010 um 21:46 Uhr

  • Im Fall von UDP spricht nichts dagegen, UDP-Pakete über 1400 Byte zu senden. Fragmentierung ist völlig legal und ein grundlegender Bestandteil des IP-Protokolls (selbst in IPv6, aber dort muss immer der ursprüngliche Sender die Fragmentierung durchführen). Bei UDP ist man immer sicher, wenn man einen Puffer von 64 KB verwendet, da kein IP-Paket (v4 oder v6) größer als 64 KB sein kann (auch nicht fragmentiert) und dies sogar die Header IIRC enthält, also Daten immer sein werden unter 64 KB sicher.

    – Mecki

    18. Dezember 2012 um 17:59 Uhr


  • @caf müssen Sie den Puffer bei jedem Aufruf von recv() leeren? Ich habe eine Codeschleife gesehen und die Daten gesammelt und erneut geschleift, wodurch mehr Daten gesammelt werden sollten. Aber wenn der Puffer jemals voll wird, müssen Sie ihn dann nicht leeren, um eine Speicherverletzung zu vermeiden, weil beim Schreiben die dem Puffer zugewiesene Speichermenge weitergegeben wird?

    – Alex_Nabu

    1. April 2015 um 7:30 Uhr

  • @Alex_Nabu: Sie müssen es nicht leeren, solange noch etwas Platz darin ist, und Sie sagen es nicht recv() um mehr Bytes zu schreiben, als Platz übrig ist.

    – Café

    1. April 2015 um 8:58 Uhr

Benutzeravatar von R Samuel Klatchko
R. Samuel Klatschko

Für Streaming-Protokolle wie TCP können Sie Ihren Puffer auf jede beliebige Größe einstellen. Es werden jedoch gängige Werte empfohlen, die Potenzen von 2 sind, z. B. 4096 oder 8192.

Wenn mehr Daten vorhanden sind als Ihr Puffer, werden sie einfach im Kernel für Ihren nächsten Aufruf gespeichert recv.

Ja, Sie können Ihren Puffer weiter vergrößern. Sie können einen Recv in die Mitte des Puffers beginnend bei offset durchführen idxDu würdest:

recv(socket, recv_buffer + idx, recv_buffer_size - idx, 0);

  • Zweierpotenzen können in mehrfacher Hinsicht effizienter sein und werden dringend empfohlen.

    – Jan Ramin

    19. Mai 2010 um 0:47 Uhr

  • Bei der Ausarbeitung von @theatrus besteht eine bemerkenswerte Effizienz darin, dass der Modulo-Operator durch bitweise und mit einer Maske ersetzt werden kann (z. B. x % 1024 == x & 1023) und die ganzzahlige Division durch eine Rechtsverschiebungsoperation ersetzt werden kann (z. B. x / 1024 = = x / 2^10 == x >> 10)

    – vicatcu

    19. Mai 2010 um 0:55 Uhr

Wenn Sie eine haben SOCK_STREAM Steckdose, recv bekommt nur “bis zu den ersten 3000 Bytes” aus dem Stream. Es gibt keine klare Anleitung, wie groß der Puffer sein soll: Wie groß ein Stream ist, weiß man erst, wenn alles fertig ist ;-).

Wenn Sie eine haben SOCK_DGRAM Socket, und das Datagramm ist größer als der Puffer, recv füllt den Puffer mit dem ersten Teil des Datagramms, gibt -1 zurück und setzt errno auf EMSGSIZE. Wenn das Protokoll UDP ist, bedeutet dies leider, dass der Rest des Datagramms verloren geht – ein Teil dessen, warum UDP als ein bezeichnet wird unzuverlässig Protokoll (Ich weiß, dass es zuverlässige Datagramm-Protokolle gibt, aber sie sind nicht sehr beliebt — ich könnte keins aus der TCP/IP-Familie nennen, obwohl ich letzteres ziemlich gut kenne;-).

Um einen Puffer dynamisch zu vergrößern, ordnen Sie ihn zunächst mit zu malloc und verwenden realloc wie benötigt. Aber das wird dir nicht weiterhelfen recv aus einer UDP-Quelle, leider.

  • Da UDP immer höchstens ein UDP-Paket zurückliefert (auch wenn sich mehrere im Socket-Puffer befinden) und kein UDP-Paket größer als 64 KB sein darf (ein IP-Paket darf höchstens 64 KB groß sein, auch fragmentiert), ist die Verwendung eines 64-KB-Puffers sinnvoll absolut sicher und garantiert, dass bei einem recv auf einem UDP-Socket niemals Daten verloren gehen.

    – Mecki

    18. Dezember 2012 um 18:04 Uhr

Benutzeravatar von smokku
Rauchku

Zum SOCK_STREAM Socket spielt die Puffergröße keine Rolle, da Sie nur einige der wartenden Bytes abrufen und bei einem nächsten Aufruf mehr abrufen können. Wählen Sie einfach die Puffergröße, die Sie sich leisten können.

Zum SOCK_DGRAM Socket erhalten Sie den passenden Teil der wartenden Nachricht und der Rest wird verworfen. Sie können die Größe des wartenden Datagramms mit dem folgenden ioctl abrufen:

#include <sys/ioctl.h>
int size;
ioctl(sockfd, FIONREAD, &size);

Alternativ können Sie verwenden MSG_PEEK und MSG_TRUNC Flaggen der recv() aufrufen, um die wartende Datagrammgröße zu erhalten.

ssize_t size = recv(sockfd, buf, len, MSG_PEEK | MSG_TRUNC);

Du brauchst MSG_PEEK um die wartende Nachricht zu spähen (nicht zu empfangen) – recv gibt die tatsächliche, nicht abgeschnittene Größe zurück; und du brauchst MSG_TRUNC damit Ihr aktueller Puffer nicht überläuft.

Dann kannst du einfach malloc(size) der echte Puffer und recv() Datagramm.

Benutzeravatar von YeenFei
YeenFei

Ihre Frage lässt sich nicht pauschal beantworten, da Technik immer implementierungsspezifisch sein muss. Ich gehe davon aus, dass Sie in UDP kommunizieren, da die Größe des eingehenden Puffers kein Problem für die TCP-Kommunikation darstellt.

Entsprechend RFC-768, kann die Paketgröße (einschließlich Header) für UDP zwischen 8 und 65.515 Byte liegen. Die ausfallsichere Größe für den eingehenden Puffer beträgt also 65.507 Bytes (~64 KB).

Allerdings können nicht alle großen Pakete von Netzwerkgeräten richtig geroutet werden. Weitere Informationen finden Sie in der bestehenden Diskussion:

Was ist die optimale Größe eines UDP-Pakets für maximalen Durchsatz?
Was ist die größte sichere UDP-Paketgröße im Internet?

Benutzeravatar von Andrew McGregor
Andreas McGregor

16kb ist ungefähr richtig; Wenn Sie Gigabit-Ethernet verwenden, kann jedes Paket 9 KB groß sein.

1424410cookie-checkWie groß sollte mein Recv-Puffer sein, wenn ich recv in der Socket-Bibliothek aufrufe?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy