2008-09-08 10 views
21

Estoy un poco confundido acerca de la programación de socket en C.¿Cómo funcionan los enchufes en C?

Crea un socket, lo vincula a una interfaz y una dirección IP y lo hace escuchar. Encontré un par de recursos web sobre eso, y lo entendí bien. En particular, encontré un artículo Network programming under Unix systems para ser muy informativo.

Lo que me confunde es la sincronización de los datos que llegan al socket.

¿Cómo puede saber cuándo llegan los paquetes, y qué tan grande es el paquete, usted mismo tiene que hacer todo el trabajo pesado?

Mi suposición básica aquí es que los paquetes pueden ser de longitud variable, por lo que una vez que los datos binarios comienzan a aparecer en el socket, ¿cómo se comienzan a construir los paquetes a partir de eso?

Respuesta

17

La respuesta breve es que tiene que hacer todo el trabajo pesado usted mismo. Se le puede notificar que hay datos disponibles para leer, pero no sabrá cuántos bytes hay disponibles. En la mayoría de los protocolos IP que usan paquetes de longitud variable, habrá un encabezado con una longitud fija conocida antepuesta al paquete. Este encabezado contendrá la longitud del paquete. Lees el encabezado, obtienes la longitud del paquete y luego lees el paquete. Repite este patrón (lee el encabezado, luego lee el paquete) hasta que se complete la comunicación.

Al leer datos de un socket, solicita un cierto número de bytes. La llamada de lectura puede bloquearse hasta que se lea el número solicitado de bytes, pero puede devolver menos bytes de los solicitados. Cuando esto sucede, simplemente vuelve a intentar leer y solicita los bytes restantes.

Aquí es una función típica C para la lectura de un número fijo de bytes desde un socket:

/* buffer points to memory block that is bigger than the number of bytes to be read */ 
/* socket is open socket that is connected to a sender */ 
/* bytesToRead is the number of bytes expected from the sender */ 
/* bytesRead is a pointer to a integer variable that will hold the number of bytes */ 
/*   actually received from the sender. */ 
/* The function returns either the number of bytes read, */ 
/*        0 if the socket was closed by the sender, and */ 
/*       -1 if an error occurred while reading from the socket */ 
int readBytes(int socket, char *buffer, int bytesToRead, int *bytesRead) 
{ 
    *bytesRead = 0; 
    while(*bytesRead < bytesToRead) 
    { 
     int ret = read(socket, buffer + *bytesRead, bytesToRead - *bytesRead); 
     if(ret <= 0) 
     { 
      /* either connection was closed or an error occurred */ 
      return ret; 
     } 
     else 
     { 
      *bytesRead += ret; 
     } 
    } 
    return *bytesRead; 
} 
+0

¿No debería el retorno anticipado después de la conexión cerrada ser "return bytesRead;" en lugar de "return ret;"? Supongo que la función readBytes está destinada a devolver el número real de bytes leídos. Supongo que podría definirlo para devolver un entero no positivo si hay un error, pero también podría detectar que ocurrió un error al verificar si readBytes devuelve un número de bytes diferente del solicitado. –

+0

un valor negativo para marcar un estado de error es una práctica común en la programación C y se debe cumplir tanto como sea posible para evitar confusiones por parte del usuario de la API. – Guss

+0

Modifiqué mi función de ejemplo para devolver el número de bytes leídos incluso si se produjo un error durante la lectura. Un punto a tener en cuenta con los sockets es que una lectura que devuelve cero indica que la conexión se cerró, y puede no ser necesariamente un error. – dfjacobs

3

Cuando haces una lectura en el socket, le dices cuántos bytes máximos leer, pero si no tiene tantos, te da cuantos más tenga. Depende de usted diseñar el protocolo para que sepa si tiene un paquete parcial o no. Por ejemplo, en el pasado cuando enviaba datos binarios de longitud variable, ponía un int al principio que decía cuántos bytes esperar. Haría una lectura solicitando un número de bytes mayor que el paquete más grande posible en mi protocolo, y luego compararía la primera int con todos los bytes que había recibido, y la procesaría o intentaría leer más hasta que yo ' d obtenido el paquete completo, dependiendo.

1

Las tomas funcionan a un nivel más alto que los paquetes sin procesar, es como un archivo desde el que se puede leer/escribir. Además, cuando intente leer desde un socket, el sistema operativo bloqueará (retendrá) su proceso hasta que tenga datos para cumplir con la solicitud.

+1

Esto solo es válido si el socket no está configurado para no bloquear. –

13

Por lo tanto, la respuesta a su pregunta depende un poco justo de si se está utilizando UDP o TCP como su transporte.

Para UDP, la vida se vuelve mucho más simple, ya que puede llamar a recv/recvfrom/recvmsg con el tamaño de paquete que necesita (probablemente envíe paquetes de longitud fija desde la fuente) y asume que si los datos están disponibles, están allí en múltiplos de tamaños de paquetes de longitud. (Por ejemplo, usted llama a recv * con el tamaño de su paquete de envío y está configurado).

Para TCP, la vida se pone un poco más interesante. Para el propósito de esta explicación, asumiré que ya sabe cómo usar socket(), bind(), listen() y accept() - siendo esta última la forma de obtener el descriptor de archivo (FD) de su conexión recién creada.

Hay dos maneras de hacer las E/S para un socket - blocking, en el que llama a read (fd, buf, N) y la lectura se queda ahí y espera hasta que haya leído N bytes en buf - o sin bloqueo, en el que debe verificar (usando select() o poll()) si el FD es legible, y LUEGO haga su lectura().

Cuando se trata de conexiones basadas en TCP, el sistema operativo no presta atención a los tamaños de paquete, ya que se considera una secuencia continua de datos, no fragmentos separados del tamaño de un paquete.

Si su aplicación usa "paquetes" (estructuras de datos empaquetadas o no empaquetadas que está pasando), debe poder llamar a read() con el argumento de tamaño adecuado y leer toda una estructura de datos fuera del socket a la vez La única advertencia con la que tiene que lidiar es recordar ordenar por byte correctamente todos los datos que está enviando, en caso de que el sistema de origen y el de destino sean de diferente entidad de bytes. Esto se aplica tanto a UDP como a TCP.

Por lo que se refiere a * NIX socket programming, recomiendo W. Richard Stevens 'Unix Network Programming, Vol. 1 "(UNPv1) y" Advanced Programming in Unix Environment "(APUE). El primero es un tomo sobre programación basada en red, independientemente del transporte, y el último es un buen libro de programación versátil, ya que se aplica a la programación * basada en NIX. Además, busque "TCP/IP Illustrated", Volúmenes 1 y 2.