Wie groß sollte mein RecV-Puffer sein, wenn Recv in der Socket-Bibliothek aufgerufen wird?

129

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

char recv_buffer[3000];
recv(socket, recv_buffer, 3000, 0);
  1. Wie entscheide ich, wie groß recv_buffer sein soll? Ich benutze 3000, aber es ist willkürlich.
  2. Was passiert, wenn recv()ein Paket empfangen wird, das größer als mein Puffer ist?
  3. Wie kann ich feststellen, ob ich die gesamte Nachricht erhalten habe, ohne erneut recv aufzurufen, und sie für immer warten lassen, wenn nichts zu empfangen ist?
  4. Gibt es eine Möglichkeit, einen Puffer ohne festen Speicherplatz zu erstellen, damit ich ihn weiter erweitern kann, ohne befürchten zu müssen, dass ihm der Speicherplatz ausgeht? Vielleicht verwenden, strcatum die letzte recv()Antwort auf den Puffer zu verketten ?

Ich weiß, dass es viele Fragen in einer gibt, aber ich würde mich über jede Antwort sehr freuen.

Adhanlon
quelle

Antworten:

230

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

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

  • SOCK_STREAM: Es ist nicht wirklich wichtig. 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 Befehl enthalten kann, die 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 entspricht in etwa der Größe des Kernel-Empfangspuffers 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 gesendet hat. Wenn Sie UDP verwenden, sollte Ihr Protokoll auf Anwendungsebene im Allgemeinen keine Pakete senden, die größer als etwa 1400 Byte sind, da diese sicherlich fragmentiert und neu zusammengesetzt werden müssen.

Was passiert, wenn recvein Paket größer als der Puffer wird?

  • SOCK_STREAM: Die Frage ist nicht wirklich sinnvoll, da Stream-Sockets kein Paketkonzept haben - sie sind nur ein kontinuierlicher Stream 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 an zur Verfügung recv.

  • SOCK_DGRAM: Die überschüssigen Bytes werden verworfen.

Wie kann ich feststellen, ob ich die gesamte Nachricht erhalten habe?

  • SOCK_STREAM: Sie müssen eine Möglichkeit zum Bestimmen des Nachrichtenende in Ihr Protokoll auf Anwendungsebene integrieren. Im Allgemeinen ist dies entweder ein Längenpräfix (wobei jede Nachricht mit der Länge der Nachricht beginnt) oder ein Trennzeichen für das Ende der Nachricht (das beispielsweise nur eine neue Zeile in einem textbasierten Protokoll sein kann). Eine dritte, weniger genutzte Option besteht darin, für jede Nachricht eine feste Größe festzulegen. Kombinationen dieser Optionen sind ebenfalls möglich - beispielsweise ein Header mit fester Größe, der einen Längenwert enthält.

  • SOCK_DGRAM: Ein einzelner recvAufruf gibt immer ein einzelnes Datagramm zurück.

Gibt es eine Möglichkeit, einen Puffer ohne festen Speicherplatz zu erstellen, damit ich ihn weiter erweitern kann, ohne befürchten zu müssen, dass ihm der Speicherplatz ausgeht?

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

caf
quelle
1
Ich habe ein "/ r / n / r / n" am Ende einer Nachricht in dem Protokoll, das ich verwende. 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, während ((! (strstr (recv_buffer, "\ r \ n \ r \ n")); Meine Frage ist, ist es möglich, dass eine recv "\ r \ n" und in der next recv get "\ r \ n", damit mein while-Zustand nie wahr wird?
Adhanlon
3
Ja, so ist es. Sie können dieses Problem lösen, indem Sie eine Schleife durchführen, wenn Sie keine vollständige Nachricht haben, und die Bytes vom nächsten recvin den Puffer nach der Teilnachricht einfügen. Sie sollten strstr()den mit gefüllten Rohpuffer nicht verwenden recv()- es gibt keine Garantie dafür, dass er einen Nullterminator enthält, sodass er strstr()zum Absturz führen kann.
Café
3
Im Fall von UDP ist das Senden von UDP-Paketen über 1400 Byte nichts Falsches. Die Fragmentierung ist vollkommen legal und ein wesentlicher Bestandteil des IP-Protokolls (auch in IPv6 muss der ursprüngliche Absender immer eine Fragmentierung durchführen). Für UDP werden Sie immer gespeichert, wenn Sie einen Puffer von 64 KB verwenden, da kein IP-Paket (v4 oder v6) eine Größe von über 64 KB haben kann (auch nicht, wenn es fragmentiert ist), und dies schließt sogar die Header IIRC ein, sodass Daten immer vorhanden sind sicher unter 64 KB.
Mecki
1
@caf Müssen Sie den Puffer bei jedem Aufruf von recv () leeren? Ich habe eine Code-Schleife gesehen und sammle die Daten und schleife sie erneut, wodurch mehr Daten gesammelt werden sollten. Aber wenn der Puffer jemals voll wird, müssen Sie ihn nicht leeren, um eine Speicherverletzung durch Schreiben zu vermeiden. Übergeben Sie die für den Puffer zugewiesene Speichermenge?
Alex_Nabu
1
@Alex_Nabu: Sie müssen es nicht leeren, solange noch Speicherplatz vorhanden ist, und Sie müssen nicht recv()mehr Bytes schreiben, als noch Speicherplatz vorhanden ist.
Café
16

Bei Streaming-Protokollen wie TCP können Sie Ihren Puffer auf eine beliebige Größe einstellen. Es werden jedoch gemeinsame Werte mit Potenzen von 2 wie 4096 oder 8192 empfohlen.

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

Ja, Sie können Ihren Puffer weiter vergrößern. Sie können eine Aufnahme in die Mitte des Puffers machen, beginnend mit dem Versatz idx. Sie würden Folgendes tun:

recv(socket, recv_buffer + idx, recv_buffer_size - idx, 0);
R Samuel Klatchko
quelle
6
Die Zweierpotenz kann auf verschiedene Weise effizienter sein und wird dringend empfohlen.
Yann Ramin
3
Bei der Ausarbeitung von @theatrus ist eine bemerkenswerte Effizienz, dass der Modulo-Operator durch bitweise und durch eine Maske (z. B. x% 1024 == x & 1023) ersetzt werden kann und die Ganzzahldivision durch eine Rechtsverschiebungsoperation (z. B. x / 1024 =) ersetzt werden kann = x / 2 ^ 10 == x >> 10)
vicatcu
15

Wenn Sie einen SOCK_STREAMSocket haben, werden recvnur "bis zu den ersten 3000 Bytes" vom Stream abgerufen. Es gibt keine klare Anleitung, wie groß der Puffer sein soll: Das einzige Mal, wenn Sie wissen, wie groß ein Stream ist, ist, wenn alles erledigt ist ;-).

Wenn Sie einen SOCK_DGRAMSocket haben und das Datagramm größer als der Puffer ist, recvfüllt es 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 unzuverlässiges Protokoll bezeichnet wird (ich weiß, dass es zuverlässige Datagrammprotokolle gibt, aber sie sind nicht sehr beliebt - ich konnte nicht Nennen Sie einen in der TCP / IP-Familie, obwohl Sie letzteres ziemlich gut kennen ;-).

Um einen Puffer dynamisch zu vergrößern, weisen Sie ihn zunächst zu mallocund verwenden Sie ihn reallocnach Bedarf. Aber das hilft Ihnen recvleider nicht bei einer UDP-Quelle.

Alex Martelli
quelle
7
Da UDP immer höchstens ein UDP-Paket zurückgibt (auch wenn sich mehrere im Socket-Puffer befinden) und kein UDP-Paket über 64 KB liegen kann (ein IP-Paket darf höchstens 64 KB groß sein, auch wenn es fragmentiert ist), wird ein 64-KB-Puffer verwendet Absolut sicher und garantiert, dass Sie während einer Aufzeichnung auf einem UDP-Socket niemals Daten verlieren.
Mecki
6

Für SOCK_STREAMSockets 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 aus, die Sie sich leisten können.

Für SOCK_DGRAMSocket erhalten Sie den passenden Teil der Wartemeldung und der Rest wird verworfen. Sie können die Größe des wartenden Datagramms mit dem folgenden ioctl erhalten:

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

Alternativ können Sie MSG_PEEKund MSG_TRUNCFlags des recv()Aufrufs verwenden, um die Größe des wartenden Datagramms zu erhalten.

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

Sie müssen MSG_PEEKdie wartende Nachricht einsehen (nicht empfangen) - recv gibt die tatsächliche, nicht abgeschnittene Größe zurück. und Sie müssen MSG_TRUNCIhren aktuellen Puffer nicht überlaufen lassen.

Dann können Sie nur malloc(size)den realen Puffer und das recv()Datagramm.

Smokku
quelle
MSG_PEEK | MSG_TRUNC macht keinen Sinn.
Marquis von Lorne
2
Sie möchten, dass MSG_PEEK die wartende Nachricht einsieht (nicht empfängt), um ihre Größe zu erhalten (recv gibt die tatsächliche, nicht abgeschnittene Größe zurück), und Sie benötigen MSG_TRUNC, um Ihren aktuellen Puffer nicht zu überlaufen. Sobald Sie die Größe erhalten haben, weisen Sie den richtigen Puffer zu und erhalten die wartende Nachricht (nicht gucken, nicht abschneiden).
Smokku
@Alex Martelli sagt, 64 KB sind die maximale Größe eines UDP-Pakets. Wenn wir also malloc()einen Puffer von 64 KB benötigen, ist dies nicht erforderlich MSG_TRUNC?
mLstudent33
Das IP-Protokoll unterstützt die Fragmentierung, sodass das Datagramm möglicherweise größer als ein einzelnes Paket ist. Es wird fragmentiert und in mehreren Paketen übertragen. Auch SOCK_DGRAMist nicht nur UDP.
Smokku
1

Es gibt keine absolute Antwort auf Ihre Frage, da die Technologie 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.

Gemäß 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 Byte (~ 64 KB).

Es können jedoch nicht alle großen Pakete ordnungsgemäß von Netzwerkgeräten weitergeleitet werden. Weitere Informationen finden Sie in der vorhandenen 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?

YeenFei
quelle
-4

16kb ist ungefähr richtig; Wenn Sie Gigabit-Ethernet verwenden, kann jedes Paket eine Größe von 9 KB haben.

Andrew McGregor
quelle
3
TCP-Sockets sind Streams. Dies bedeutet, dass ein Recv möglicherweise Daten zurückgibt, die aus mehreren Paketen stammen, sodass die Paketgröße für TCP völlig irrelevant ist. Im Falle von UDP gibt jeder Recv-Aufruf höchstens ein einzelnes UDP-Paket zurück. Hier ist die Paketgröße relevant, aber die korrekte Paketgröße beträgt ungefähr 64 KB, da ein UDP-Paket bei Bedarf fragmentiert werden kann (und häufig wird). Es kann jedoch kein IP-Paket über 64 KB liegen, auch nicht bei Fragmentierung. Daher kann recv auf einem UDP-Socket höchstens 64 KB zurückgeben (und was nicht zurückgegeben wird, wird für das aktuelle Paket verworfen!)
Mecki