Está en la página 1de 26

LECCIN 4

COMUNICACIONES BASADAS EN EL PROTOCOLO TCP


4.1 INTRODUCCIN
El protocolo TCP (Transmisin Control Protocol) funciona en el nivel de transporte, basndose en el protocolo de red IP (Internet Protocol). IP proporciona comunicaciones no fiables y no basadas en conexin, muy dependientes de saturaciones en la red, cadas de nodos, etc. Por el contrario, TCP est orientado a conexin y proporciona comunicaciones fiables basadas en mecanismos de red que gestionan el control de flujo de paquetes y de congestin en los nodos. El control de flujo aludido es una caracterstica tcnica importante en el funcionamiento del protocolo TCP: evita que los nodos que envan informacin puedan saturar a los que la reciben; para lograr este objetivo, el protocolo TCP utiliza de manera interna un mecanismo de sincronizacin basado en tres comunicaciones realizadas entre cliente y servidor. Un aspecto que hay que tener en cuenta cuando se programan comunicaciones con TCP es la eficiencia que conseguimos. Cuando vamos a traspasar una gran cantidad de informacin (por ejemplo un fichero de vdeo), si no necesitamos tiempo real, TCP es un protocolo adecuado, puesto que el tiempo necesario para establecer la comunicacin es despreciable respecto al utilizado para transmitir los datos. En el otro extremo, si necesitamos una gran cantidad de comunicaciones cortas en las que la fiabilidad no es muy importante, TCP no es un protocolo adecuado; sera mucho mejor UDP, que se explicar ms adelante. Un ejemplo de esta situacin es el caso de una serie de sensores de temperatura y presin que mandan sus medidas por la red cada segundo y un aparato que recoge las medidas y las visualiza en una central de control.

42 JESS BOBADILLA SANCHO

En definitiva, el protocolo TCP posibilita la comunicacin fiable de datos entre nodos cliente y nodos servidores; resulta especialmente adecuado cuando el tamao de los datos que se transmiten no es pequeo. En Java, las comunicaciones TCP se realizan utilizando la clsica abstraccin de socket. Los sockets nos permiten establecer y programar comunicaciones sin tener que conocer los niveles inferiores sobre los que se asientan. Para identificar el destino de los paquetes de datos, los sockets utilizan los conceptos de direccin y puerto. La direccin se refiere a la m quina a la que se dirigen los datos; se determina gracias a la resolucin de nombres que proporcionan los DNS o simplemente aportando al socket, de manera directa, la direccin IP del nodo destino. Puesto que una misma mquina (nodo) puede hacerse cargo d e recoger varias comunicaciones diferentes de datos (habitualmente ligadas a distintos servicios), existe la necesidad de proporcionar un mecanismo que nos permita distinguir los paquetes que llegan relacionados con los distintos servicios ofrecidos (correo, news, web, etc.): este mecanismo son los puertos. Los puertos se representan con valores enteros, que no deben coincidir para diferentes servicios. Los datos que enviemos a un nodo con direccin IP 138.100.57.45 y puerto 6000 acabarn siendo tratados por programas diferentes (servidores) que los enviados al mismo nodo (138.100.57.45) y puerto 7000. Este mecanismo no es tan diferente al que usamos en el correo ordinario: adems de la direccin de la casa a la que enviamos una carta, indicamos la persona destinataria (que es el equivalente al puerto). Los valores numricos de puertos 1-1023 se reservan a servicios de inters general, montados a menudo sobre protocolos de uso extendido: el 80 para web con HTTP, el 25 para correo saliente con SMTP, el 110 para correo entrante con POP3, el 119 para el servicio de noticias con NNTP, etc. Los valores de puertos entre 1024 y 49151 se usan para servicios especficos de uso no general, el resto (a partir de 49152) se emplean para designar servicios de uso espordic o. En los siguientes apartados se explicar el mecanismo general con el que se utilizan los sockets TCP, la diferencia entre las clases Socket y ServerSocket, un primer ejemplo de comunicaciones (Hola mundo), una aplicacin teletipo, otra de traspaso del contenido de un fichero, etc.

JESS BOBADILLA SANCHO

43

4.2 ESTABLECIMIENTO DE COMUNICACIONES


Java proporciona dos clases de abstraccin de comunicaciones TCP: una para los procesos cliente (Socket) y otra para los procesos servidor (ServerSocket). Antes de entrar en los detalles de programacin vamos a mostrar grficamente el esquema bsico de establecimiento de comunicaciones TCP.

1 El programa que proporciona el servicio (programa servidor) crea una instancia de la clase ServerSocket, indicando el puerto asociado al servicio:
ServerSocket SocketServidor = new ServerSocket(Puerto);

2 El programa que proporciona el servicio invoca el mtodo accept sobre el socket de tipo ServerSocket. Este mtodo bloquea el programa hasta que se produce una conexin por parte de un cliente: ...SocketServidor.accept(); 3 El mtodo accept devuelve un socket de tipo Socket, con el que se realiza la comunicacin de datos del cliente al servidor: Socket
ComunicaConCliente = SocketServidor.accept();

4 El programa cliente crea una instancia de tipo Socket, a la que proporciona la direccin del nodo destino y el puerto del servicio: Socket SocketCliente
= new Socket(Destino, Puerto);

5 Internamente, el socket del cliente trata de establecer comunicacin con el socket de tipo ServerSocket existente en el servidor; cuando la comunicacin se establece es cuando realmente (fsicamente) se produce el paso 3 del diagrama. 6 Con los pasos anteriores completados se puede empezar a comunicar datos entre el cliente (o clientes) y el servidor.

44 JESS BOBADILLA SANCHO

4.3 TRANSMISIN DE DATOS


TCP es un protocolo especialmente til cuando se desea transmitir un flujo de datos en lugar de pequeas cantidades aisladas de informacin. Debido a esta caracterstica, los sockets de Java estn diseados para transmitir y recibir datos a travs de los Streams definidos en el paquete java.io. La clase Socket contiene dos mtodos importantes que se emplean en el proceso de transmisin de flujos de datos: InputStream getInputStream() OutputStream getOutputStream() Empleando el Stream de salida del socket del cliente y el Stream de entrada del socket del servidor podemos establecer un flujo de datos continuo a travs de la conexin TCP establecida:
Socket cliente Socket servidor

GetOutputStream()

GetInputStream()

OutputStream FlujoDeSalida = SocketCliente.getOutputStream(); InputStream FlujoDeEntrada = ComunicaConCliente.getInputStream(); Las clases OutputStream e InputStream son abstractas, por lo que no podemos emplear directamente todos sus mtodos. En general utilizaremos otras clases ms especializadas que nos permiten trabajar con flujos de datos: DataOutputStream, DataInputStream, FileOutputStream, FileInputStream, etc. DataInputStream Flujo = new DataInputStream(FlujoDeEntrada); DataOutputStream Flujo = new DataOutputStream(FlujoDeSalida); Una vez definidas instancias de este tipo de clases, nos basta con emplear los mtodos de escritura y lectura de datos que mejor se adapten a nuestras necesidades: Flujo.writeBytes("Lnea de texto"); int BytesLeidos = Flujo.read(Mensaje);

JESS BOBADILLA SANCHO

45

4.4 HOLA MUNDO


Para consolidar todas las ideas expuestas hasta el momento vamos a realizar la aplicacin ms sencilla posible: el tpico Hola mundo en versin TCP. En este caso necesitamos: Un programa que se ejecute en el equipo cliente y enve el texto Hola Mundo: TCPClienteHolaMundo Un programa que se ejecute en el equipo servidor y reciba e imprima el mensaje: TCPServidorHolaMundo Los programas TCPClienteHolaMundo y TCPServidorHolaMundo han sido implementados siguiendo los esquemas mostrados en los apartados anteriores, por lo que resultarn muy fciles de entender. Comencemos con TCPClienteHolaMundo :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import java.net.Socket; import java.io.*; import java.net.UnknownHostException; public class TCPClienteHolaMundo { public static void main(String[] args) { OutputStream FlujoDeSalida; DataOutputStream Flujo; try { Socket SocketCliente = new Socket("localhost", 8000); FlujoDeSalida = SocketCliente.getOutputStream(); Flujo = new DataOutputStream(FlujoDeSalida); Flujo.writeBytes("Hola Mundo"); SocketCliente.close(); } catch (UnknownHostException e) { System.out.println("Referencia a host no resuelta"); } catch (IOException e) { System.out.println("Error en las comunicaciones"); } catch (SecurityException e) { System.out.println("Comunicacion no permitida por razones de seguridad"); } } }

46 JESS BOBADILLA SANCHO

En la lnea 1 se indica que vamos a utilizar l a clase Socket del paquete java.net. Como ya sabemos, el cliente utiliza la clase Socket y el servidor utiliza, adems, la clase ServerSocket. En la lnea 11 se crea la instancia SocketCliente de la clase Socket, indicando que el nodo destino es la mquina local (localhost) y el puerto es el 8000 (asignado arbitrariamente). En la lnea 14 se obtiene el objeto FlujoDeSalida , de tipo OutputStream (lnea 8). Utilizando este objeto creamos una instancia Flujo (lnea 15) de tipo DataOutputStream (lnea 9). Entre los mtodos de la clase DataOutputStream se encuentra writeBytes , que utilizamos para enviar el texto Hola Mundo (lnea 16) a travs de la red. Tras enviar este nico mensaje cerramos el socket haciendo uso del mtodo close (lnea 18) y de esta manera liberamos los recursos empleados. Cuando utilizamos el mtodo writeBytes debemos ser conscientes de que estamos escribiendo un texto en un objeto de tipo DataOutputStream, que a su vez se sustenta en un objeto de tipo OutputStream, que se encuentra asocia do a un socket de tipo Socket; de manera que, finalmente, como esperamos, los datos escritos acaban saliendo a la red a travs del socket. Las lneas 19 a 28 contienen los diferentes tratamientos de excepciones que nos podemos encontrar al instanciar el socket y realizar el traspaso de informacin. Para completar TCPServidorHolaMundo :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

el

ejemplo

debemos

escribir

la

clase

import java.net.ServerSocket; import java.net.Socket; import java.io.*; public class TCPServidorHolaMundo { public static void main(String[] args) { byte[] Mensaje=new byte[80]; InputStream FlujoDeEntrada; DataInputStream Flujo; try { ServerSocket SocketServidor = new ServerSocket(8000); Socket ComunicaConCliente = SocketServidor.accept(); System.out.println("Comunicacion establecida"); FlujoDeEntrada =

JESS BOBADILLA SANCHO

47

18 ComunicaConCliente.getInputStream(); 19 Flujo = new DataInputStream(FlujoDeEntrada); 20 int BytesLeidos = Flujo.read(Mensaje); 21 System.out.println(new String(Mensaje)); 22 23 ComunicaConCliente.close(); 24 SocketServidor.close(); 25 26 } catch (IOException e) { 27 System.out.println("Error en las comunicaciones"); 28 System.exit(0); 29 } catch (SecurityException e) { 30 System.out.println("Comunicacion no permitida por 31 razones de seguridad"); 32 System.exit(0); 33 } 34 35 } 36 37 }

En las lneas 1 y 2 importamos las clase ServerSocket y Socket, que vamos a utilizar. En la lnea 3 nos aseguramos de que vamos a tener accesibles las distintas clases del paquete java.io que usamos en el programa: InputStream, DataInputStream e IOException . En la lnea 12 creamos la instancia SocketServidor, utilizando el puerto 8000 (que ha de ser el mismo que el definido en TCPClienteHolaMundo); a esta instancia se le aplica el mtodo accept (lnea 14) para establecer la conexin y obtener el objeto ComunicaConCliente , de tipo Socket. En las lneas 17 y 18 se obtiene el Stream de entrada asociado al socket que acabamos de conseguir. En la lnea 19 utilizamos el objeto FlujoDeEntrada , de tipo InputStream, para crear una instancia Flujo de tipo DataInputStream. La lectura de los datos que nos llegan por la lnea de comunicaciones se hace, en nuestro ejemplo, empleando el mtodo read de la clase DataInputStream (lnea 20). Mensaje es un vector de bytes (lnea 8) de 80 posiciones (el tamao del vector ha sido elegido arbitrariamente). Finalmente se imprime por consola (lnea 21) el texto que nos llega (esperamos Hola Mundo) y cerramos los sockets utilizados (lneas 23 y 24). Tras ejecutar cada programa en una consola diferente, las siguientes figuras muestran el correcto funcionamiento de nuestras clases. Ntese que debemos ejecutar en primer lugar la clase servidor, o nos encontraremos con una excepcin de tipo IOException en el cliente, tal y como mostramos a continuacin.

48 JESS BOBADILLA SANCHO

4.5 APLICANDO LA PROGRAMACIN ORIENTADA A OBJETOS


Partiendo del ejemplo Hola Mundo y cambiando muy pocas cosas, podemos crear dos clases abstractas: TCPCliente y TCPServidor que nos servirn de base para una gran cantidad de aplicaciones. Las nuevas clases se encargarn de crear los sockets necesarios y gestionar las excepciones que se puedan dar, dejando los detalles de envo y recepcin de datos a sendos mtodos abstractos Comunicacion , que deber implementar cada aplicacin concreta que deseemos realizar basndonos en las clases TCPCliente y TCPServidor . A continuacin se muestra la clase TCPCliente , que consta de un constructor (lnea 8) que admite como entrada la direccin del host y el puerto destino de las comunicaciones. La clase se encarga de crear el socket (lnea 12), establecer el flujo de salida (lneas 14 y 15) y tratar las excepciones (lneas 20 a 28). El envo de datos de cada aplicacin se debe implementar dentro del mtodo abstracto Comunicacin (lnea 32), que acta sobre el flujo de salida establecido (lnea 17).
1 2 3 4 import java.net.Socket; import java.io.*; import java.net.UnknownHostException;

JESS BOBADILLA SANCHO

49

5 public abstract class TCPCliente { 6 private DataOutputStream Flujo; 7 8 TCPCliente(String Host, int Puerto) { 9 OutputStream FlujoDeSalida; 10 11 try { 12 Socket SocketCliente = new Socket(Host, Puerto); 13 14 FlujoDeSalida = SocketCliente.getOutputStream(); 15 Flujo = new DataOutputStream(FlujoDeSalida); 16 17 Comunicacion(Flujo); 18 19 SocketCliente.close(); 20 } catch (UnknownHostException e) { 21 System.out.println("Referencia a host no 22 resuelta"); 23 } catch (IOException e) { 24 System.out.println("Error en las comunicaciones"); 25 } catch (SecurityException e) { 26 System.out.println("Comunicacion no permitida por 27 razones de seguridad"); 28 } 29 } 30 31 32 public abstract void Comunicacion (DataOutputStream 33 Flujo); 34 35 }

La clase TCPServidor es similar a TCPServidorHolaMundo , pero aplicando cambios similares a los explicados en TCPCliente .
1 2 3 4 5 6 7 8 9 10 11 12 13 import java.net.ServerSocket; import java.net.Socket; import java.io.*; public abstract class TCPServidor { private DataInputStream Flujo; TCPServidor(int Puerto){ byte[] Mensaje=new byte[256]; InputStream FlujoDeEntrada; try { ServerSocket SocketServidor = new ServerSocket(Puerto);

50 JESS BOBADILLA SANCHO

14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

Socket ComunicaConCliente = SocketServidor.accept(); System.out.println("Comunicacion establecida"); FlujoDeEntrada = ComunicaConCliente.getInputStream(); Flujo = new DataInputStream(FlujoDeEntrada); Comunicacion (Flujo); ComunicaConCliente.close(); SocketServidor.close(); } catch (IOException e) { System.out.println("Error en las comunicaciones"); System.exit(0); } catch (SecurityException e) { System.out.println("Comunicacion no permitida por razones de seguridad"); System.exit(0); } }

public abstract void Comunicacion (DataInputStream Flujo); }

4.6 APLICACIN TELETIPO: TALK


Utilizando las clases desarrolladas hasta el momento vamos a crear una aplicacin que nos permita visualizar por consola, en un ordenador, las frases que introducimos por teclado en otro (o el mismo) nodo. A esta aplicacin se le denomina talk ; es un servicio telemtico sncrono bastante utilizado. La clase TCPTeletipoCliente extiende a TCPCliente , por lo que heredamos la capacidad de establecer la conexin TCP que hemos programado en esta ltima clase. Al heredar la clase abstracta TCPCliente estamos obligados a implementar su mtodo abstracto Comunicacion (lnea 9); al menos si deseamos poder crear instancias de TCPTeletipoCliente .

JESS BOBADILLA SANCHO

51

En el mtodo Comunicacion nos encargaremos de programar la lectura de frases por teclado y el envo de estas frases por el flujo de salida Flujo, de tipo DataOutputStream. Para leer frases del teclado creamos un objeto Teclado de tipo InputStream, asignndolo a System.in (entrada estndar del sistema), todo ello en la lnea 12. En la lnea 16 utilizamos el mtodo read para leer una secuencia de hasta 256 caracteres (lnea 10) introducidos por el teclado. La siguiente lnea de cdigo (si no se levanta una excepcin) es la 23, donde se escribe (write ) por el Stream de salida (Flujo ) el array de NumBytesLeidos bytes Valor . Finalmente, las lneas 28 y 29 convierten la secuencia de bytes en un String Mensaje, que utilizamos para codificar una condicin de finalizacin del bucle de escritura y envo de datos (escribir el texto Fin). Cuando la ejecucin del mtodo Comunicacin finaliza, se contina con la secuencia de instrucciones del constructor de la clase TCPCliente , cerrndose el socket empleado en la comunicacin.
1 import java.io.*; 2 3 public class TCPTeletipoCliente extends TCPCliente { 4 5 TCPTeletipoCliente(String Host, int Puerto) { 6 super(Host, Puerto); 7 } 8 9 public void Comunicacion (DataOutputStream Flujo) { 10 byte[] Valor = new byte[256]; 11 int NumBytesLeidos = 0; 12 InputStream Teclado = System.in; 13 String Mensaje; 14 do { 15 try { 16 NumBytesLeidos = Teclado.read(Valor); 17 } 18 catch (IOException e){ 19 System.out.println("Error en la entrada de datos 20 por consola"); 21 } 22 try { 23 Flujo.write(Valor,0,NumBytesLeidos); 24 }catch (IOException e) { 25 System.out.println("Error en la escritura de 26 datos a la linea");

52 JESS BOBADILLA SANCHO

27 28 29 30 31 32 33 34

} Mensaje = new String(Valor); Mensaje = Mensaje.substring(0,NumBytesLeidos-2); } while (!Mensaje.equals("Fin")); } }

La clase TCPTeletipoServidor extiende la clase abstracta TCPServidor e implementa su nico mtodo Comunicacion . En primer lugar se lee cada frase proveniente del cliente (lnea 15), utilizando el mtodo read sobre el flujo de entrada Flujo. En las lneas 20 y 21 se convierte el array de bytes en un String; en la lnea 22 se imprime el String por la consola y en la lnea 24 se comprueba si llega la condicin de finalizacin del bucle.
1 import java.io.*; 2 3 public class TCPTeletipoServidor extends TCPServidor { 4 5 TCPTeletipoServidor(int Puerto) { 6 super(Puerto); 7 } 8 9 public void Comunicacion (DataInputStream Flujo) { 10 byte[] buffer = new byte[256]; 11 int BytesLeidos=0; 12 String Mensaje; 13 do { 14 try { 15 BytesLeidos = Flujo.read(buffer); 16 } catch (IOException e) { 17 System.out.println("Error en la lectura de 18 datos por linea"); 19 } 20 Mensaje = new String(buffer); 21 Mensaje = Mensaje.substring(0,BytesLeidos-2); 22 System.out.println(Mensaje); 23 24 } while (!Mensaje.equals("Fin")); 25 } 26 27 }

JESS BOBADILLA SANCHO

53

Para poder ejecutar la aplicacin Teletipo (talk) nicamente nos falta crear las clases, cliente y servidor, que instancien a TCPTeletipoCliente y TCPTeletipoServidor. Los p armetros empleados en cliente: host destino y puerto destino, los obtenemos de los argumentos con los que invocamos a TCPTeletipoClientePrincipal. Lo mismo hacemos con el puerto local que requieren las clases de tipo servidor.
1 public class TCPTeletipoClientePrincipal { 2 3 public static void main(String[] args) { 4 int Puerto = Integer.parseInt(args[1]); 5 String Host = args[0]; 6 TCPTeletipoCliente InstanciaCliente = new 7 TCPTeletipoCliente(Host,Puerto); 8 } 9 10 } 1 2 3 4 5 6 7 8 public class TCPTeletipoServidorPrincipal { public static void main(String[] args) { int Puerto = Integer.parseInt(args[0]); TCPTeletipoServidor InstanciaServidor = new TCPTeletipoServidor(Puerto); } }

En los grficos anteriores se observa como se comunica el cliente con el servidor. En este caso estamos utilizando un mismo equipo para albergar a ambos programas (cliente y servidor), por eso utilizamos el nombre localhost, que se refiere a la direccin de la mquina local.

54 JESS BOBADILLA SANCHO

4.7 UTILIZACIN DE HILOS


Cuando se programan servicios de comunicaciones normalmente surge la necesidad de utilizar threads para que diversos programas se ejecuten en paralelo, por ejemplo cuando existen distintos servidores y servicios en una misma mquina. Imaginemos que deseamos establecer un servicio de comunicaciones en cadena, de manera que una primera persona (el director) enva un mensaje a la segunda persona (un jefe de proyecto), que a su vez enva sus peticiones a una tercera persona (un analista) y as sucesivamente hasta llegar al programador. De esta manera la peticin del director se va traduciendo en las distintas peticiones jerrquicas necesarias. Con las clases que hemos implementado podemos resolver fcilmente el problema: el director necesita una ventana de consola ejecutando la clase TCPTeletipoClientePrincipal y el programador tambin necesita una sola ventana de consola ejecutando la clase TCPTeletipoServidorPrincipal; todos los dems implicados necesitan dos ventanas: una para recibir mensajes de su superior y otra para enviar a su subordinado. Para evitar el uso de varias consolas simultneas, podemos utilizar el mecanismo de programacin concurrente que nos ofrece Java: los hilos. Veamos como podemos variar nuestras clases para obtener una ventana que atienda concurrentemente la entrada (servidor) y salida (cliente) de mensajes.

La clase cliente se programa en TCPTeletipoClienteConHilos, que implementa a Thread, por lo que es un hilo (lnea 6); su constructor admite, adems de las referencias Host y Puerto , un campo de texto AreaEntrada donde se escribe el mensaje de salida (lneas 16 y 17). El mtodo run (lnea 24) de la clase Thread implementa el establecimiento de comunicaciones (lnea 26) y la determinacin del flujo de salida (lneas 27 y 28). Este mtodo se ejecuta cuando se arranca ( start) cada hilo instanciado de esta clase. En la lnea 41 se define la clase que implementa el nico mtodo (actionPerformed ) del interfaz ActionListener. La lnea 45 se encarga de recoger el mensaje introducido en el campo de texto (getText), convertirlo en array de bytes (getBytes) y escribirlo (write ) en el Stream de salida (Flujo ).

JESS BOBADILLA SANCHO

55

El final de la aplicacin (lnea 50) se alcanza cuando se enva el mensaje Fin (lnea 49).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import import import import java.io.*; java.awt.TextField; java.awt.event.*; java.net.*;

public class TCPTeletipoClienteConHilos extends Thread{ TextField AreaEntrada; OutputStream FlujoDeSalida; DataOutputStream Flujo; Socket SocketCliente; String Host; int Puerto;

TCPTeletipoClienteConHilos(String Host, int Puerto, TextField AreaEntrada) { this.AreaEntrada = AreaEntrada; this.Host = Host; this.Puerto = Puerto; }

public void run() { try { SocketCliente = new Socket(Host, Puerto); FlujoDeSalida = SocketCliente.getOutputStream(); Flujo = new DataOutputStream(FlujoDeSalida); } catch (UnknownHostException e) { System.out.println("Referencia a host no resuelta"); } catch (IOException e) { System.out.println("Error en las comunicaciones"); } AreaEntrada.addActionListener(new TextoModificado()); }

private class TextoModificado implements ActionListener { public void actionPerformed(ActionEvent e) { try { Flujo.write(AreaEntrada.getText().getBytes()); } catch (IOException IOe) {

56 JESS BOBADILLA SANCHO

47 48 49 50 51 52 53 54 55

System.out.println("Error al enviar los datos"); } if (AreaEntrada.getText().equals("Fin")) System.exit(0); AreaEntrada.setText(""); } } }

La clase TCPTeletipoServidorConHilos se apoya en TCPServidorConHilos , por lo que el establecimiento de comunicaciones lo tenemos resuelto y nos basta con implementar el mtodo abstracto Comunicacion . El mtodo Comunicacion recoge los mensajes que le llegan por la red (lnea 20), los convierte en String (lnea 25) y los visualiza en un campo de texto (lnea 26). El objeto de tipo TextField se obtiene a travs del constructor de la clase (lnea 9). El mtodo Comunicacion finaliza cuando llega el mensaje Fin (lnea 28).
1 import java.io.*; 2 import java.awt.TextField; 3 4 public class TCPTeletipoServidorConHilos extends 5 TCPServidorConHilos { 6 TextField AreaSalida; 7 8 TCPTeletipoServidorConHilos(int Puerto, 9 TextField AreaSalida) { 10 super(Puerto); 11 this.AreaSalida = AreaSalida; 12 } 13 14 public void Comunicacion (DataInputStream Flujo) { 15 byte[] buffer = new byte[256]; 16 int BytesLeidos=0; 17 String Mensaje; 18 do { 19 try { 20 BytesLeidos = Flujo.read(buffer); 21 } catch (IOException e) { 22 System.out.println("Error en la lectura de 23 datos por linea"); 24 } 25 Mensaje = new String(buffer,0,BytesLeidos); 26 AreaSalida.setText(Mensaje); 27 28 } while (!Mensaje.equals("Fin"));

JESS BOBADILLA SANCHO

57

29 30 31 }

La clase TCPServidorConHilos es idntica a TCPServidor, salvo en la definicin de la clase, donde ahora se extiende Tread: public abstract class TCPServidorConHilos extends Thread{ Finalmente necesitamos una clase, TCPTeletipoConHilos, que defina el interfaz grfico de usuario, instancie los hilos cliente y servidor y los arranque (start). El GUI de la aplicacin se define entre las lneas 6 y 24. En las lneas 12 y 13 se instancian los campos de texto Entrada y Salida, que sern utilizados para introducir mensajes (que recoge el hilo cliente) y visualizar los mensajes provenientes de la red (que recoge el hilo servidor). En las lneas 30 y 31 se intancia el hilo servidor, mientras que en las lneas 32 a 34 se hace lo propio con el hilo cliente. Una vez creados los hilos se arrancan (mtodo start) en las lneas 35 y 36. En las lneas 27 a 29 se recogen los argumentos de llamada a la clase, siguiendo el orden: direccin destino, puerto destino, puerto local.
1 import java.awt.*; 2 3 public class TCPTeletipoConHilos { 4 5 public static void main(String[] args) { 6 Frame MiMarco = new Frame(); 7 Panel Visor = new Panel(new GridLayout(2,1)); 8 Panel AreaEnviar = new Panel(new 9 FlowLayout(FlowLayout.LEFT)); 10 Panel AreaRecibir = new Panel(new 11 FlowLayout(FlowLayout.LEFT)); 12 TextField Entrada = new TextField(30); 13 TextField Salida = new TextField(30); 14 15 AreaEnviar.add(new Label("Enviado :")); 16 AreaEnviar.add(Entrada); 17 AreaRecibir.add(new Label("Recibido:")); 18 AreaRecibir.add(Salida); 19 Visor.add(AreaEnviar); 20 Visor.add(AreaRecibir);

58 JESS BOBADILLA SANCHO

21 MiMarco.add(Visor); 22 MiMarco.setSize(400,90); 23 MiMarco.setTitle("Talk con TCP"); 24 MiMarco.setVisible(true); 25 26 if (args.length==3) { 27 String HostRemoto = args[0]; 28 int PuertoLocal = Integer.parseInt(args[2]); 29 int PuertoRemoto = Integer.parseInt(args[1]); 30 TCPTeletipoServidorConHilos InstanciaServidor = new 31 TCPTeletipoServidorConHilos(PuertoLocal,Salida); 32 TCPTeletipoClienteConHilos InstanciaCliente = new 33 TCPTeletipoClienteConHilos(HostRemoto, 34 PuertoRemoto,Entrada); 35 InstanciaServidor.start(); 36 InstanciaCliente.start(); 37 } 38 else { 39 System.out.println("Es necesario pasar tres 40 argumentos (host remoto, puerto remoto, puerto local)"); 41 System.exit(0); 42 } 43 44 } 45 46 }

Ejecutando en tres mquinas diferentes las lneas de comando: Java TCPTeletipoConHilos Host2, 8002, 8000 Java TCPTeletipoConHilos Host3, 8004, 8002 Java TCPTeletipoConHilos Host4, 8006, 8004 ... Obtenemos

JESS BOBADILLA SANCHO

59

4.8 TRASPASO DE FICHEROS


El protocolo TCP resulta especialmente til para transmitir flujos de datos que requieren una comunicacin fiable; un ejemplo de esta situacin es el envo de un programa, que podra tener un tamao considerable y que no admite errores en la recepcin. En el siguiente ejemplo se proporcionan dos clases (TCPFicheroCliente y TCPFicheroServidor) que se encargan de traspasar un fichero de un ordenador a otro. TCPFicheroCliente extiende la clase TCPCliente , por lo que nicamente necesitamos implementar el mtodo abstracto Comunicacion (lnea 10). Para acceder al fichero secuencial utilizamos la clase FileInputStream (lneas 16 y 17). Si no existen problemas al leer el fichero (lneas 18 a 21) nos metemos en un bucle (lnea 24) donde se leen (mtodo read en la lnea 25) hasta 256 bytes (lneas 11 y 12). Tras la lectura de datos en el fichero, se realiza la escritura (write ) en el Stream de salida a la red (lnea 26); el bucle se termina cuando en alguna lectura del fichero no hemos rellenado por completo el buffer de entrada (lnea 27). La ltima accin es cerrar el fichero de entrada (lnea 28).
1 import java.io.*; 2 3 public class TCPFicheroCliente extends TCPCliente { 4 private FileInputStream FicheroOrigen; 5 6 TCPFicheroCliente(String Host, int Puerto) { 7 super(Host, Puerto); 8 } 9 10 public void Comunicacion (DataOutputStream Flujo) { 11 final int TAMANIO_BUFFER = 256; 12 byte buffer[] = new byte[TAMANIO_BUFFER]; 13 int NumBytesLeidos = 0; 14 15 try { 16 FicheroOrigen = new 17 FileInputStream("TCPCliente.java"); 18 } catch (FileNotFoundException e) { 19 System.out.println("Fichero no encontrado"); 20 System.exit(0); 21 } 22 23 try { 24 do {

60 JESS BOBADILLA SANCHO

25 26 27 28 29 30 31 32 33

NumBytesLeidos = FicheroOrigen.read(buffer); Flujo.write(buffer,0,NumBytesLeidos); } while (NumBytesLeidos == TAMANIO_BUFFER); FicheroOrigen.close(); } catch (IOException e){ System.out.println(e.getMessage()); } } }

La clase TCPFicheroServidor se encarga de recoger los datos que llegan por la red (lnea 20) y de escribirlos en el fichero de salida (lnea 21). Los detalles de implementacin son muy similares a los que existen en la clase TCPClienteServidor .
1 import java.io.*; 2 3 public class TCPFicheroServidor extends TCPServidor { 4 5 TCPFicheroServidor(int Puerto) { 6 super(Puerto); 7 } 8 9 public void Comunicacion (DataInputStream Flujo) { 10 final int TAMANIO_BUFFER = 256; 11 byte buffer[] = new byte[TAMANIO_BUFFER]; 12 int NumBytes=0; 13 14 try { 15 FileOutputStream FicheroDestino = new 16 FileOutputStream("Salida.txt"); 17 18 try { 19 do { 20 NumBytes = Flujo.read(buffer); 21 FicheroDestino.write(buffer,0,NumBytes); 22 } while (NumBytes==TAMANIO_BUFFER); 23 FicheroDestino.close(); 24 } catch (IOException e){ 25 System.out.println("Error de 26 entrada/salida"); 27 } 28 29 } catch (FileNotFoundException e) { 30 System.out.println("Fichero no encontrado"); 31 } 32 33 } 34 }

JESS BOBADILLA SANCHO

61

Para poder ejecutar estas clases nicamente nos falta instanciarlas desde sendos mtodos main . En la lnea 5 de la clase TCPFicheroClientePrincipal utilizamos directamente la direccin IP del nodo destino de la comunicacin.
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 public class TCPFicheroClientePrincipal { public static void main(String[] args) { TCPFicheroCliente InstanciaCliente = new TCPFicheroCliente("138.100.155.17",20000); } } public class TCPFicheroServidorPrincipal { public static void main(String[] args) { TCPFicheroServidor InstanciaServidor = new TCPFicheroServidor(20000); } }

En los siguientes grficos aparece el resultado de ejecutar la aplicacin y comprobar que el fichero ha sido traspasado correctamente:

62 JESS BOBADILLA SANCHO

4.9 COMUNICACIN BIDIRECCIONAL


Cuando se establece una conexin TCP, existe la posibilidad de realizar comunicaciones bidireccionales a travs de los sockets existentes en el cliente y el servidor. El modelo cliente-servidor se adapta de forma natural al enfoque de comunicacin semidplex: se realiza la peticin de servicio en un sentido y, posteriormente, se enva la respuesta en sentido contrario; en este apartado se presenta un ejemplo muy conciso en el que un programa cliente y un programa servidor se envan informacin en los dos sentidos (de cliente a servidor y de servidor a cliente). El programa cliente es TCPClienteDuplexHolaMundo . En la lnea 13 se define el socket a travs del cual se establecen las comunicaciones en la parte del cliente. En las lneas 1 5 a 21 se definen los Streams que canalizarn los datos de entrada y salida. La lnea 23 codifica un bucle dentro del cual se realiza, de manera alternativa, la comunicacin de salida (lnea 25) y la de entrada (lneas 28 a 34). La condicin de finalizaci n se ha establecido de manera que el bucle itere 20 veces. Finalmente, en la lnea 37 se cierra el socket.
1 import java.net.*; 2 import java.io.*; 3 4 public class TCPClienteDuplexHolaMundo { 5 6 public static void main(String[] args) { 7 DataInputStream FlujoEntrada; 8 DataOutputStream FlujoSalida; 9 byte[] Mensaje=new byte[80]; 10 int BytesLeidos=0, Frases=0; 11 12 try { 13 Socket SocketCliente = new Socket("localhost", 8000); 14 15 OutputStream FlujoDeSalida = 16 SocketCliente.getOutputStream(); 17 InputStream FlujoDeEntrada = 18 SocketCliente.getInputStream(); 19 20 FlujoEntrada = new DataInputStream(FlujoDeEntrada); 21 FlujoSalida = new DataOutputStream(FlujoDeSalida); 22 23 do { 24 try { 25 FlujoSalida.writeBytes("Hola terricola\n"); 26 Frases++;

JESS BOBADILLA SANCHO

63

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46

BytesLeidos = FlujoEntrada.read(Mensaje); } catch (IOException e) { System.out.println("Error en la lectura de datos"); System.exit(0); } System.out.print(new String(Mensaje,0,BytesLeidos)); } while (Frases!=20); SocketCliente.close(); } catch (UnknownHostException e) { System.out.println("Referencia a host no resuelta"); } catch (IOException e) { System.out.println("Error en las comunicaciones"); } } }

El programa servidor, TCPServidorDuplexHolaMundo , realiza acciones muy similares a TCPClienteDuplexHolaMundo . En este caso se crea una instancia de la clase ServerSocket (lnea 14) y, con ella, se crea el socket (lnea 15) que se comunicar con el cliente. Las comunicaciones se comienzan con la lectura de datos (lneas 28 a 32) y se terminan con la escritura (lnea 34).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import java.net.ServerSocket; import java.net.Socket; import java.io.*; public class TCPServidorDuplexHolaMundo { public static void main(String[] args) DataInputStream FlujoEntrada; DataOutputStream FlujoSalida; byte[] Mensaje=new byte[80]; int BytesLeidos=0, Frases=0; {

try { ServerSocket SocketServidor = new ServerSocket(8000); Socket ComunicaConCliente = SocketServidor.accept(); System.out.println("Comunicacion establecida"); OutputStream FlujoDeSalida =

64 JESS BOBADILLA SANCHO

19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 } 48 49 50 }

InputStream

ComunicaConCliente .getOutputStream(); FlujoDeEntrada = ComunicaConCliente .getInputStream();

FlujoEntrada = new DataInputStream(FlujoDeEntrada); FlujoSalida = new DataOutputStream(FlujoDeSalida); do { try { BytesLeidos = FlujoEntrada.read(Mensaje); } catch (IOException e) { System.out.println("Error en la lectura de datos"); System.exit(0); } System.out.print(new String(Mensaje,0,BytesLeidos)); FlujoSalida.writeBytes("Hola humano\n"); Frases++; } while (Frases!=20);

ComunicaConCliente.close(); SocketServidor.close(); } catch (IOException e) { System.out.println("Error en las comunicaciones"); System.exit(0); }

JESS BOBADILLA SANCHO

65

4.10 CONFIGURACIN DE LAS COMUNICACIONES


Los ejemplos que hemos desarrollado utilizan los valores por defecto en el comportamiento de los sockets, y por tanto en el desarrollo de la s comunicaciones. Es posible variar por programa ciertos comportamientos de las comunicaciones, as como obtener y establecer una gran cantidad de informacin relativa a las clases Socket y ServerSocket.
ServerSocket Mtodos principales Socket accept() void bind(SocketAddress a) Accin Espera a que se realice una conexin y devuelve un socket para comunicarse con el cliente Asigna la direccin establecida al socket creado con accept, si no se utiliza este mtodo se asigna automticamente una direccin temporal Cierra el socket Devuelve la direccin a la que est conectada el socket Devuelve el nmero de puerto asociado al socket Devuelve el valor en milisegundos que el socket espera al establecimiento de comunicacin tras la ejecucin de accept Asigna el nmero de milisegundos que el socket espera al establecimiento de comunicacin tras la ejecucin de accept

void close() InetAddress getInetAddress() int getLocalPort() int getSoTimeout()

void setSoTimeout(int ms)

Socket Mtodos principales void bind(SocketAddress a) Accin Asigna la direccin establecida al socket creado con accept, si no se utiliza este mtodo se asigna automticamente una direccin temporal Cierra el socket Conecta el socket a la direccin de servidor establecida Conecta el socket a la direccin de servidor establecida, esperando un mximo de ms milisegundos Devuelve la direccin a la que est conectada el socket Devuelve el stream de entrada asociado al socket Devuelve el nmero de puerto asociado al socket Devuelve el stream de salida asociado al socket Devuelve el valor del Puerto remoto al que est conectado Devuelve el nmero de milisegundos que se espera a los datos despus de cerrar (close) el socket Devuelve el valor en milisegundos que el socket espera al establecimiento de comunicacin tras la ejecucin de accept

void close() void connect(SocketAddress a) void connect(SocketAddress a, int ms) InetAddress getInetAddress() InputStream getInputStream() int getLocalPort() OutputStream getOutputStream() int getPort() int getSoLinger() int getSoTimeout()

66 JESS BOBADILLA SANCHO

boolean isBound() boolean isClosed() boolean isConnected() void setSoLinger(boolean Activo, int ms) void setSoTimeout(int ms)

Indica si el socket est vinculado Indica si el socket est cerrado Indica si el socket est conectado Se establece si se esperan o no los datos despus de cerrar el socket (y cuanto tiempo se esperan) Asigna el nmero de milisegundos que el socket espera al establecimiento de comunicacin tras la ejecucin de accept