Quelle doit être la taille de mon tampon de recv lors de l'appel de recv dans la bibliothèque de sockets

129

J'ai quelques questions sur la bibliothèque de sockets en C. Voici un extrait de code auquel je ferai référence dans mes questions.

char recv_buffer[3000];
recv(socket, recv_buffer, 3000, 0);
  1. Comment décider de la taille de recv_buffer? J'utilise 3000, mais c'est arbitraire.
  2. que se passe-t-il si recv()reçoit un paquet plus gros que mon tampon?
  3. comment puis-je savoir si j'ai reçu le message entier sans appeler à nouveau recv et le faire attendre indéfiniment alors qu'il n'y a rien à recevoir?
  4. y a-t-il un moyen de faire en sorte qu'un tampon n'ait pas une quantité fixe d'espace, de sorte que je puisse continuer à y ajouter sans craindre de manquer d'espace? peut-être en utilisant strcatpour concaténer la dernière recv()réponse au tampon?

Je sais qu'il y a beaucoup de questions en une, mais j'apprécierais beaucoup les réponses.

Adhanlon
la source

Réponses:

230

Les réponses à ces questions varient selon que vous utilisez un socket de flux ( SOCK_STREAM) ou un socket de datagramme ( SOCK_DGRAM) - dans TCP / IP, le premier correspond à TCP et le second à UDP.

Comment savez-vous quelle taille faire passer le tampon recv()?

  • SOCK_STREAM: Cela n'a pas vraiment trop d'importance. Si votre protocole est transactionnel / interactif, choisissez simplement une taille pouvant contenir le plus grand message / commande individuel auquel vous vous attendez raisonnablement (3000 est probablement très bien). Si votre protocole transfère des données en vrac, des tampons plus grands peuvent être plus efficaces - une bonne règle de base est à peu près la même que la taille du tampon de réception du noyau du socket (souvent quelque chose autour de 256 Ko).

  • SOCK_DGRAM: Utilisez une mémoire tampon suffisamment grande pour contenir le plus gros paquet que votre protocole de niveau application envoie jamais. Si vous utilisez UDP, alors en général, votre protocole au niveau de l'application ne devrait pas envoyer de paquets de plus de 1400 octets, car ils devront certainement être fragmentés et réassemblés.

Que se passe-t-il si recvobtient un paquet plus grand que le tampon?

  • SOCK_STREAM: La question n'a pas vraiment de sens, car les sockets de flux n'ont pas de concept de paquets - ils ne sont qu'un flux continu d'octets. S'il y a plus d'octets disponibles à lire que votre mémoire tampon n'a de place, ils seront mis en file d'attente par le système d'exploitation et disponibles pour votre prochain appel recv.

  • SOCK_DGRAM: Les octets en excès sont ignorés.

Comment puis-je savoir si j'ai reçu l'intégralité du message?

  • SOCK_STREAM: Vous devez créer un moyen de déterminer la fin du message dans votre protocole au niveau de l'application. Il s'agit généralement d'un préfixe de longueur (commençant chaque message par la longueur du message) ou d'un délimiteur de fin de message (qui peut être simplement une nouvelle ligne dans un protocole textuel, par exemple). Une troisième option, moins utilisée, consiste à imposer une taille fixe pour chaque message. Des combinaisons de ces options sont également possibles - par exemple, un en-tête de taille fixe qui comprend une valeur de longueur.

  • SOCK_DGRAM: Un seul recvappel renvoie toujours un seul datagramme.

Existe-t-il un moyen de faire en sorte qu'un tampon n'ait pas une quantité fixe d'espace, de sorte que je puisse continuer à y ajouter sans craindre de manquer d'espace?

Non. Cependant, vous pouvez essayer de redimensionner le tampon en utilisant realloc()(s'il a été initialement alloué avec malloc()ou calloc(), c'est-à-dire).

caf
la source
1
J'ai un "/ r / n / r / n" à la fin d'un message dans le protocole que j'utilise. Et j'ai une boucle do while, à l'intérieur j'appelle recv, je place le message au début de recv_buffer. et mon instruction while ressemble à ceci while ((! (strstr (recv_buffer, "\ r \ n \ r \ n")); Ma question est, est-il possible pour un recv d'obtenir "\ r \ n" et dans le prochain recv obtenir "\ r \ n", de sorte que ma condition while ne se réalise jamais?
adhanlon
3
Oui, ça l'est. Vous pouvez résoudre ce problème en faisant une boucle si vous n'avez pas de message complet et en remplissant les octets du suivant recvdans la mémoire tampon après le message partiel. Vous ne devriez pas utiliser strstr()sur le tampon brut rempli par recv()- il n'y a aucune garantie qu'il contienne un nul-terminator, donc il pourrait provoquer un strstr()crash.
caf
3
Dans le cas d'UDP, il n'y a rien de mal à envoyer des paquets UDP au-dessus de 1400 octets. La fragmentation est parfaitement légale et constitue une partie fondamentale du protocole IP (même dans IPv6, mais là toujours l'expéditeur initial doit effectuer la fragmentation). Pour UDP, vous êtes toujours sauvegardé si vous utilisez un tampon de 64 Ko, car aucun paquet IP (v4 ou v6) ne peut dépasser 64 Ko en taille (même s'il est fragmenté) et cela inclut même les en-têtes IIRC, donc les données seront toujours en dessous de 64 Ko pour sûr.
Mecki
1
@caf avez-vous besoin de vider le tampon à chaque appel à recv ()? J'ai vu une boucle de code et collecté les données et une nouvelle boucle, ce qui devrait collecter plus de données. Mais si jamais le tampon est plein, n'avez-vous pas besoin de le vider pour éviter une violation de mémoire due à l'écriture, passez la quantité de mémoire allouée pour le tampon?
Alex_Nabu
1
@Alex_Nabu: Vous n'avez pas besoin de le vider tant qu'il reste de l'espace, et vous ne dites pas recv()d'écrire plus d'octets qu'il ne reste d'espace.
caf
16

Pour les protocoles de streaming tels que TCP, vous pouvez à peu près définir votre tampon sur n'importe quelle taille. Cela dit, des valeurs communes qui sont des puissances de 2 telles que 4096 ou 8192 sont recommandées.

S'il y a plus de données que votre tampon, elles seront simplement enregistrées dans le noyau pour votre prochain appel recv.

Oui, vous pouvez continuer à agrandir votre tampon. Vous pouvez faire un recv au milieu du tampon en commençant à offset idx, vous feriez:

recv(socket, recv_buffer + idx, recv_buffer_size - idx, 0);
R Samuel Klatchko
la source
6
La puissance de deux peut être plus efficace de plusieurs manières et est fortement suggérée.
Yann Ramin
3
en élaborant sur @theatrus, une efficacité notable est que l'opérateur modulo peut être remplacé par bitwise et avec un masque (par exemple x% 1024 == x & 1023), et la division entière peut être remplacée par une opération de décalage à droite (par exemple x / 1024 = = x / 2 ^ 10 == x >> 10)
vicatcu
15

Si vous avez une SOCK_STREAMsocket, recvrécupère juste "jusqu'aux 3000 premiers octets" du flux. Il n'y a pas d'indications claires sur la taille du tampon: la seule fois où vous savez la taille d'un flux, c'est quand tout est fait ;-).

Si vous avez une SOCK_DGRAMsocket et que le datagramme est plus grand que le tampon, recvremplit le tampon avec la première partie du datagramme, renvoie -1 et définit errno sur EMSGSIZE. Malheureusement, si le protocole est UDP, cela signifie que le reste du datagramme est perdu - une partie de la raison pour laquelle UDP est appelé un protocole non fiable (je sais qu'il existe des protocoles de datagramme fiables mais ils ne sont pas très populaires - je ne pourrais pas nommez-en un dans la famille TCP / IP, même si vous connaissez assez bien ce dernier ;-).

Pour agrandir dynamiquement un tampon, allouez-le initialement avec mallocet utilisez-le reallocsi nécessaire. Mais cela ne vous aidera pas à recvpartir d'une source UDP, hélas.

Alex Martelli
la source
7
Comme UDP renvoie toujours au plus un paquet UDP (même si plusieurs sont dans le tampon de socket) et qu'aucun paquet UDP ne peut dépasser 64 Ko (un paquet IP peut être au plus de 64 Ko, même fragmenté), l'utilisation d'un tampon de 64 Ko est absolument sûr et garantit que vous ne perdez jamais de données lors d'une recv sur un socket UDP.
Mecki
7

Pour le SOCK_STREAMsocket, la taille de la mémoire tampon n'a pas vraiment d'importance, car vous tirez simplement certains des octets en attente et vous pouvez en récupérer plus lors d'un prochain appel. Choisissez simplement la taille de tampon que vous pouvez vous permettre.

Pour SOCK_DGRAMsocket, vous obtiendrez la partie appropriée du message d'attente et le reste sera ignoré. Vous pouvez obtenir la taille du datagramme en attente avec l'ioctl suivant:

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

Vous pouvez également utiliser les indicateurs MSG_PEEKet MSG_TRUNCde l' recv()appel pour obtenir la taille du datagramme en attente.

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

Vous devez MSG_PEEKjeter un coup d'œil (et non recevoir) le message en attente - recv renvoie la taille réelle, non tronquée; et vous ne devez MSG_TRUNCpas déborder de votre tampon actuel.

Ensuite, vous pouvez juste malloc(size)le vrai tampon et recv()datagramme.

smokku
la source
MSG_PEEK | MSG_TRUNC n'a aucun sens.
Marquis of Lorne
3
Vous voulez que MSG_PEEK regarde (ne reçoit pas) le message en attente, pour obtenir sa taille (recv renvoie la taille réelle, non tronquée) et vous avez besoin de MSG_TRUNC pour ne pas déborder de votre tampon actuel. Une fois que vous obtenez la taille, vous allouez le tampon correct et recevez (pas de coup d'oeil, pas de tronque) le message en attente.
smokku
@Alex Martelli dit que 64 Ko est la taille maximale d'un paquet UDP, donc si nous malloc()pour un tampon de 64 Ko, MSG_TRUNCest-ce inutile?
mLstudent33
1
Le protocole IP prend en charge la fragmentation, de sorte que le datagramme peut être plus gros qu'un seul paquet - il sera fragmenté et transmis en plusieurs paquets. Il SOCK_DGRAMn'y a pas que UDP.
smokku
1

Il n'y a pas de réponse absolue à votre question, car la technologie est toujours liée à la mise en œuvre. Je suppose que vous communiquez en UDP parce que la taille de la mémoire tampon entrante ne pose pas de problème à la communication TCP.

Selon RFC 768 , la taille de paquet (en-tête inclus) pour UDP peut aller de 8 à 65 515 octets. Ainsi, la taille à l'épreuve des pannes pour le tampon entrant est de 65 507 octets (~ 64 Ko)

Cependant, tous les gros paquets ne peuvent pas être correctement acheminés par les périphériques réseau, reportez-vous à la discussion existante pour plus d'informations:

Quelle est la taille optimale d'un paquet UDP pour un débit maximal?
Quelle est la plus grande taille de paquet UDP sûre sur Internet

YeenFei
la source
-4

16 ko est à peu près correct; si vous utilisez Gigabit Ethernet, chaque paquet peut avoir une taille de 9 Ko.

Andrew McGregor
la source
3
Les sockets TCP sont des flux, ce qui signifie qu'un recv peut renvoyer des données accumulées à partir de plusieurs paquets, de sorte que la taille du paquet est totalement sans importance pour TCP. Dans le cas d'UDP, chaque appel recv renvoie au plus un seul paquet UDP, ici la taille du paquet est pertinente mais la taille correcte du paquet est d'environ 64 Ko, car un paquet UDP peut (et sera souvent) fragmenté si nécessaire. Cependant, aucun paquet IP ne peut dépasser 64 Ko, même pas en cas de fragmentation, donc recv sur un socket UDP peut au maximum renvoyer 64 Ko (et ce qui n'est pas retourné est rejeté pour le paquet actuel!)
Mecki