Conceptos Avanzados en socket

Entrada/Salida

Por defecto los sockets son bloqueantes. Esto quiere decir que cuando realizamos una llamada a alguna función relacionada con sockets que no puede ser completada inmediatamente, nuestro proceso pasa al estado de dormido, esperando que se satisfaga alguna condición que permita que se complete la llamada anterior:

Podemos dividir las llamadas de socket bloqueantes en cuatro categorías:

Con sockets no bloqueantes, si no se puede realizar la operación de lectura, la llamada retorna inmediatamente y devuelve el error EWOULDBLOCK

Con un socket TCP no bloqueante, si no hay espacio en el buffer de envío del socket, retorna inmediatamente con un error EWOULDBLOCK. Si hay espacio el valor de retorno será el número de bytes que el kernel haya podido copiar en el buffer (y que será menor del requerido porque en caso contrario no se habría producido el bloqueo).

Cuando trabajamos con sockets UDP no existe el concepto de buffer de envío, pero es posible que se produzca el bloqueo debido al control de flujo y los distintos buffers dentro del código de red del kernel.

Si el socket es no bloqueante y no hay nuevas conexiones disponibles, se devuelve el error EWOULDBLOCK.

En un socket no bloqueante si no se puede establecer conexión inmediatamente se envía el primer paquete del establecimiento de conexión TCP y se retorna devolviendo el error EINPROGRESS. Es posible que incluso con este tipo de sockets la conexión se pueda establecer inmediatamente y se retorne el valor 0 en lugar del error anterior (por ejemplo, cuando cliente y servidor residen en un mismo host).

Cómo hacer nobloqueante un socket: la función fcntl()

fcntl es un acrónimo de 'file control'. ESta función permite llevar a cabo distintas operaciones de control sobre descriptores. El protipo de la función es el siguiente:

#include <fcntl.h>

int fcntl(int fd, int cmd , /* int arg*/);

Cada descriptor tiene un conjunto de flags que se obtiene utilizando la orden (parámetro cmd) F_GETFL y que se obtienen utilizando la orden F_SETFL. Del conjunto de flags que puede manipular fcntl, los dos flags que afectan al comportamiento de los sockets son los siguientes:

O_NONBLOCK E/S no bloqueante.

O_ASYNC E/S guiada por señales (signal-drive I/O)

Si nos centramos en la programación de aplicaciones en redes la función fcntl nos permite hacer lo siguiente:

Nosotros nos vamos a centrar en el último uso que hemos comentado de fcntl, esto es como hacer un socket no bloqueante. El código apropiado sería:

int flags;

//Obtenemos el conjunto de descriptores activados en el descriptor fd

if( (flags = fcntl(fd, F_GETFL, 0)) < 0)

perror("F_GETFL error:");

//Activamos el flag O_NONBLOCK

flags |= O_NONBLOCK;

if(fcntl(fd, F_SETFL, flags) < 0)

perror("Error con F_SETFL");

 

En un primer momento podríamos pensar que una forma válida de activar un flag sería la siguiente:

/* Así no se debe activar un flag*/

if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)

perror("F_SETFL error");

De este modo es cierto que activamos el flag O_NONBLOCK pero también es cierto que perdemos el valor del resto de los flags. La única forma correcta de hacerlo es obtener el valor de los flags de estado y añadirle el nuevo flag mediante un OR.

Para desactivar el flag de no bloqueo hacemos lo siguiente:

/* Hacemos un AND con el complementario del flag: ponemos a 0 el bit correspondiente*/

flags &= ~O_NONBLOCK;

if (fcntl(fd, F_SETFL, flags) <0)

perror("Error de F_SETFL:");

 

Bibliografía

W.R. Stevens, B. Fenner, A.M. Ruddoff. Unix Netork Programming. Volume 1. The Socket Networking API 3ª Edition. Ed. Addison-Wesley. 2003.