Quanto dovrebbe essere grande il mio buffer recv quando richiamo recv nella libreria socket


129

Ho alcune domande sulla libreria socket in C. Ecco un frammento di codice a cui farò riferimento nelle mie domande.

char recv_buffer[3000];
recv(socket, recv_buffer, 3000, 0);
  1. Come faccio a decidere quanto è grande fare recv_buffer? Sto usando 3000, ma è arbitrario.
  2. cosa succede se recv()riceve un pacchetto più grande del mio buffer?
  3. come posso sapere se ho ricevuto l'intero messaggio senza chiamare di nuovo recv e farlo attendere per sempre quando non c'è nulla da ricevere?
  4. c'è un modo in cui posso fare in modo che un buffer non abbia una quantità fissa di spazio, in modo da poter continuare ad aggiungerlo senza paura di rimanere senza spazio? forse usando strcatper concatenare l'ultima recv()risposta al buffer?

So che ci sono molte domande in una, ma apprezzerei molto qualsiasi risposta.

Risposte:


230

Le risposte a queste domande variano a seconda che si stia utilizzando un flusso socket ( SOCK_STREAM) o un datagramma socket ( SOCK_DGRAM) - all'interno di TCP / IP, il primo corrisponde a TCP e il secondo a UDP.

Come fai a sapere quanto è grande per far passare il buffer recv()?

  • SOCK_STREAM: Non importa davvero troppo. Se il tuo protocollo è transazionale / interattivo, scegli una dimensione che può contenere il messaggio / comando individuale più grande che ti aspetteresti ragionevolmente (3000 probabilmente va bene). Se il protocollo trasferisce dati in blocco, i buffer di dimensioni maggiori possono essere più efficienti: una buona regola empirica è simile a quella del kernel che riceve le dimensioni del buffer del socket (spesso circa 256 KB).

  • SOCK_DGRAM: Usa un buffer abbastanza grande da contenere il pacchetto più grande che il tuo protocollo a livello di applicazione abbia mai inviato. Se stai usando UDP, in generale il tuo protocollo a livello di applicazione non dovrebbe inviare pacchetti più grandi di circa 1400 byte, perché dovranno certamente essere frammentati e riassemblati.

Cosa succede se recvottiene un pacchetto più grande del buffer?

  • SOCK_STREAM: La domanda non ha davvero senso, perché i socket di flusso non hanno un concetto di pacchetti: sono solo un flusso continuo di byte. Se ci sono più byte disponibili da leggere di quelli che il buffer ha spazio, verranno messi in coda dal sistema operativo e disponibili per la prossima chiamata a recv.

  • SOCK_DGRAM: I byte in eccesso vengono eliminati.

Come posso sapere se ho ricevuto l'intero messaggio?

  • SOCK_STREAM: È necessario creare un modo per determinare la fine del messaggio nel protocollo a livello di applicazione. Comunemente si tratta di un prefisso di lunghezza (che avvia ciascun messaggio con la lunghezza del messaggio) o di un delimitatore di fine messaggio (che potrebbe essere solo una nuova riga in un protocollo basato su testo, ad esempio). Una terza opzione, meno utilizzata, è quella di imporre una dimensione fissa per ciascun messaggio. Sono anche possibili combinazioni di queste opzioni, ad esempio un'intestazione a dimensione fissa che include un valore di lunghezza.

  • SOCK_DGRAM: Una singola recvchiamata restituisce sempre un singolo datagramma.

C'è un modo in cui posso fare in modo che un buffer non abbia una quantità fissa di spazio, in modo da poter continuare ad aggiungerlo senza paura di rimanere senza spazio?

No. Tuttavia, puoi provare a ridimensionare il buffer usando realloc()(se è stato originariamente allocato con malloc()o calloc(), cioè).


1
Ho un "/ r / n / r / n" alla fine di un messaggio nel protocollo che sto usando. E ho un ciclo do while, dentro chiamo recv inserisco il messaggio all'inizio di recv_buffer. e la mia dichiarazione while assomiglia a questo mentre ((! (strstr (recv_buffer, "\ r \ n \ r \ n")); La mia domanda è, è possibile che un recv ottenga "\ r \ n" e nel il prossimo recv ottiene "\ r \ n", in modo che la mia condizione while non si
realizzi

3
Sì. È possibile risolvere il problema eseguendo il ciclo continuo se non si dispone di un messaggio completo e inserendo i byte dal successivo recvnel buffer dopo il messaggio parziale. Non dovresti usarlo strstr()sul buffer non elaborato compilato da recv(): non esiste alcuna garanzia che contenga un nul-terminator, quindi potrebbe causare un strstr()arresto anomalo.
caf

3
Nel caso di UDP, non c'è nulla di sbagliato nell'invio di pacchetti UDP superiori a 1400 byte. La frammentazione è perfettamente legale e una parte fondamentale del protocollo IP (anche in IPv6, tuttavia il mittente iniziale deve sempre eseguire la frammentazione). Per UDP risparmi sempre se usi un buffer di 64 KB, poiché nessun pacchetto IP (v4 o v6) può avere dimensioni superiori a 64 KB (nemmeno se frammentato) e questo include anche le intestazioni IIRC, quindi i dati saranno sempre sotto i 64 KB di sicuro.
Mecki,

1
@caf hai bisogno di svuotare il buffer su ogni chiamata a recv ()? Ho visto eseguire il looping del codice, raccogliere i dati e eseguirne nuovamente il loop per raccogliere più dati. Ma se il buffer si riempie mai, non è necessario svuotarlo per evitare una violazione della memoria dovuta alla scrittura, passare la quantità di memoria allocata per il buffer?
Alex_Nabu

1
@Alex_Nabu: non è necessario svuotarlo fintanto che rimane dello spazio e non si dice recv()di scrivere più byte di quanto non rimanga spazio.
Caf

16

Per protocolli di streaming come TCP, puoi praticamente impostare il tuo buffer su qualsiasi dimensione. Detto questo, sono raccomandati valori comuni che sono potenze di 2 come 4096 o 8192.

Se ci sono più dati di quanti siano i tuoi buffer, questi verranno semplicemente salvati nel kernel per la tua prossima chiamata recv .

Sì, puoi continuare a far crescere il tuo buffer. Puoi fare un recv nel mezzo del buffer iniziando dall'offset idx, faresti:

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

6
La potenza di due può essere più efficiente in più modi ed è fortemente consigliata.
Yann Ramin,

3
elaborando su @theatrus, una notevole efficienza è che l'operatore modulo può essere sostituito da bit a bit e con una maschera (ad esempio x% 1024 == x & 1023), e la divisione intera può essere sostituita da un'operazione di spostamento a destra (ad esempio x / 1024 = = x / 2 ^ 10 == x >> 10)
vicatcu

15

Se hai un SOCK_STREAMsocket, recvottiene semplicemente "fino ai primi 3000 byte" dallo stream. Non esiste una guida chiara su quanto sia grande il buffer: l'unica volta che sai quanto è grande uno stream è quando è tutto finito ;-).

Se hai un SOCK_DGRAMsocket e il datagramma è più grande del buffer,recv riempie il buffer con la prima parte del datagramma, restituisce -1 e imposta errno su EMSGSIZE. Sfortunatamente, se il protocollo è UDP, questo significa che il resto del datagramma è perso - parte del motivo per cui UDP è chiamato inaffidabile protocollo (so che ci sono protocolli datagramma affidabili ma non sono molto popolari - Non potrei nominarne uno nella famiglia TCP / IP, pur conoscendo abbastanza bene quest'ultimo ;-).

Per espandere dinamicamente un buffer, allocarlo inizialmente con malloce utilizzare reallocsecondo necessità. Ma questo non ti aiuterà recvda una fonte UDP, ahimè.


7
Poiché UDP restituisce sempre al massimo un pacchetto UDP (anche se più sono nel buffer socket) e nessun pacchetto UDP può essere superiore a 64 KB (un pacchetto IP può essere al massimo 64 KB, anche se frammentato), l'utilizzo di un buffer da 64 KB è assolutamente sicuro e garantisce che non si perde mai alcun dato durante un recv su un socket UDP.
Mecki,

7

Per il SOCK_STREAMsocket, la dimensione del buffer non ha molta importanza, perché stai semplicemente estraendo alcuni dei byte in attesa e puoi recuperarne altri nella prossima chiamata. Scegli qualsiasi dimensione del buffer che puoi permetterti.

Per il SOCK_DGRAMsocket, otterrai la parte adatta del messaggio di attesa e il resto verrà scartato. Puoi ottenere la dimensione del datagramma in attesa con il seguente ioctl:

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

In alternativa è possibile utilizzare MSG_PEEKe MSG_TRUNCflag della recv()chiamata per ottenere la dimensione del datagramma in attesa.

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

È necessario MSG_PEEKsbirciare (non ricevere) il messaggio in attesa: recv restituisce la dimensione reale, non troncata; e non è necessario MSG_TRUNCoverflow del buffer corrente.

Quindi puoi solo malloc(size)il vero buffer e recv()datagramma.


MSG_PEEK | MSG_TRUNC non ha senso.
Marchese di Lorne,

3
Volete che MSG_PEEK sbircia (non riceva) il messaggio in attesa, per ottenere la sua dimensione (recv restituisce la dimensione reale, non troncata) e avete bisogno di MSG_TRUNC per non sovraccaricare il vostro buffer corrente. Una volta ottenuta la dimensione, allocare il buffer corretto e ricevere (non sbirciare, non troncare) il messaggio in attesa.
smokku,

@Alex Martelli dice che 64 KB è la dimensione massima di un pacchetto UDP quindi se malloc()per un buffer di 64 KB MSG_TRUNCnon è necessario?
mLstudent33

1
Il protocollo IP supporta la frammentazione, quindi il datagramma potrebbe essere più grande di un singolo pacchetto: sarà frammentato e trasmesso in più pacchetti. Inoltre SOCK_DGRAMnon è solo UDP.
smokku

1

Non esiste una risposta assoluta alla tua domanda, poiché la tecnologia è sempre destinata a essere specifica per l'implementazione. Presumo che tu stia comunicando in UDP perché la dimensione del buffer in entrata non causa problemi alla comunicazione TCP.

Secondo RFC 768 , la dimensione del pacchetto (compresa l'intestazione) per UDP può variare da 8 a 65 515 byte. Quindi la dimensione a prova di errore per il buffer in entrata è 65 507 byte (~ 64 KB)

Tuttavia, non tutti i pacchetti di grandi dimensioni possono essere instradati correttamente dai dispositivi di rete, fare riferimento alla discussione esistente per ulteriori informazioni:

Qual è la dimensione ottimale di un pacchetto UDP per la massima velocità effettiva?
Qual è la dimensione del pacchetto UDP sicuro più grande su Internet


-4

16kb è giusto; se stai usando Gigabit Ethernet, ogni pacchetto potrebbe avere una dimensione di 9kb.


3
I socket TCP sono flussi, ciò significa che un recv può restituire dati accumulati da più pacchetti, quindi la dimensione dei pacchetti è totalmente irrilevante per TCP. Nel caso di UDP, ogni chiamata recv restituisce al massimo un singolo pacchetto UDP, qui la dimensione del pacchetto è rilevante ma la dimensione del pacchetto corretta è di circa 64 KB, poiché un pacchetto UDP può (e spesso verrà) frammentato se necessario. Tuttavia, nessun pacchetto IP può essere superiore a 64 KB, nemmeno con la frammentazione, quindi il recv su un socket UDP può al massimo restituire 64 KB (e ciò che non viene restituito viene scartato per il pacchetto corrente!)
Mecki,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.