2009-03-20 19 views
18

Ecco il mio codice:Qual è il modo corretto di leggere da un socket TCP in C/C++?

// Not all headers are relevant to the code snippet. 
#include <stdio.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <netdb.h> 
#include <cstdlib> 
#include <cstring> 
#include <unistd.h> 

char *buffer; 
stringstream readStream; 
bool readData = true; 

while (readData) 
{ 
    cout << "Receiving chunk... "; 

    // Read a bit at a time, eventually "end" string will be received. 
    bzero(buffer, BUFFER_SIZE); 
    int readResult = read(socketFileDescriptor, buffer, BUFFER_SIZE); 
    if (readResult < 0) 
    { 
     THROW_VIMRID_EX("Could not read from socket."); 
    } 

    // Concatenate the received data to the existing data. 
    readStream << buffer; 

    // Continue reading while end is not found. 
    readData = readStream.str().find("end;") == string::npos; 

    cout << "Done (length: " << readStream.str().length() << ")" << endl; 
} 

E 'un po' di C e C++ come si può dire. BUFFER_SIZE è 256 - dovrei semplicemente aumentare la dimensione? Se sì, cosa? Importa?

So che se "fine" non viene ricevuto per qualsiasi motivo, questo sarà un ciclo infinito, il che è negativo, quindi se si può suggerire un modo migliore, si prega di farlo anche.

+1

Questo è semplice C++ ... –

+11

Grazie per il vostro contributo. Si noti che il mio codice implementa il metodo read(), che può essere trovato nel sys/socket.h library, che è "parte della libreria GNU C" e non la libreria C++. –

risposta

26

Senza conoscere la tua applicazione completa è difficile dire quale sia il modo migliore per affrontare il problema, ma una tecnica comune è quella di utilizzare un'intestazione che inizia con un campo a lunghezza fissa, che indica la lunghezza del resto del tuo Messaggio.

Si supponga che l'intestazione sia costituita solo da un intero di 4 byte che denota la lunghezza del resto del messaggio. Quindi fai semplicemente quanto segue.

// This assumes buffer is at least x bytes long, 
// and that the socket is blocking. 
void ReadXBytes(int socket, unsigned int x, void* buffer) 
{ 
    int bytesRead = 0; 
    int result; 
    while (bytesRead < x) 
    { 
     result = read(socket, buffer + bytesRead, x - bytesRead); 
     if (result < 1) 
     { 
      // Throw your error. 
     } 

     bytesRead += result; 
    } 
} 

Poi più tardi nel codice

unsigned int length = 0; 
char* buffer = 0; 
// we assume that sizeof(length) will return 4 here. 
ReadXBytes(socketFileDescriptor, sizeof(length), (void*)(&length)); 
buffer = new char[length]; 
ReadXBytes(socketFileDescriptor, length, (void*)buffer); 

// Then process the data as needed. 

delete [] buffer; 

Questo rende alcuni presupposti:

  • interi hanno la stessa dimensione del mittente e del destinatario.
  • L'endianità è la stessa sia per il mittente che per il destinatario.
  • Si ha il controllo del protocollo su entrambi i lati
  • Quando si invia un messaggio, è possibile calcolare la lunghezza in avanti.

Dal momento che è comune a voler sapere esplicitamente la dimensione del numero intero si sta inviando attraverso la rete li definiscono in un file di intestazione e li usano in modo esplicito come ad esempio:

// These typedefs will vary across different platforms 
// such as linux, win32, OS/X etc, but the idea 
// is that a Int8 is always 8 bits, and a UInt32 is always 
// 32 bits regardless of the platform you are on. 
// These vary from compiler to compiler, so you have to 
// look them up in the compiler documentation. 
typedef char Int8; 
typedef short int Int16; 
typedef int Int32; 

typedef unsigned char UInt8; 
typedef unsigned short int UInt16; 
typedef unsigned int UInt32; 

questo cambierebbe la sopra a:

UInt32 length = 0; 
char* buffer = 0; 

ReadXBytes(socketFileDescriptor, sizeof(length), (void*)(&length)); 
buffer = new char[length]; 
ReadXBytes(socketFileDescriptor, length, (void*)buffer); 

// process 

delete [] buffer; 

Spero che questo aiuti.

+0

Il commento di Ori Pessach è una buona risposta in omaggio a questo. – grieve

+0

tardi alla festa, ma come non si conoscono le Endianess dell'altro lato della comunicazione, la lunghezza dovrebbe probabilmente essere nell'ordine dei byte di rete, quindi nell'esempio: 'ReadXBytes (socketFileDescriptor, sizeof (length), (void *)(&lunghezza)); lunghezza = :: ntohl (lunghezza); buffer = new char [lunghezza]; ReadXBytes (socketFileDescriptor, length, (void *) buffer); ' –

1

Dove stai allocando memoria per il tuo buffer? La riga in cui viene richiamato bzero richiama il comportamento non definito poiché il buffer non punta a nessuna regione di memoria valida.

char *buffer = new char[ BUFFER_SIZE ]; 
// do processing 

// don't forget to release 
delete[] buffer; 
7

diversi puntatori:

è necessario gestire un valore di ritorno di 0, che indica che l'host remoto ha chiuso la presa.

Per gli zoccoli non bloccanti, è inoltre necessario controllare un valore di ritorno errore (-1) e assicurarsi che errno non sia EINPROGRESS, che è previsto.

Hai sicuramente bisogno di una migliore gestione degli errori - stai potenzialmente perdendo il buffer indicato da "buffer". Che, ho notato, non si assegna ovunque in questo snippet di codice.

Qualcun altro ha fatto un buon punto su come il buffer non è una stringa C terminata da null se read() riempie l'intero buffer. Questo è davvero un problema, e serio.

La dimensione del buffer è un po 'piccola, ma dovrebbe funzionare fino a quando non si tenta di leggere più di 256 byte, o qualsiasi cosa si assegni per esso.

Se si è preoccupati di entrare in un ciclo infinito quando l'host remoto invia un messaggio malformato (un potenziale attacco di tipo Denial of Service), utilizzare select() con un timeout sul socket per verificare la leggibilità, e leggi solo se i dati sono disponibili e salva se select() scade.

Qualcosa di simile potrebbe funzionare per voi:

fd_set read_set; 
struct timeval timeout; 

timeout.tv_sec = 60; // Time out after a minute 
timeout.tv_usec = 0; 

FD_ZERO(&read_set); 
FD_SET(socketFileDescriptor, &read_set); 

int r=select(socketFileDescriptor+1, &read_set, NULL, NULL, &timeout); 

if(r<0) { 
    // Handle the error 
} 

if(r==0) { 
    // Timeout - handle that. You could try waiting again, close the socket... 
} 

if(r>0) { 
    // The socket is ready for reading - call read() on it. 
} 

A seconda del volume di dati che ci si aspetta di ricevere, il modo in cui eseguire la scansione l'intero messaggio più volte per la "fine"; il token è molto inefficiente. Questo è meglio con una macchina a stati (gli stati sono 'e' -> 'n' -> 'd' -> ';') in modo che tu guardi solo ogni carattere in arrivo una volta.

E seriamente, dovresti considerare di cercare una biblioteca per fare tutto questo per te. Non è facile farlo bene.

+0

Non EINPROGRESS. EAGAIN o EWOULDBLOCK. – EJP

3

Se effettivamente crea il buffer secondo dirks suggerimento, quindi:

int readResult = read(socketFileDescriptor, buffer, BUFFER_SIZE); 

può riempire completamente il buffer, possibilmente sovrascrivendo il carattere terminale nullo che dipendono durante l'estrazione di uno stringstream. Hai bisogno di:

int readResult = read(socketFileDescriptor, buffer, BUFFER_SIZE - 1); 
1

Si tratta di un articolo che ho sempre riferisco a quando si lavora con i socket ..

THE WORLD OF SELECT()

vi mostrerà come utilizzare in modo affidabile 'select()' e contiene alcuni altri link utili in basso per ulteriori informazioni sui socket.

+1

Sebbene questo possa teoricamente rispondere alla domanda, [sarebbe preferibile] (http://meta.stackexchange.com/q/8259) includere le parti essenziali della risposta qui e fornire il link per riferimento. –

3

1) Altri (in particolare dirkgently) hanno notato che al buffer deve essere assegnato dello spazio di memoria. Per i valori smallish di N (per esempio, N < = 4096), è anche possibile allocare in pila:

#define BUFFER_SIZE 4096 
char buffer[BUFFER_SIZE] 

Ciò consente di risparmiare la preoccupazione di garantire che si delete[] il buffer dovrebbe essere gettato un'eccezione.

Ma ricorda che le pile sono di dimensioni finite (quindi sono cumuli, ma le pile sono più fini), quindi non si vuole mettere troppo lì.

2) Su un codice di ritorno -1, non si dovrebbe semplicemente tornare immediatamente (lanciare un'eccezione immediatamente è ancora più approssimativa.) Ci sono alcune condizioni normali che è necessario gestire, se il codice deve essere qualcosa di più di un breve compito a casa. Ad esempio, EAGAIN può essere restituito in errno se nessun dato è attualmente disponibile su un socket non bloccante. Dai un'occhiata alla pagina man per leggere (2).

+0

Buon punto, non è buono lasciare le maniglie aperte in giro; prenderà in considerazione il lancio in seguito. –

+0

In realtà, non ho indirizzato l'handle del socket aperto, perché non è stato aperto nello snippet che hai postato. Ma sono felice che tu ci abbia pensato :-) –

Problemi correlati