Una de las grandezas de Java es su funcionamiento en red. Se ha abstraido todo lo posible los detalles especificos de la red para hacer que trabajar en red sea paredido a trabajar con el sistema normal de archivos. La Máquina Virtual será la que haga el trabajo sucio. El modelo de trabajo es el mismo que se usa con los archivos. Se envuelve la conexión en un “socket” y a partir de ahi se trabaja con fujos de datos.
Para identificar las máquinas en la red está el sistema IP y DNS y java tiene la clase InetAddress en el paquete java.net que nos sirve para estas cosas. Mira como funciona…
//QuienEs.java
// utilidad para saber quien es una ip
import java.net.*;
public class QuienEs{
public static void main(String[] args) throws Exception{
if ( args.length != 1 ){
System.out.println("Uso QuienEs NombreDeMáquina");
System.exit(1);
}
InetAddress a = InetAddress.getByName(args[0]);
System.out.println(a);
}
}
Un socket es una abstraccción de porgrama que se usa para representar terminales de una conexion entre dos máquinas. En cada una de las máquinas hay un socket. Es, digamos como el enchufe donde se conectan los dos extremos del cable de la conexión.
Java crea un socket para cada conexion, del que extrae un InputStream y un OutputStream lo cual se puede convertir finalmente en cualquier tipo de Reader y de Writer de esta manera se puede tratar el socket como cualquier objeto de flujo de E/S (entrada / salida).
El proceso básico es el siguiente:
// ServidorParlante.java
import java.io.*;
import java.net.*;
public class ServidorParlante{
public static final int PUERTO = 1984;
public static void main(String[] args) throws IOException{
ServerSocket ss = new ServerSocket(PUERTO);
System.out.println("Empezando a escuchar" + ss);
try{
// vamos a probar de abrir el socket y a esperar a ver que pasa
Socket s = ss.accept(); // con esto le decimos que cuando le llegue alguna conexion la acepte y cree el socket s
try{
// leemos lo que nos dicen
System.out.println("conexion aceptada " + s);
BufferedReader entrada = new BufferedReader( new InputStreamReader(s.getInputStream())) ;
// respondemos
PrintWriter salida = new PrintWriter(
new OutputStreamWriter(
s.getOutputStream()
), true);
// ya tenemos la entrada y la salida...
while(true){
String str = entrada.readLine();
if(str.equals("FIN")) break; //condicion de salida
System.out.println("repoduccion de lo escuchado : " + str);
// respondemos lo mismo que hemos escuchado
salida.println(str);
}
}finally{
// queremos que al final cierre el socket
System.out.println("cerrando el socket");
s.close();
}
}finally{
// queremos que finalmente, pase lo que pase cierre el sevidor de sockets. si no lo tendremos ahi oyendo hasta que apaguemos el ordenador:
System.out.println("cerrando el servidor de socket" + ss);
ss.close();
}
}
}
Como puedes ver el servidor de sockets lo único que hace es iniciarse y quedarse bloqueado a la escucha. Cuando alguien le habla entonces crea un socket que es quien llevará a cabo la accion. Fijate que el socket actua de forma parecida a cualquier flujo de E/S . Y fijate que no es hasta que recibe la cadena preestablecida de final (FIN) el sigue leyendo sin importarle lo que le llega linea a linea.
Y aqui tienes el cliente:
// ClienteParlante.java
// cliente muy simple que solo envia lineas al sevidor
import java.net.*;
import java.io.*;
public class ClienteParlante{
public static void main(String[] args) throws IOException{
InetAddress addr = InetAddress.getByName("localhost");
System.out.println("addr="+addr);
Socket s = new Socket(addr, ServidorParlante.PUERTO);
try{
BufferedReader entrada = new BufferedReader(new InputStreamReader(s.getInputStream() ) );
PrintWriter salida = new PrintWriter( new BufferedWriter(new OutputStreamWriter( s.getOutputStream())), true);
for(int i = 0; i< 10; i++){
salida.println("hola "+i);
System.out.println( entrada.readLine() );
}
salida.println("FIN");
}finally{
System.out.println("cerrando");
s.close();
}
}
}
Fijate que:
Esto nos permite la opción de escribir realmente cada linea cuando nosotros queremos que la escriba. Dicho de otro modo, que envíe la linea a la red. De esta manera conseguimos enviar en cada caso exactamente lo que queremos: un string con “hola y un numero de bucle” de otra manera el BufferedWritter lo haría cuando el lo considerase oportuno. Esto es útil para aprovechar los paquetes de red y que la conexion sea mas fluida pero en nuestro caso, con el esquema que hemos seguido, nos saldría un churro y además como que la condición de fin es que la cadena enviada sea exactamente “FIN” si lo hicieramos del segundo modo nunca se enteraría porque enviaría un paquete lleno, con más de una de nuestras lineas por lo que nunca estaría sólamente la palabra “FIN”.
El problema del ejemplo anterior del servidor se sockets es que tan sólo puede atender una petición. No es capaz de gestionar más de una llamada y así como llamadas concurrentes. Grácias a la posibilidad de ejecucuón de hilos paralelos podemos hacer que el servidor de sockets abra un hilo nuevo para cada petición y cada socket sea un hilo independiente. De este modo el servidor sigue trabajando normalmente mientras que cada petición es atendida y gestionada en cada hilo.
//ServidorMultipleParlante.java
// un servidor multi - hilo
import java.io.*;
import java.net.*;
class ServidorUnParlante extends Thread{
private Socket s;
private BufferedReader entrada;
private PrintWriter salida;
public ServidorUnParlante(Socket socket) throws IOException{
s = socket;
entrada = new BufferedReader( new InputStreamReader(s.getInputStream())) ;
salida = new PrintWriter( new OutputStreamWriter( s.getOutputStream()), true);
start(); // llama a run();
}
public void run(){
try{
while(true){
String str = entrada.readLine();
if(str.equals("FIN")) break;
System.out.println("haciendo echo :" + str);
salida.println(str);
}
System.out.println("cerrando la conexion");
} catch (IOException e) {
System.err.println("Excepcion de E/S");
} finally {
try{
s.close();
} catch (IOException e) {
System.err.println("Socket sin cerrar...");
}
}
}
}
public class ServidorMultipleParlante{
static final int PUERTO = 1984;
public static void main(String[] args) throws IOException{
ServerSocket ss = new ServerSocket(PUERTO);
System.out.println("servidor iniciado");
try{
while(true){
Socket s = ss.accept();
try{
new ServidorUnParlante(s);
}catch( IOException ex){
// en caso de fallar al cerrar el socket lo cerramos aqui
s.close();
}
}
}finally{
ss.close();
}
}
}