Download Sockets en Java
Document related concepts
no text concepts found
Transcript
El servicio de “echo” con sockets PRÁCTICA 2 E l objetivo de esta práctica es crear y ejecutar una aplicación cliente servidor "echo" basada en sockets TCP pero estructurándola según el modelo de objetos distribuidos. La comunicación remota se realizará utilizando el método de RPC (Remote Procedure Call) o invocaciones remotas a procedimientos (métodos de objetos en este caso). Esta primera práctica de comunicación remota pretende demostrar cómo realizar los stubs y esqueletos de la RPC manualmente. En las siguientes prácticas ya se generarán de forma automática. 2.1.- Estructura de la aplicación La aplicación Echo se estructura en tres paquetes: la interfaz, el cliente y el servidor, los cuales se describen a continuación. Ver figura 1 (pág. 16). 2.1.1.- El paquete interfaz (rmi) Consta del siguiente fichero: DYA 15 Estructura de la aplicación Misma interfaz Paquete: server Paquete: client Echo.java Paquete: rmi EchoInt.java EchoStub.java EchoServer.java echo(s: String) C EchoObject.java echo:(s: String) S Código de red FIGURA 1. Estructura de una RPC • EchoInt.java: describe el servicio "echo". Su finalidad es proporcionar a este servicio una interfaz de invocación a objeto remoto, ocultando el hecho de que la comunicación se realiza mediante sockets. Este fichero se encuentra completamente implementado. Visite este código y observe que lo único sorprendente de este código es la propagación de la siguiente excepción de RMI: throws java.rmi.RemoteException: representa cualquier error de comunicación remota. Cualquier excepción de comunicación con sockets debe ser reconvertido a esta excepción. Esto se realiza para mantener una uniformidad de Interfaz con la práctica siguiente sobre RMI. Allí se comprenderá plenamente. 2.1.2.- El paquete servidor (server) Consta, básicamente, de los siguientes ficheros: • EchoObject.java: implementa la interfaz EchoInt y proporciona el servicio de "echo". La implementación de ste servicio consiste en devolver la cadena que se envía, junto con el URL y la hora de la máquina servidora al cabo de 3 segundos. Este retraso simula que el servicio tiene un tiempo de cómputo largo y apreciable. Visite este código. • EchoServer.java: es el esqueleto de un servidor secuencial que realiza las siguientes operaciones: - Recibe una conexión a través de un socket 16 El servicio de “echo” con sockets Realización de la parte básica de la práctica - Invoca un objeto de l clase EchoObject.java - Devuelve la respuesta de la anterior invocación por el socket Este fichero se encuentra completamente implementado. Visite este código y observe el manejo de sockets. Existe también una segunda versión multihilo de EchoServer.java denominada EchoMultiServer.java que se analizará más adelante. 2.1.3.- El paquete cliente (client) Consta, básicamente, de los siguientes ficheros: • Echo.java: es el cliente propiamente dicho. Realiza el siguiente bucle: - Leer de teclado - Invocar el stub - Imprimir el resultado por pantalla. Visite este código y observe que existen EJERCICIOS. • EchoObjectStub.java: es el proxy del objeto en el nodo del cliente (stub del cliente). Observe que implementa la misma interfaz que el objeto: interfaz EchoInt y, adicionalmente, el método setHostAndPort, para especificar con que host y port se van a realizar las conexiones. Visite este código y observe que existen EJERCICIOS. Existe también una segunda versión de este fichero denominada EchoObjectStub4.java que implementa una política diferente de conexión/desconexión con el servidor que se analizará más adelante. 2.2.- Realización de la parte básica de la práctica Para la realización de la parte básica de la práctica cree un proyecto prj-sockets y siga la metodología descrita en la práctica 1 para el desarrollo de una aplicación Java. 1. 2. DYA Descargue los ficheros de ayuda al directorio de descargas. Cree un proyecto prj-sockets en el workspace según se indica en la práctica 1 cree también los paquetes de que consta la aplicación: rmi, client, server. 17 Realización de la parte básica de la práctica 2.2.1.- Versión no distribuida En primer lugar se desarollará una versión no distribuida de la práctica con “llamada local a procedimiento”. Se asumirá que la parte cliente y la servidora se encuentran en la misma máquina y el programa cliente invoca los servicios mediante invocación usual de un método Java. En esta versión no existen stubs. Ver figura 2 (pág. 18). FIGURA 2. Versión no distribuida de la práctica Para ello: 3. Incluya en el proyecto los ficheros necesarios para esta parte, copiándolos del directorio de descargas al workspace y actualizando la visión del Package explorer. - Paquete rmi: fichero EchoInt.java . El fichero se encuentra completamente terminado. - Paquete server: fichero EchoObject.java. El fichero se encuentra completamente terminado. - Paquete client: fichero Echo.java. Incluya en este fichero una invocación local. 4. Escriba el código necesario y ejecute la aplicación. 2.2.2.- Versión distribuida Posteriormente incluya los stubs necesarios en cada paquete y desarrolle la versión distribuida de la aplicación: 5. Desarrolle el paquete server: - Copie el fichero EchoServer.java del directorio de descargas al workspace. - Realice los ejercicios propuestos. - Ejecute y depure EchoSever.java utilizando el menú Run... y proporcionado los parámetros de ejecución que sean necesarios. 6. Desarrolle el paquete client: - Copie el fichero EchoObjectStub.java del directorio de descargas al workspace. - Realice los ejercicios propuestos. 18 El servicio de “echo” con sockets Realización de variantes de la práctica - Ejecute y depure Echo.java utilizando el menú Run... y proporcionado los parámetros de ejecución que sean necesarios. 7. Compruebe el correcto funcionamiento del cliente y servidores del servicio de echo conectándose con servidores remotos desarrollados por sus compañeros (y viceversa). 2.3.- Realización de variantes de la práctica En relación con esta práctica se proponen realizar dos ejercicios más avanzados con variantes del stub del cliente y del esqueleto del servidor. Estas variantes se proponen en los siguientes puntos. 2.3.1.- Servidor de echo multihilo. Un servidor de echo multihilo es un servidor que debe ser capaz de atender varias peticiones concurrentemente. La ejecución solapada de las diferentes peticiones se puede observar creando varios clientes e iniciando peticiones de servicio desde todos ellos de forma más o menos simultánea. La duración de tres segundos para la ejecución del servicio permitirá observar que las ejecuciones se solapan en el tiempo. Para la realización de este servicio, deberá realizar un nuevo esqueleto del servidor cuya implementación parcial se proporciona en el fichero EchoMultiServer.java. que sustituirá al antiguo esqueleto monohilo del servidor EchoServer.java. El fichero EchoObject.java que implementa el servicio, será el mismo que en el caso anterior. 2.3.2.- Stub del cliente con desconexión por timeout En este apartado se propone sustituir el stub del cliente de echo EchoObjectStub. java por un nuevo stub EschoObjectStub2. java que gestione las conexiones con el servidor con otra política. El stub original cabría una conexión al principio de cada petición y la cerraba al final de la misma. Para evitar la sobrecarga de abrir y cerrar conexiones cuando se producen peticiones muy frecuentes se propone realizar un nuevo stub del cliente con la siguiente política de gestión de conexiones: - Cuando termina una petición el stub del cliente programa la desconexión con el servidor para la cabo de 5 segundos. - Si el cliente realiza una petición y existe una conexión establecida, se envían los datos al servidor por la conexión existente, sino, se abre una nueva conexión y se envían los datos al servidor. DYA 19 Ficheros de apoyo - Si durante los cinco segundos siguientes a una invocación no llegan nuevas peticiones, se cierra automáticamente la conexión. La figura figura 3 (pág. 20) (a) muestra un cronograma de conexiones y desconexiones que indica cuándo debe hacerse una conexión, cuándo debe programarse una desconexión, cuando debe desconectarse y cuándo debe cancelarse una desconexión programada. Asimismo, la figura (b) ilustra cómo debe evitarse que la desconexión ocurra estando una petición en marcha FIGURA 3. gestión de la desconexión con timeout en stubs Se recomienda realizar una clase Timeout en base a las clases Timer y TimerTask: public class Timer extends Object A facility for threads to schedule tasks for future execution in a background thread. Tasks may be scheduled for one-time execution, or for repeated execution at regular intervals. public abstract class TimerTask extends Object implements Runnable A task that can be scheduled for one-time or repeated execution by a Timer 2.4.- Ficheros de apoyo 2.4.1.- Fichero rmi/EchoInt.java package rmi; public interface EchoInt extends java.rmi.Remote { public String echo(String input)throws java.rmi.RemoteException; } 20 El servicio de “echo” con sockets Ficheros de apoyo 2.4.2.- Fichero server/EchoObject.java package server; import java.net.*; import java.io.*; import java.text.*; import java.util.*; public class EchoObject implements EchoInt { String myURL="localhost"; public EchoObject(){ try { myURL=InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { myURL="localhost"; } } public String echo(String input) throws java.rmi.RemoteException { Date h = new Date(); String fecha = DateFormat.getTimeInstance(3,Locale.FRANCE).format(h); String ret = myURL + ":" + fecha + "> " + input; System.out.println("Procesando: '" + input + "'"); try { Thread.sleep(3000); ret = ret + " (retrasada 3 segundos)"; } catch (InterruptedException e) {} System.out.println("Procesamiento de '"+ input +"' terminado."); return ret; } } 2.4.3.- Fichero server/EchoServer.java package server; import java.net.*; import java.io.*; public class EchoServer { private static EchoObject eo = new EchoObject(); private static String myURL="localhost"; private static ServerSocket serverSocket = null; private static Socket clientSocket = null; private static BufferedReader is = null; private static PrintWriter os = null; private static String inputline = new String(); public static void main(String[] args) { try { myURL=InetAddress.getLocalHost().getHostName(); DYA 21 Ficheros de apoyo } catch (UnknownHostException e) { System.out.println("Unknown Host :" + e.toString()); System.exit(1); } try { serverSocket = new ServerSocket(7); } catch (IOException e) { System.out.println(myURL + ": could not listen on port: 7, " + e.toString()); System.exit(1); } System.out.println(myURL + ": EchoServer listening on port: 7"); try { boolean listening = true; while(listening){ clientSocket = serverSocket.accept(); is = new BufferedReader( new InputStreamReader( clientSocket.getInputStream())); os = new PrintWriter(clientSocket.getOutputStream()); while ((inputline = is.readLine()) != null) { os.println(eo.echo(inputline)); os.flush(); } os.close(); is.close(); clientSocket.close(); } serverSocket.close(); } catch (IOException e) { System.err.println("Error sending/receiving" + e.getMessage()); e.printStackTrace(); } } } 2.4.4.- Fichero server/EchoMultiServer.java package server; import java.net.*; import java.io.*; public class EchoMultiServer { private static ServerSocket serverSocket = null; public static void main(String[] args) { try { serverSocket = new ServerSocket(7); } catch (IOException e) { System.out.println("EchoMultiServer: could not listen on port: 7, " + e.toString()); 22 El servicio de “echo” con sockets Ficheros de apoyo System.exit(1); } System.out.println("EchoMultiServer listening on port: 7"); boolean listening = true; while (listening) { //EJERCICIO: aceptar una nueva conexión //EJERCICIO: y crear un Thread para que la gestione } try { serverSocket.close(); } catch (IOException e) { System.err.println("Could not close server socket." + e.getMessage()); } } } //---------------------------------------------------------------------------// class EchoMultiServerThread //---------------------------------------------------------------------------class EchoMultiServerThread extends Thread { private static EchoObject eo = new EchoObject(); private Socket clientSocket = null; private String myURL = "localhost"; private BufferedReader is = null; private PrintWriter os = null; private String inputline = new String(); EchoMultiServerThread(Socket socket) { super("EchoMultiServerThread"); clientSocket = socket; try { is = new BufferedReader(new InputStreamReader( //EJERCICIO ... )); os = new PrintWriter( //EJERCICIO ... ); } catch (IOException e) { System.err.println("Error sending/receiving" + e.getMessage()); e.printStackTrace(); } try { myURL=InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { System.out.println("Unknown Host :" + e.toString()); System.exit(1); } } public void run() { try { while ((inputline = is.readLine()) != null) { //EJERCICIO: Invocar el objeto //EJERCICIO: y devolver la respuesta por el socket DYA 23 Ficheros de apoyo } os.close(); is.close(); clientSocket.close(); } catch (IOException e) { System.err.println("Error sending/receiving" + e.getMessage()); e.printStackTrace(); } } } 2.4.5.- Fichero client/Echo.java package client; import java.io.*; import java.net.*; public class Echo { private static EchoObjectStub ss; public static void main(String[] args) { if (args.length<2) { System.out.println("Usage: Echo <host> <port#>"); System.exit(1); } ss = //EJERCICIO: crear una instancia del stub ss.setHostAndPort(args[0],Integer.parseInt(args[1])); BufferedReader stdIn = new BufferedReader( new InputStreamReader(System.in)); PrintWriter stdOut = new PrintWriter(System.out); String input,output; try { //EJERCICIO: el bucle infinito: //EJERCICIO: Leer de teclado //EJERCICIO: Invocar el stub //EJERCICIO: Imprimir por pantalla } catch (UnknownHostException e) { System.err.println("Don't know about host: "+ args[0]); } catch (IOException e) { System.err.println("I/O failed for connection to: "+args[0]); } } } 2.4.6.- Fichero client/EchoObjectStub.java package client; import java.net.*; import java.io.*; 24 El servicio de “echo” con sockets Ficheros de apoyo class EchoObjectStub implements EchoInt{ private Socket echoSocket = null; private PrintWriter os = null; private BufferedReader is = null; private String host = "localhost"; private int port=7; private String output = "Error"; private boolean connected = false; public void setHostAndPort(String host, int port) { this.host= host; this.port =port; } public String echo(String input)throws java.rmi.RemoteException { connect(); if (echoSocket != null && os != null && is != null) { try { os.println(input); os.flush(); output= is.readLine(); } catch (IOException e) { System.err.println("I/O failed in reading/writing socket"); throw new java.rmi.RemoteException("I/O failed in reading/writing socket"); } } disconnect(); return output; } private synchronized void connect() throws java.rmi.RemoteException { //EJERCICIO: Implemente el método connect } private synchronized void disconnect(){ //EJERCICIO: Implemente el método disconnect } } 2.4.7.- Fichero client/EchoStub2.java package client; import java.net.*; import java.io.*; class EchoObjectStub implements EchoInt, Runnable { private Socket echoSocket = null; private PrintWriter os = null; private BufferedReader is = null; private String host = "localhost"; DYA 25 Ficheros de apoyo private int port=7; private String output = "Error"; private boolean connected = false; private Thread reloj = new Thread(this, "reloj"); private int timeout = 50; private boolean firstTime = true; public void setHostAndPort(String host, int port) { this.host= host; this.port =port; } public String echo(String input)throws java.rmi.RemoteException { connect(); if (echoSocket != null && os != null && is != null) { try { os.println(input); os.flush(); output= is.readLine(); } catch (IOException e) { System.err.println("I/O failed in reading/writing socket"); throw new java.rmi.RemoteException("I/O failed in reading/writing socket"); } } programDisconnection(); return output; } private synchronized void connect() throws java.rmi.RemoteException { //EJERCICIO: lo mismo que en EchoObjectStub } private synchronized void disconnect(){ //EJERCICIO: lo mismo que en EchoObjectStub } private synchronized void programDisconnection(){ //EJERCICIO: programar un timeout para la cabo de 5 segundos } class Timeout { Timer timer; EchoObjectStub stub; int seconds; public Timeout (int seconds, EchoObjectStub stub) { this.seconds = seconds; this.stub = stub; } public void start() { //EJERCICIO 26 El servicio de “echo” con sockets Ficheros de apoyo } public void cancel() { //EJERCICIO } class TimeoutTask extends TimerTask { //EJERCICIO } } } DYA 27 Ficheros de apoyo 28 El servicio de “echo” con sockets