2009-03-20 13 views
18

Aquí está mi código:¿Cuál es la forma correcta de leer desde un socket TCP en 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; 
} 

Es un poco de C y C++ como se puede ver. El BUFFER_SIZE es 256 - ¿debería simplemente aumentar el tamaño? Si es así, ¿qué? ¿Importa?

Sé que si "fin" no se recibe por alguna razón, este será un ciclo sin fin, lo cual es malo, por lo que si puede sugerir una mejor manera, hágalo también.

+1

Esto es simple C++ ... –

+11

Gracias por su contribución. Tenga en cuenta que mi código implementa el método read(), que se puede encontrar en el sys/socket.h biblioteca, que es "parte de la Biblioteca GNU C" y no la biblioteca C++. –

Respuesta

26

Sin conocer su aplicación completa, es difícil decir cuál es la mejor manera de abordar el problema, pero una técnica común es usar un encabezado que comience con un campo de longitud fija, que denota la longitud del resto de su mensaje.

Suponga que su encabezado consiste únicamente en un entero de 4 bytes que indica la longitud del resto de su mensaje. Entonces simplemente haz lo siguiente.

// 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; 
    } 
} 

Luego, más tarde en el código

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; 

Esto hace algunas suposiciones:

  • enteros son del mismo tamaño en el emisor y el receptor.
  • Endianess es el mismo en el emisor y el receptor.
  • Usted tiene el control del protocolo en ambos lados
  • Cuando envía un mensaje puede calcular la longitud por adelantado.

ya que es común a querer saber explícitamente el tamaño del número entero que va a enviar a través de la red las definen en un archivo de cabecera y utilizarlos de forma explícita como:

// 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; 

Esto cambiaría la arriba 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; 

Espero que esto ayude.

+0

El comentario de Ori Pessach es una buena respuesta complementaria a esta. – grieve

+0

tarde a la fiesta, pero como no conoce la Endianess del otro lado de la comunicación, la longitud probablemente debería estar en orden de bytes de red, por lo que en su ejemplo: 'ReadXBytes (socketFileDescriptor, sizeof (length), (void *)(&longitud)); longitud = :: ntohl (longitud); buffer = new char [longitud]; ReadXBytes (socketFileDescriptor, length, (void *) buffer); ' –

1

¿Dónde está asignando memoria para su buffer? La línea donde invoca bzero invoca un comportamiento indefinido ya que el búfer no apunta a ninguna región de memoria válida.

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

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

Varios punteros:

que necesita para manejar un valor de retorno de 0, que indica que el host remoto cerró la toma de corriente.

Para sockets no bloqueantes, también debe comprobar un valor de retorno de error (-1) y asegurarse de que errno no sea EINPROGRESS, que es lo que se espera.

Definitivamente necesita un mejor manejo de errores: potencialmente está filtrando el buffer señalado por 'buffer'. Lo cual, noté, no asigna en ningún lugar en este fragmento de código.

Alguien más hizo una buena observación acerca de cómo su búfer no es una cadena C terminada nula si su lectura() llena todo el búfer. Eso es realmente un problema, y ​​uno serio.

Su tamaño de búfer es un poco pequeño, pero debería funcionar siempre y cuando no intente leer más de 256 bytes, o lo que sea que le asigne.

Si le preocupa entrar en un bucle infinito cuando el host remoto le envía un mensaje incorrecto (un posible ataque de denegación de servicio), debe usar select() con un tiempo de espera en el socket para verificar la legibilidad, y solo lea si los datos están disponibles, y rescate si selecciona() tiempo de espera.

Algo como esto podría funcionar para usted:

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. 
} 

Dependiendo del volumen de datos que espera recibir, de la manera que escanear todo el mensaje en varias ocasiones por el "fin"; token es muy ineficiente. Esto se hace mejor con una máquina de estado (los estados son 'e' -> 'n' -> 'd' -> ';') para que solo observe cada carácter entrante una vez.

Y en serio, debería considerar buscar una biblioteca para hacer todo esto por usted. No es fácil hacerlo bien.

+0

No EINPROGRESS. EAGAIN o EWOULDBLOCK. – EJP

3

Si realmente crear el buffer según la sugerencia Dirks, entonces:

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

puede llenar completamente el buffer, posiblemente sobrescribir el carácter de terminación cero que depende de la hora de extraer a un stringstream. Es necesario:

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

Este es un artículo que siempre me refiero a cuando se trabaja con tomas ..

THE WORLD OF SELECT()

le mostrará cómo utilizar de forma fiable 'select()' y contiene algunos otros enlaces útiles en la parte inferior para obtener más información sobre los enchufes.

+1

Si bien esto puede responder teóricamente a la pregunta, [sería preferible] (http://meta.stackexchange.com/q/8259) incluir las partes esenciales de la respuesta aquí, y proporcione el enlace de referencia. –

3

1) Otros (especialmente dirkgently) han notado que el búfer necesita espacio de memoria asignado. Para valores más bien pequeñas de N (por ejemplo, N = < 4096), también se puede asignar en la pila:

#define BUFFER_SIZE 4096 
char buffer[BUFFER_SIZE] 

Esto le ahorra la preocupación de asegurar que delete[] la memoria intermedia se debe lanzar una excepción.

Pero recuerde que las pilas son de tamaño finito (por lo que son montones, pero las pilas son finiter), por lo que no desea poner demasiado allí.

2) En un código de retorno -1, no debe simplemente devolver inmediatamente (lanzar una excepción inmediatamente es aún más incompleto). Hay ciertas condiciones normales que debe manejar, si su código va a ser algo más que una tarea corta de tarea. Por ejemplo, EAGAIN puede devolverse en errno si no hay datos actualmente disponibles en un socket que no sea de bloqueo. Eche un vistazo a la página man para leer (2).

+0

Buen punto, no es bueno dejar las manijas de socket abiertas por ahí; considerará tirar después. –

+0

En realidad, no me referí al identificador de socket abierto porque no está abierto en el fragmento que publicó. Pero me alegra que lo hayas pensado :-) –

Cuestiones relacionadas