Sockets en Java

La potencialidad de internet se basa en la facilidad de conexión entre dos lugares geográficos que pueden ser muy distantes. Gracias a eso tenemos Google, servidores de email, redes P2P, videoconferéncias y todo lo demás. En Java, para aprovechar todo ese potencial, tenemos dos clases que nos ayudan para que toda la transferencia de información sea transparente para el programador: las clases Socket y Serversocket.

La primera de las clases, Socket, implementa una “puerta de enlace” con el otro dispositivo, enmascarando así toda la red y las capas inferiores (implementa internamente TCP/IP). El cliente solicita la conexión con una clase de este tipo. Por otra parte, la clase Serversocket reserva un puerto del ordenador y lo deja escuchando a la espera de conexiones entrantes. Cuando encuentra una, crea una clase Socket (igual que la utilizada por el cliente para conectarse) para que sirva también de “puerta de enlace”, esta vez de lado del servidor.

La ventaja de estas clases es que nos permiten enviar streams de un socket al otro. Un stream es un flujo de información: una secuencia de bits que se pueden interpretar de una u otra manera. Y esto es una gran ventaja, ya que en Java existen multitud de clases (todas ellas extendiendo InputStream o OutputStream) para personalizar nuestra comunicación:

  • FileInputStream: para leer de ficheros.
  • ObjectInputStream: para enviar objetos enterss serializados (es decir, convertidos a cadenas de bits).
  • CipherInputStream: cifra la cadena de datos que atraviesa el Stream.
  • DigestInputStream: crea un código hash del Stream que le está atravesando.
  • BufferedInputStream: añade un buffer al stream.

Y hay muchas más que se pueden encontrar fácilmente en el JavaDoc (concretamente, aqui). Además se tiene la ventaja de que se pueden encadenar: si queremos implementar un buffer y descifrar el contenido de un archivo que estamos leyendo, no tenemos más que crear una clase como:

InputStream in = new CipherInputStream(new BufferedInputStream(new FileInputStream(“archivo.txt”)));

Evidentemente, para cada clase ***InputStream, existe su equivalente ***OutputStream. Con toda esta lista quiero dar una idea de la cantidad de cosas que se pueden enviar por mediante Sockets.

Como siempre es más fácil ver las cosas con ejemplos, aquí va uno del uso de Sockets:

Código del servidor:

            Socket s;
ServerSocket server = new ServerSocket(4976);  //escuchar por el puerto 4976
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
while(true) {
s = socket.accept();
in = socket.getInputStream();
out = socket.getOutputStream();
procesarPeticionServidor(in, out);
}

Código del cliente:

            Socket s = new Socket();
s.connect(new InetSocketAddress(4976)); //se intenta conectar al puerto 4976 del localhost
in = s.getInputStream();
out = s.getOutputStream();
procesarPeticionCliente(in,out);

Solo me queda comentar algunas cosas:

  • Es importante el orden de llamada de socket.getInputStream() y de socket.getOutputStream(). Así las cosas funcionan, pero si lo hubiésemos hecho al revés no, ya que ambos sockets se quedarían algo así como esperando la confirmación de que el stream de salida se ha configurado correctamente, y ambos se quedarían a la espera.
  • El servidor solo acepta una conexión entrante (ya que en cuanto la acepta deja de escuchar). Para aceptar más peticiones simultáneas debería empezar cada procesador de petición como un Thread independiente (es decir, una línea de ejecución de comandos independiente).
  • El código es necesario ponerlo entre cláusulas try-catch para tratar excepciones.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *