lunes, 20 de junio de 2011

Sockets en C (I)


Como parte de mi trabajo de tesis de maestría, debo establecer un mecanismo de comunicación entre varias máquinas (en una red local) que forman un cluster de balanceo de carga. En el nodo director (el encargado de repartir la carga) debo monitorear constantemente el estado de carga de los servidores reales.

En el nodo director debe estar ejecutándose siempre un proceso que recopile cierta información de los servidores reales, mientras que en estos, debe estar ejecutándose un proceso que capture y envíe la información al nodo director.

¿Cómo comunicar estos dos procesos a través de una LAN? La respuesta es simple: Sockets. Un socket es una interfaz de entrada-salida que permite comunicar dos procesos que pueden estar ejecutándose en el mismo sistema o en sistemas diferentes.

En términos prácticos, un socket no es más que un archivo, sobre el cual se pueden utilizar las funciones read() and write() del lenguaje C.

Los sockets pueden clasificarse en 2, de acuerdo al tipo de conexión utilizada:
  • Sockets stream: orientados a conexión, comunicación confiable, utilizan el protocolo TCP.
  • Sockets datagram: no orientados a conexión, comunicación no confiable, utilizan el protocolo UDP.
Nota: Existe un tercer tipo en esta clasificación, los sockets raw, que dan acceso a protocolos de bajo nivel, y no serán tomados en cuenta en lo sucesivo.


El modelo cliente-servidor

Bajo el problema planteado al principio (el cluster de balanceo de carga), se puede apreciar claramente el modelo cliente-servidor, característico de los sockets: el servidor es el proceso que reside en el nodo director del cluster y que está constantemente recibiendo y procesando información (está escuchando, a espera de recibir la data) y el cliente es el proceso que reside en los servidores reales y que cada cierto tiempo contacta al servidor para enviarle sus datos.

Normalmente en un modelo cliente-servidor, el servidor generalmente envía data a los servidores reales, a petición de estos. En este caso, por la definición del problema, esto no se realizará.


¿Qué se necesita?

Para poder establecer la conexión entre cliente y servidor, se necesitan 3 elementos:
  • Protocolo de comunicación: ambos procesos deben hablar el mismo idioma; el protocolo como ya se dijo puede ser TCP o UDP, dependiendo de si se requiere confiabilidad o no.
  • Dirección IP del servidor: evidentemente, dentro de la red, para que un cliente pueda alcanzar al servidor debe conocer la dirección IP de este.
  • Puerto: identifica al proceso en la otra máquina.

Modelo cliente-servidor orientado a conexión

Para los sockets tipo stream, el modelo cliente-servidor es el siguiente:

En el servidor, lo primero que se hace es abrir el socket, mediante la función socket(int dominio, int tipo, int protocolo). Luego se informa al sistema operativo que se ha abierto el socket, mediante la función bind(int descriptor, (struct sockaddr *) direccion, socklen_t sizeof(direccion)). Posteriormente se informa al sistema operativo que el proceso está listo para recibir solicitudes, mediante la función listen(int descriptor, int max_clientes). Finalmente, la función accept(int descriptor, (struct sockaddr *) direccion, socklen_t sizeof(direccion)) acepta las solicitudes de conexión entrantes, es decir, reserva los recursos necesarios para manejar la conexión.

En el cliente, se comienza también abriendo el socket, mediante la función socket(int dominio, int tipo, int protocolo). Luego se solicita la conexión con el servidor, mediante la función connect(int descriptor, (struct sockaddr *) direccion, socklen_t sizeof(direccion)).

En sistemas Unix/Linux, una vez establecida la conexión, cliente y servidor pueden intercambiar información, mediante las funciones write() y read(), las mismas funciones de escritura/lectura utilizadas normalmente en C. En Windows, se utilizan las funciones equivalentes send() y recv().

Tanto cliente como servidor pueden utilizar la función close(int descriptor) (la misma utilizada para cerrar archivos) para terminar la comunicación y cerrar el socket.


Modelo cliente-servidor no orientado a conexión

Para la creación de sockets tipo datagram, el modelo cliente-servidor es:

El modelo es similar al anterior, con la salvedad de que no se establece una conexión. En el servidor, una vez que el socket se ha asociado con un puerto con la función bind(), queda activado para recibir/responder mensajes, mediante las funciones recvfrom() y sendto(), respectivamente. De igual forma, en el cliente no es necesario solicitar una conexión, sino que una vez abierto el socket, se puede enviar/recibir data.

A diferencia de write() y read(), en las funciones recvfrom() y sendto() se debe especificar el destinatario, pues no hay ninguna conexión previamente establecida.


Más información

Voy a dejar hasta acá esta entrada, para en la siguiente entrar de lleno con un par de ejemplos y explicar algunos detalles propios de la programación, pues teóricamente ya no hay mucho que decir.

En la red se consigue información casi ilimitada sobre el tema. Dejo algunos links:
  • Linux HowTos: C/C++ Sockets Tutorial (inglés). link
  • Programación de sockets en C de Unix/Linux (español). link
  • Sockets UDP en C para Linux (español). link

No hay comentarios: