¿Cuál es la forma correcta de leer desde un socket TCP en C / C ++?

Aquí está mi código:

// Not all headers are relevant to the code snippet. #include  #include  #include  #include  #include  #include  #include  #include  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 puedes ver. El BUFFER_SIZE es 256 – ¿debería simplemente boost el tamaño? Si es así, ¿qué? ¿Importa?

Sé que si el “final” no se recibe por alguna razón, este será un ciclo interminable, lo que es malo, por lo que si puede sugerir una mejor manera, hágalo también.

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 duración del rest de su mensaje.

Suponga que su encabezado consiste únicamente en un entero de 4 bytes que denota la longitud del rest 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 adelante 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:

  • Las entradas tienen el mismo tamaño en el emisor y el receptor.
  • Endianess es lo mismo tanto para el emisor como para el receptor.
  • Usted tiene el control del protocolo en ambos lados
  • Cuando envía un mensaje puede calcular la longitud por adelantado.

Como es común querer saber explícitamente el tamaño del entero que está enviando a través de la red, defínalas en un archivo de cabecera y úsalas explícitamente, 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 lo anterior 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.

Varios indicadores:

Necesita manejar un valor de retorno de 0, que le indica que el host remoto cerró el socket.

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

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

Otra persona 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.

El tamaño del búfer es un poco pequeño, pero debería funcionar siempre que 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 malformado (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 leer si los datos están disponibles, y rescatar 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, la forma en que escanea el mensaje completo repetidamente para 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.

Si realmente crea el búfer según la sugerencia de dirks, entonces:

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

puede llenar completamente el búfer, posiblemente sobreescribiendo el carácter cero de terminación del que depende al extraer a un flujo de cadenas. Necesitas:

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

1) Otros (especialmente dirkgently) han notado que al buffer se le debe asignar algo de espacio de memoria. Para valores pequeños de N (por ejemplo, N < = 4096), también puede asignarlo a la pila:

 #define BUFFER_SIZE 4096 char buffer[BUFFER_SIZE] 

Esto le ahorra la preocupación de asegurarse de delete[] el búfer si se lanza una excepción.

Pero recuerde que los montones son de tamaño finito (por lo que son montones, pero los montones son finitos), por lo que no quiere poner demasiado allí.

2) En un código de retorno -1, no debe simplemente regresar 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. . Por ejemplo, EAGAIN puede devolverse en errno si no hay datos actualmente disponibles en un socket que no sea de locking. Eche un vistazo a la página man para leer (2).

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

 char *buffer = new char[ BUFFER_SIZE ]; // do processing // don't forget to release delete[] buffer; 

Este es un artículo al que siempre me refiero cuando trabajo con conectores.

EL MUNDO DE SELECT ()

Le mostrará cómo utilizar de manera confiable ‘select ()’ y contiene algunos otros enlaces útiles en la parte inferior para obtener más información sobre los sockets.