Programacion en red

Un objeto java.net.Socket es un "conector" a trav�s del cual enviamos y recibimos datos mediante el protocolo TCP. A diferencia de los "conectores" java.net.DatagramSocket, que eran usados para enviar paquetes sueltos, estos "conectores" TCP sirven para enviar o recibir datos de forma continua, como si trabaj�ramos con un flujo InputStream o OutputStream.

El hecho de que el protocolo subyacente sea TCP nos permite olvidarnos de detalles relacionados con la p�rdida de datos, ya que es el propio protocolo el encargado de hacerlo por nosotros. A todos los efectos, podemos tratar un Socket TCP como un canal carente de errores.

�C�mo se usa un objeto Socket? La inicializaci�n de estos objetos es m�s compleja que en el caso de DatagramSocket, ya que es necesario que previamente haya alguien "escuchando" en el extremo receptor.

Suponiendo que, de alguna forma, hay un programa "escuchando" en el puerto 1234 de la m�quina con direcci�n IP 209.41.57.70, la inicializaci�n de nuestro Socket ser�a:

   InetAddress d = InetAddress.getByName("209.41.57.70"); 
   Socket s = new Socket(d,1234); 
   /* Utilizacion del socket */ 
   ... 
   /* Cerramos el socket */ 
   s.close();

Una vez tenemos un Socket abierto con otra m�quina, podemos obtener un flujo de entrada o de salida para poder recibir o transmitir datos. Esto se hace con los m�todos Socket.getInputStream() y Socket.getOutputStream():

Veamos un ejemplo donde abrimos un socket, leemos los bytes que nos transmitan desde el otro extremo y los imprimimos en pantalla:

   InetAddress d = InetAddress.getByName("209.41.57.70"); 
   Socket s = new Socket(d, 1234); 
   Inputstream is = s.getInputStream(); 
   while((int dato=is.read())!=-1){ 
   System.out.println("Recibido " + dato); 
   } 
   is.close(); 
   s.close();

.�La clase ServerSocket

La clase java.net.ServerSocket es el mecanismo mediante el cual nuestros programas pueden quedarse "escuchando" en un puerto, esperando conexiones entrantes. La forma general de trabajar con sockets ser� entonces: Un programa (lo llamaremos "servidor") crea un ServerSocket en un determinado puerto conocido por el resto de programas. El servidor queda esperando a que alg�n cliente intente conectar con �l. En el momento en que se establece la conexi�n, ambos programas (el cliente y el servidor) obtienen un objeto Socket. Mediante objetos InputStream y OutputStream obtenidos a trav�s de los objetos Socket, el cliente y el servidor intercambian datos. Uno de los dos programas cierra la conexi�n.

La parte del cliente ya la hemos visto en los apartados anteriores. Veamos como se realiza la parte del servidor.

La forma m�s sencilla de crear un objeto ServerSocket es indicando el n�mero de puerto al constructor:

   ServerSocket ss = new ServerSocket(1234); 
   /* Utilizamos el objeto ServerSocket */ 

Una vez creado, tenemos que quedarnos esperando a que alguien intente realizar la conexi�n. Esto se consigue mediante la funci�n ServerSocket.accept(). Esta funci�n espera una conexi�n entrante, y devuelve un objeto de tipo Socket.

   ServerSocket ss = new ServerSocket(1234); 
   Socket s = ss.accept(); 
   /* Utilizamos el objeto Socket */ 
   s.close();

Una vez el servidor tiene el objeto Socket, puede realizar las mismas acciones que el cliente (extraer los flujos de entrada/salida, cerrar la conexi�n, etc.)

En el siguiente ejemplo, nuestro servidor espera la conexi�n entrante y responde con un mensaje de bienvenida:

   ServerSocket ss = new ServerSocket(1234); 
   Socket s = ss.accept(); 
   
   OutputStream os = s.getOutputStream(); 
   String mensaje = "�Con�ctate con otro sitio y d�jame en paz, pesado!"; 
   byte[] matriz = mensaje.getBytes(); 

   os.write(matriz); 
   os.close(); 
   s.close();

De un objeto ServerSocket se pueden obtener muchos objetos Socket diferentes, cada uno independiente de los dem�s. Por ejemplo, podemos tener un programa que trabaje en el puerto 80 y que asigne cada nueva conexi�n a un hilo de ejecuci�n distinto. No es necesario que se cierren los objetos Socket previos antes de poder aceptar una nueva conexi�n. Esto es lo que hace que los servidores Web puedan atender a varias personas al mismo tiempo, sin tener que esperar a terminar con cada cliente antes de atender al siguiente.

Por ejemplo, supongamos que tenemos una clase de objetos "MiniServidor", que implementan la interfaz Runnable y que est�n programados para responder a las peticiones que les llegan a trav�s de un Socket. Una posible implementaci�n para el servidor ser�a:

   Serversocket ss = new ServerSocket(1234); 
   while(true){ 
   Socket s = ss.accept(); 
   MiniServidor m = new MiniServidor(s); 
   Thread t = new Thread(m); 
   t.start(); 
   }

.�Gesti�n de excepciones

Como en todos los casos en que tengamos que trabajar con protocolos de red o sistemas de entrada/salida, tenemos que encargarnos de gestionar las posibles excepciones. Las m�s comunes son java.io.IOException (para los casos en los que haya problemas con la conexi�n) y java.net.UnknownHostException (cuando especificamos una direcci�n IP desconocida o incorrecta).

.�Un ejemplo cliente/servidor completo usando TCP

Con el fin de comparar ambos m�todos, vamos a realizar un par de programas que realicen la misma funci�n que el ejemplo anterior, pero usando el protocolo TCP en vez de UDP.

Veremos una implementaci�n en la que para cada nuevo n�mero transmitido el cliente abre una conexi�n TCP distinta.Aunque este sistema es perfectamente v�lido, no es muy eficiente, ya que pierde mucho tiempo en el proceso de establecimiento de las conexiones. Ser�a m�s deseable una implementaci�n en la que todas las peticiones fueran a trav�s del mismo socket TCP.

El funcionamiento del programa es muy similar al del que usaba Datagramas. Se realiza un completo control de errores.

  import java.io.*; 
  import java.net.*; 

  class servidor{ 

  public static void main(String args[]){ 
  
 // Primero indicamos la direcci�n IP local 
 try{ 
   System.out.println("LocalHost = " + InetAddress.getLocalHost().toString()); 
   } catch (UnknownHostException uhe){ 
   System.err.println("No puedo saber la direcci�n IP local : " + uhe); 
   } 
  
  
 // Abrimos un "Socket de Servidor" TCP en el puerto 1234. 
 ServerSocket ss = null; 
 try{ 
   ss = new ServerSocket(1234); 
   } catch (IOException ioe){ 
   System.err.println("Error al abrir el socket de servidor : " + ioe); 
   System.exit(-1); 
   } 

  
 int entrada; 
 long salida; 
 // Bucle infinito 
 while(true){ 
   try{ 
   // Esperamos a que alguien se conecte a nuestro Socket 
 
   Socket sckt = ss.accept(); 
 
   // Extraemos los Streams de entrada y de salida 
   DataInputStream dis = new DataInputStream(sckt.getInputStream()); 
 
   DataOutputStream dos = new DataOutputStream(sckt.getOutputStream()); 
 
   // Podemos extraer informaci�n del socket 
   // N� de puerto remoto 
   int puerto = sckt.getPort(); 
 
   // Direcci�n de Internet remota 
   InetAddress direcc = sckt.getInetAddress(); 
   
   // Leemos datos de la peticion 
 
   entrada = dis.readInt(); 
   // Calculamos resultado 
 
   salida = (long)entrada*(long)entrada; 

   // Escribimos el resultado 
 
   dos.writeLong(salida); 
   // Cerramos los streams 
   dis.close(); 
   dos.close(); 
 
   sckt.close(); 
 
 
   // Registramos en salida estandard  
   System.out.println(  "Cliente = " + direcc + ":" + puerto + 
  "\tEntrada = " + entrada + 
  "\tSalida = " + salida ); 
   } catch(Exception e){ 
   System.err.println("Se ha producido la excepci�n : " +e); 
   } 
   } 
 
 } 
  
 } 
 
 

  class cliente { 

  public static void main(String args[]){ 
  
 // Leemos el primer par�metro, donde debe ir la direcci�n 
 // IP del servidor 
  
 InetAddress direcc = null; 
 try{ 
   direcc = InetAddress.getByName(args[0]); 
   } catch(UnknownHostException uhe){ 
   System.err.println("Host no encontrado : " + uhe); 
   System.exit(-1); 
   } 
  
 // Puerto que hemos usado para el servidor 
 int puerto = 1234; 
 
 // Para cada uno de los argumentos... 
 for (int n=1;n<args.length;n++){ 
   Socket sckt = null; 
   DataInputStream dis = null; 
   DataOutputStream dos = null; 
   try{ 
 
   // Convertimos el texto en n�mero 
   int numero = Integer.parseInt(args[n]); 
 
   // Creamos el Socket 
 
   sckt = new Socket(direcc,puerto); 
 
   // Extraemos los streams de entrada y salida 
   dis = new DataInputStream(sckt.getInputStream()); 
 
   dos = new DataOutputStream(sckt.getOutputStream());   
 
 
   // Lo escribimos 
   dos.writeInt(numero); 
    
   // Leemos el resultado final 
 
   long resultado = dis.readLong(); 
 
   // Indicamos en pantalla 
   System.out.println(  "Solicitud = " + numero + 
  "\tResultado = " +resultado ); 
   // y cerramos los streams y el socket 
   dis.close(); 
   dos.close(); 
   } catch(Exception e){ 
   System.err.println("Se ha producido la excepci�n : " +e); 
   } 
    
   try{ 
   if (sckt!=null) sckt.close(); 
   } catch(IOException ioe){ 
  System.err.println("Error al cerrar el socket : " + ioe); 
  } 
 
   } 
 
 } 
  }

Aparte de la introducci�n de los m�todos Socket.getPort() y Socket.getInetAddress(), que nos devuelven, respectivamente, el puerto y la direcci�n IP de la m�quina remota, el c�digo anterior �nicamente resume las ideas presentadas con anterioridad.

Se deja como "ejercicio para el lector" el cambiar el c�digo anterior para que todas las peticiones que el cliente transmite al servidor vayan por el mismo socket TCP.

.�Trabajo con URLs

Una URL (Uniform Resource Locator) es, a grandes rasgos, el nombre de un determinado recurso (archivos, bases de datos, ordenadores, impresoras, etc) en Internet. Por ejemplo, http://www.akal.com/index2.htm es una URL que "apunta" a una p�gina dentro de un servidor Web.

El formato de una URL est� definido en el standard RFC 1738, y suele seguir el esquema:

PROTOCOLO://MAQUINA/DIRECTORIO/SUBDIRECTORIO/ARCHIVO

Por ejemplo:

   ftp://ftp.microsoft.com/public/file.txt 
   http://www.etsit.upv.es/iaeste/index.html

Una forma m�s general, que nos permite especificar el puerto de conexi�n, as� como el login y el password es la siguiente:

http://login:password@maquina:puerto/dir/subdir/archivo

En Java, las URLs se representan mediante la clase java.net.URL. Esta clase solo representa la direcci�n, no su contenido. Para acceder al contenido de un objeto URL necesitamos obtener un objeto java.net.URLConnection, extra�do a partir del propio URL.

Por ejemplo, el siguiente c�digo crea una URL que apunta a http://www.javasoft.com, posteriormente obtiene un objeto URLConnection y por �ltimo hace algo realmente �til: leer el contenido de la URL:

   URL direccion = new URL("http://www.javasoft.com"); 
   URLConnection conex = direccion.openConnection(); 
   InputStream entrada = conex.getInputStream(); 
   while((int dato=entrada.read())!=-1){ 
   /* Hacemos algo interesante con lo que leemos */ 
   }

El ejemplo anterior es una muestra muy simple de como utilizar las clases URL y URLConnection. En una aplicaci�n real probablemente habr�a que usar el resto de m�todos que nos proporciona la clase URLConnection (para conocer datos del recurso remoto, leer informaci�n sobre la comunicaci�n y el tipo de protocolo usado, etc.)

Veamos un ejemplo completo. El siguiente programa simplemente se conecta con una URL especificada en la l�nea de comandos y guarda el contenido en un archivo.

  /* Este programa muestra como trabajar con URL's a trav�s de Java */ 

  /* El programa se limita a acceder a una URL, extraer su stream de salida, 
  y leer los datos byte a byte, guard�ndolos en el archivo especificado */ 

  import java.net.*; 
  import java.io.*; 

  class getURL{ 

  public static void main(String params[]){ 
  
 /* El programa debe recibir dos par�metros:  
   1.- la URL 
   2.- el archivo local donde guardar el resultado */ 
 if (params.length<2){ 
   System.err.println("Necesito una URL y un nombre de archivo local v�lidos"); 
   System.exit(-1); 
   } 
 /* Intentamos acceder a la URL */ 
  
 try{ 
   /* Si la URL fuera incorrecta saltar�a al catch de MalformedURLException */ 
   URL miUrl = new URL(params[0]); 
   /* Obtenemos una URLConnection, mediante openConnection(), y sacamos 
   un InputStream mediante getInputStream() */ 
    
   /* Si se produce alg�n error salta al catch de IOException */ 
   InputStream is = miUrl.openConnection().getInputStream(); 
 
   /* Abrimos el archivo para escritura */ 
   FileOutputStream fos = new FileOutputStream(params[1]); 
 
   int dato; 
   /* Vamos leyendo bytes hasta que read() nos devuelva -1 */ 
   while ((dato=is.read()) != -1) 
   fos.write(dato); 
 
   /* Cerramos todos los streams */ 
   fos.close(); 
   is.close(); 
   } 
 catch(MalformedURLException errorURL){ 
   System.err.println("La URL " + params[0] + " es incorrecta"); 
   } 
 catch(IOException errorIO){ 
   System.err.println("Error de entrada/salida : " + errorIO); 
   } 
   }   /* fin main */ 
 }   /*fin clase */

La forma de invocar el programa ser�a como sigue:

java getURL http://www.akal.com/index2.htm mifichero.htm

En el c�digo anterior adem�s aparecen las excepciones t�picas que aparecen en estos casos (java.net.MalformedURLException, java.io.IOException, etc.)

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP
ARTÍCULO ANTERIOR

SIGUIENTE ARTÍCULO