Los sockets son un sistema de comunicación entre procesos de diferentes máquinas de una red. Más exactamente, un socket es un punto de comunicación por el cual un proceso puede emitir o recibir información.
Fueron popularizados por Berckley Software Distribution, de la universidad norteamericana de Berkley. Los sockets han de ser capaces de utilizar el protocolo de streams TCP (Transfer Contro Protocol) y el de datagramas UDP (User Datagram Protocol).
Utilizan una serie de primitivas para establecer el punto de comunicación, para conectarse a una máquina remota en un determinado puerto que esté disponible, para escuchar en él, para leer o escribir y publicar información en él, y finalmente para desconectarse.
Con todas primitivas se puede crear un sistema de diálogo muy completo.
Imagen 10: Funcionamiento de una conexión socket
Para más información véase [Rifflet, 1998].
B. Ejemplo de usoPara comprender el funcionamiento de los sockets no hay nada mejor que estudiar un ejemplo. El que a continuación se presenta establece un pequeño diálogo entre un programa servidor y sus clientes, que intercambiarán cadenas de información.
a.) Programa ClienteEl programa cliente se conecta a un servidor indicando el nombre de la máquina y el número puerto (tipo de servicio que solicita) en el que el servidor está instalado.
Una vez conectado, lee una cadena del servidor y la escribe en la pantalla:
import java.io.*; import java.net.*; class Cliente { static final String HOST = "localhost"; static final int PUERTO=5000; public Cliente( ) { try{ Socket skCliente = new Socket( HOST , Puerto ); InputStream aux = skCliente.getInputStream(); DataInputStream flujo = new DataInputStream( aux ); System.out.println( flujo.readUTF() ); skCliente.close(); } catch( Exception e ) { System.out.println( e.getMessage() ); } } public static void main( String[] arg ) { new Cliente(); } }
En primer lugar se crea el socket denominado skCliente, al que se le especifican el nombre de host (HOST) y el número de puerto (PORT) en este ejemplo constantes.
Luego se asocia el flujo de datos de dicho socket (obtenido mediante getInputStream)), que es asociado a un flujo (flujo) DataInputStream de lectura secuencial. De dicho flujo capturamos una cadena ( readUTF() ), y la imprimimos por pantalla (System.out).
El socket se cierra, una vez finalizadas las operaciones, mediante el método close().
Debe observarse que se realiza una gestión de excepción para capturar los posibles fallos tanto de los flujos de datos como del socket.
b.) Programa ServidorEl programa servidor se instala en un puerto determinado, a la espera de conexiones, a las que tratará mediante un segundo socket.
Cada vez que se presenta un cliente, le saluda con una frase "Hola cliente N".
Este servidor sólo atenderá hasta tres clientes, y después finalizará su ejecución, pero es habitual utilizar bucles infinitos ( while(true) ) en los servidores, para que atiendan llamadas continuamente.
Tras atender cuatro clientes, el servidor deja de ofrecer su servicio:
import java.io.* ; import java.net.* ; class Servidor { static final int PUERTO=5000; public Servidor( ) { try { ServerSocket skServidor = new ServerSocket( PUERTO ); System.out.println("Escucho el puerto " + PUERTO ); for ( int numCli = 0; numCli < 3; numCli++; ) { Socket skCliente = skServidor.accept(); // Crea objeto System.out.println("Sirvo al cliente " + numCli); OutputStream aux = skCliente.getOutputStream(); DataOutputStream flujo= new DataOutputStream( aux ); flujo.writeUTF( "Hola cliente " + numCli ); skCliente.close(); } System.out.println("Demasiados clientes por hoy"); } catch( Exception e ) { System.out.println( e.getMessage() ); } } public static void main( String[] arg ) { new Servidor(); } }
Utiliza un objeto de la clase ServerSocket (skServidor), que sirve para esperar las conexiones en un puerto determinado (PUERTO), y un objeto de la clase Socket (skCliente) que sirve para gestionar una conexión con cada cliente.
Mediante un bucle for y la variable numCli se restringe el número de clientes a tres, con lo que cada vez que en el puerto de este servidor aparezca un cliente, se atiende y se incrementa el contador.
Para atender a los clientes se utiliza la primitiva accept() de la clase ServerSocket, que es una rutina que crea un nuevo Socket (skCliente) para atender a un cliente que se ha conectado a ese servidor.
Se asocia al socket creado (skCliente) un flujo (flujo) de salida DataOutputStream de escritura secuencial, en el que se escribe el mensaje a enviar al cliente.
El tratamiento de las excepciones es muy reducido en nuestro ejemplo, tan solo se captura e imprime el mensaje que incluye la excepción mediante getMessage().
c.) EjecuciónAunque la ejecución de los sockets está diseñada para trabajar con ordenadores en red, en sistemas operativos multitarea (por ejemplo Windows y UNIX) se puede probar el correcto funcionamiento de un programa de sockets en una misma máquina.
Para ellos se ha de colocar el servidor en una ventana, obteniendo lo siguiente:
>java Servidor Escucho el puerto 5000En otra ventana se lanza varias veces el programa cliente, obteniendo:
>java Cliente Hola cliente 1 >java cliente Hola cliente 2 >java cliente Hola cliente 3 >java cliente connection refused: no further informationMientras tanto en la ventana del servidor se ha impreso:
Sirvo al cliente 1 Sirvo al cliente 2 Sirvo al cliente 3 Demasiados clientes por hoyCuando se lanza el cuarto de cliente, el servidor ya ha cortado la conexión, con lo que se lanza una excepción.
Obsérvese que tanto el cliente como el servidor pueden leer o escribir del socket. Los mecanismos de comunicación pueden ser refinados cambiando la implementación de los sockets, mediante la utilización de las clases abstractas que el paquete java.net provee.