Sei sulla pagina 1di 24

CMO HACER UN CHAT EN JAVA (JDK 1.2-1.3 y JDK 1.

4): FUNDAMENTOS, DESARROLLO E IMPLEMENTACIN


No cabe duda de que la nica razn por la que Java es popular es porque est diseado y pensado para desarrollar programas que se ejecutan en el navegador. A. Nicolau. A survey of distributed languages (1996) No se puede conducir un automvil mirando el retrovisor. Mire hacia atrs con ira. No hay nada bueno que mirar. M. McLuhan (1970) El propsito de este artculo es desarrollar de forma didctica un chat en Java y para ello se presentan en detalle las clases ServerSocket y Socket del paquete java.net, las nuevas clases Selector, java.nio.channels.SocketChannel y java.nio.channels.ServerSocketChannel de JDK 1.4, junto con una breve introduccin a los conceptos necesarios para entender su funcin y sus mtodos. En el apartado 1 se introducen los fundamentos necesarios para entender los conceptos utilizados; en el apartado 2 se realiza una breve panormica del paquete java.net y se explica con detalle las dos clases necesarias para el desarrollo del chat: ServerSocket y Socket. En el apartado 3 se presenta el cdigo necesario para realizar un chat por consola y en el apartado 4 se presenta el cdigo necesario para realizar un chat con interfaz grfico para el usuario. En el apartado 5 se explica la actualizacin del chat a JDK 1.4, usando las clases del nuevo paquete java.nio, que proporcionan importantes ventajas en cuanto a tiempos de respuesta y escalabilidad.

1.-Fundamentos
El protocolo IP es el protocolo de red utilizado para enviar datos entre los ordenadores conectados a Internet o que forman parte de Intranets. Los ordenadores que utilizan este protocolo se identifican mediante una o ms direcciones IP. Actualmente la versin en vigor de este protocolo es la IPv4 que utiliza 4 bytes (32 bits) para cada direccin IP. En formato decimal una direccin IP est formada por 4 nmeros enteros separados por puntos, siendo cada entero menor o igual a 255. La direccin IP tambin puede representarse a travs del DNS (Domain System Name), un nombre que identifica al ordenador de un modo mucho ms sencillo de recordar. Por ejemplo, www.aidima.es. La mayora de las aplicaciones que se ejecutan en Internet (Web, FTP, Telnet, Email, etc.) corresponden a una arquitectura denominada cliente-servidor. Se dice que una aplicacin informtica (definida como un conjunto de programas y datos) tiene una estructura cliente-servidor cuando los programas y datos que la constituyen se encuentran repartidos entre dos (o ms) ordenadores. Un ordenador, denominado cliente, contiene la aplicacin cliente (parte de la aplicacin informtica anterior) que se encarga de la gestin de la interaccin inmediata con el usuario y posiblemente de parte de la lgica y de los datos de la aplicacin. Las operaciones o los datos que no residen en el cliente pero que ste necesita se solicitan a la aplicacin servidor mediante el protocolo de aplicacin correspondiente. Esta aplicacin servidor se encuentra en un

ordenador compartido (el ordenador servidor), generalmente ms potente y de mayor capacidad que el equipo cliente, y est formada por el resto de datos y funcionalidades de la aplicacin informtica. Este equipo es el encargado de servir o atender y contestar las peticiones realizadas por los clientes. De acuerdo con la ubicacin de la lgica y los datos de la aplicacin se puede establecer distintas clasificaciones dentro de esta arquitectura (lgica distribuida, presentacin descentralizada, lgica descentralizada), pero un estudio en detalle no es necesario. Un ejemplo tpico de arquitectura cliente-servidor es el de un servidor web y un navegador web. El ordenador cliente es el del usuario que desea acceder a una pgina web y tiene en ejecucin un programa cliente (un navegador web, en este caso). Cuando un usuario introduce una direccin web en el navegador, ste solicita, a travs del protocolo HTTP, la pgina web a la aplicacin servidor web que se ejecuta en el ordenador servidor donde reside la pgina web. La aplicacin servidor web enva la pgina por Internet a la aplicacin cliente (el navegador) que se la ha solicitado y cuando el navegador la recibe la muestra al usuario.

Al igual que el protocolo IP permite la comunicacin entre ordenadores de Internet, los puertos permiten la comunicacin entre programas o aplicaciones especficas (genricamente, entre procesos) que se ejecutan en aquellos ordenadores. Esencialmente un puerto es una direccin de 16 bits asociado generalmente con un protocolo de aplicacin o de nivel 5. Un interfaz de ordenador de red est dividido lgicamente en 65536 puertos distintos. Cuando se considera una arquitectura clienteservidor (por ejemplo, el ejemplo anterior del servidor web y el navegador cliente) el programa o la aplicacin servidor permanece a la escucha de peticiones por parte de los clientes a travs de los puertos que tiene activos. La informacin o los servicios solicitados son enviados a los clientes por el servidor a travs de los puertos que estos han utilizado para realizar las peticiones. En general, los servicios de Internet tienen puertos estndar por defecto. Por ejemplo, el protocolo HTTP utiliza el puerto estndar 80 y el protocolo FTP el puerto estndar 21. Por ello no es preciso escribir en un navegador http://www.aidima.es:21, basta con escribir ftp://www.aidima.es.

Es posible utilizar puertos distintos de los estndares para los servicios de Internet, aunque no es lo habitual. Se podra perfectamente, por ejemplo, asignar el puerto 2278 al protocolo HTTP y el puerto 6756 al protocolo FTP. Lo nico que necesitaran conocer los clientes para intercambiar datos con el servidor seran los nuevos puertos asociados a cada servicio.

Existen dos protocolos de transporte (el TCP y el UDP) en la arquitectura TCP/IP que se encargan de enviar datos de un puerto a otro para hacer posible la comunicacin entre aplicaciones o programas. El protocolo TCP incorpora algoritmos de deteccin y correccin de errores en la transmisin de datos, mientras que el protocolo UDP no. En ambos se utilizan sockets para comunicar programas entre s en una arquitectura clienteservidor. Un socket es un punto de comunicacin bidireccional entre ordenadores. Eckel describe de una forma visual, con su estilo chispeante, en Thinking in Java 3rd Edition (Ver Seccin Noticias de javaHispano) un socket as: El socket es la abstraccin de software usada para representar los terminales de una conexin entre dos mquinas. Para una conexin dada, hay un socket en cada mquina, y puedes imaginar un cable hipottico corriendo entre las dos mquinas con cada extremo del cable enchufado a un socket. Desde luego, el hardware fsico y el cableado entre mquinas es completamente desconocido. El punto fundamental de la abstraccin es que no necesitamos conocer ms de lo necesario.

Los sockets envan secuencias de datos entre aplicaciones que se ejecutan, por lo general, en ordenadores distintos. Todo socket tiene asociado una direccin IP del host en el que se ejecuta el programa servidor o cliente (conocida como direccin del host) y una direccin del puerto utilizado por el programa cliente o servidor. Por ejemplo, para el protocolo HTTP:

Para que sea posible la comunicacin cliente-servidor el cliente debe establecer sockets y conectarlos a sockets del servidor. Los pasos que se siguen son los siguientes: 1) Creacin de los sockets en el cliente y el servidor 2) El servidor establece el puerto por el que proporcionar el servicio 3) El servidor permanece a la escucha de las peticiones de los clientes 4) Un cliente conecta con el servidor 5) El servidor acepta la conexin 6) Se realiza el intercambio de datos 7) El cliente, el servidor o ambos cierran la conexin. Para el programador de Java un socket es la representacin de una conexin para la transmisin de informacin entre dos ordenadores distintos o entre un ordenador y l mismo. Esta abstraccin de alto nivel permite despreocuparse de los detalles que yacen bajo ella (correspondientes a protocolos subyacentes). Bsicamente, y en resumen, un socket permite conectarse a un equipo a travs de un puerto, enviar y/o recibir datos y cerrar la conexin establecida.

2.-El paquete java.net de Java


Gran parte de la popularidad de Java se debe a su orientacin a Internet. Desde luego esta popularidad no es inmerecida: Java proporciona un interfaz orientada a objetos para sockets que simplifica notablemente las comunicaciones en red. En Java comunicarse a travs de Internet con otras aplicaciones es muy similar a obtener la entrada del usuario a travs de la consola o a leer ficheros (lo cual no quiere decir que sea trivial: debido al carcter multiplataforma de Java, estas operaciones tienen sus propias sutilezas y la eleccin equivocada de las clases a utilizar puede repercutir muy negativamente en el rendimiento de las aplicaciones desarrolladas), en contraste con lo que sucede en lenguajes como C. Cronolgicamente Java fue el primer lenguaje de programacin en el que la manipulacin de la entrada y salida de datos a travs de la red se realiza del mismo modo como se manipulan los datos de entrada y salida en ficheros. El paquete java.net, que proporciona un interfaz orientado a objetos para crear y manejar sockets, comprende clases que se pueden dividir en dos grandes grupos: a) Clases que corresponden a las APIs (interfaces de programacin de aplicaciones) de los sockets: Socket, ServerSocket, DatagramSocket, etc. b) Clases correspondientes a herramientas para trabajar con URL: URL, URLConnection, HttpURLConnection, URLEncoder, etc. El contenido completo de java.net es el siguiente: Clases: Authenticator ContentHandler DatagramPacket DatagramSocket DatagramSocketImpl HttpURLConnection InetAddress JarURLConnection

MulticastSocket NetPermission PasswordAuthentication ServerSocket Socket SocketImpl SocketPermission URL URLClassLoader URLConnection URLDecoder URLEncoder URLStreamHandler Excepciones: BindException ConnectException MalformedURLException NoRouteToHostException ProtocolException SocketException UnknownHostException UnknownServiceException Interfaces: ContentHandlerFactory FileNameMap SocketImplFactory SocketOptions URLStreamHandlerFactory

La jerarqua de clases del paquete java.net se detalla en la figura:

El estudio detallado de cada una de las clases de java.net cae fuera del mbito del presente artculo y solamente se van a exponer brevemente aquellas necesarias para el funcionamiento del chat del apartado 3: 1) Clase java.net.ServerSocket: Esta clase es utilizada por el servidor para recibir conexiones de los clientes bajo el protocolo TCP. Toda aplicacin que acte como servidor crear un objeto o instancia de esta clase y aguardar bloqueada (o, lo que es lo mismo, permanecer a la escucha) en una invocacin a su mtodo accept() hasta que llegue una conexin por parte de algn cliente. Cuando la aplicacin escuche alguna peticin el mtodo accept() crear un objeto o instancia de la clase java.net.Socket que se usar para comunicarse con el cliente. Es perfectamente posible que un servidor tenga un nico objeto ServerSocket y muchos objetos Socket asociados (cada uno a un cliente concreto).

E introduciendo esquemticamente- en la figura los puertos del host servidor y el mtodo accept() del ServerSocket:

Esta clase proporciona tres constructores que especifican el puerto utilizado por el socket del servidor para escuchar las peticiones TCP de los clientes, la longitud mxima de la cola para conexiones entrantes y una direccin de Internet (solo el nmero del puerto es obligatorio): public ServerSocket (int puerto) throws IOException public ServerSocket (int puerto, int longitudcola) throws IOException public ServerSocket (int puerto, int longitudcola, InetAddress dirInternet) throws IOException Resulta importante resaltar que la cola de conexiones entrantes es de tipo FIFO (First In, First Out) y que la longitud mxima de sta viene determinada por el sistema operativo. Es decir, si el parmetro longitudcola supera el tamao mximo de la cola establecida por el sistema operativo se usar en realidad este ltimo valor, sin que se genere ningn aviso para el programador. En el caso de que la cola est llena el objeto ServerSocket rechazar nuevas conexiones hasta que aparezcan huecos en la cola. El mtodo getInetAddress() proporciona la direccin del host servidor al que est asociado el socket; el mtodo getLocalPort() proporciona el puerto en el que el socket del servidor permanece a la escucha de posibles peticiones de clientes. El mtodo close() cierra el ServerSocket. Curiosamente no existen mtodos en esta clase para extraer los flujos de datos de entrada y salida: es necesario usar el mtodo accept() y aplicar los mtodos

getInputStream() y getOutputStream() de la clase Socket, que se ver a continuacin, sobre el objeto Socket devuelto. Ejemplo: // Se ha seleccionado el puerto 9000 para escuchar las peticiones //de los clientes ServerSocket socketservidor=new ServerSocket(9000); // Crear un socket cuando un cliente haga una peticin Socket socketcliente=socketservidor.accept(); En el caso de que un objeto ServerSocket intentara escuchar peticiones de los clientes a travs de un puerto que estuviera siendo usado por otro ServerSocket, se producira una excepcin java.net.BindException. 2) Clase java.net.Socket: Esta clase implementa sockets del cliente por medio del protocolo TCP (de ahora en adelante se usar sockets para este tipo de sockets, salvo que se diga lo contrario). De los 8 constructores de esta clase seis utilizan como argumentos el nombre (o direccin IP) del host y la direccin del puerto de destino: protected Socket () throws SocketException protected Socket (SocketImpl impl) throws SocketException public Socket (String host, int puerto) throws UnknownHostException, IOException public Socket (InetAddress direccion, int puerto) throws IOException public Socket(String host, int puerto, InetAddress dirLocal, int puertoLocal) throws IOException public Socket (InetAddress direccion, int puerto, InetAddress dirLocal, int puertoLocal) throws IOException public Socket (String host, int puerto, boolean stream) throws IOException public Socket (InetAddress host,int puerto,boolean stream) throws IOException Dentro de esta clase, los mtodos getInetAddress() y getPort() proporcionan, respectivamente la direccin IP del host de destino y el nmero de puerto del host de destino al que est asociado el socket. Los mtodos getInputStream() y getOutputStream() se utilizan para acceder a los flujos (streams) de entrada y salida asociados al socket. El mtodo getLocalPort() devuelve la direccin del puerto local a la que est asociado el socket y el mtodo close() cierra el socket y los flujos de entrada y salida asociados. Ejemplos: //Crea un socket con el nombre de host especificado y el // puerto 25. Socket socketcliente=new Socket(www.aidima.es, 25); // Crea un socket con la direccin IP dada y el puerto 25 Socket socketcliente= new Socket(26.56.78.140, 25); // Crea un socket con la direccin IP dada y el puerto 25 Socket socketcliente= new Socket(26.56.78.140, 25); //Una vez establecida la conexin se lee un byte del servidor

//usando el mtodo read() InputStream entrada=socketcliente.getInputStream(); OutputStream salida=socketcliente.getOutputStream(); salida.read(); //Se usa la clase PrintWriter como envoltorio de OutputStream //para enviar una cadena de caracteres al flujo de salida PrintWriter misalida=new PrintWriter(salida, true); misalida.println(Escribiendo en la salida); socketcliente.close();

3.-Un chat en Java por consola


En este apartado se presenta el cdigo necesario para la creacin de un chat mediante consola, en el que se han utilizado los conceptos, clases y mtodos expuestos anteriormente. Un chat corresponde exactamente a una arquitectura cliente-servidor: los usuarios del chat (los clientes) se conectan al chat (el servidor) intercambiando mensajes entre s. La implementacin del programa servidor del chat en Java (y, en general, de cualquier programa servidor en Java) se realiza con los siguientes pasos: 1. Se crea un objeto de la clase ServerSocket para escuchar a las peticiones que llegan al puerto asociado al servicio. 2. El socket aguarda las peticiones de los clientes que llegan a travs del puerto usando el mtodo accept(). 3. Al llegar una solicitud se sigue la siguiente secuencia; 3.1. Se acepta la conexin, generando un objeto Socket. 3.2. Se asocian objetos de las clases contenidas en el paquete java.io a los flujos (streams) de entrada y salida del socket. 3.3. Se lee y/o escribe en los streams o, lo que es lo mismo, se leen, procesan y envan respuestas a los clientes 4. Se cierran los streams. 5. Se cierra el socket. Igualmente el programa cliente se implementa as: 1. Se crea un objeto Socket, que tiene asociado un host y un puerto donde se ejecuta el servicio. 2. Se asocian objetos de las clases contenidas en el paquete java.io a los flujos (streams) de entrada y salida del socket. 3. Se lee y/o escribe en los streams. 4. Se cierran los streams. 5. Se cierra el socket. Lo anterior se podra representar esquemticamente as:

Deliberadamente se ha evitado en este apartado el uso de un interfaz grfico para centrar la atencin en las clases y mtodos utilizados. El cdigo permite el intercambio de mensajes entre los clientes a travs de la consola y se genera un archivo de texto plano a modo de historial en el directorio raz del ordenador servidor (historial.txt) en el que figuran la fecha y hora de cada conexin (con el nombre del host cliente y el puerto asociado) y la fecha y hora de su cierre. Si no se pudiera generar o acceder al archivo de historial (por ejemplo, por cuestiones de asignacin de permisos), se lanzar un mensaje de aviso pero se podr usar el chat. Una salida tpica sera la siguiente:

ServidorChat.java
import java.net.*; import java.io.*; import java.util.*;

import java.awt.*; import javax.swing.*; public class ServidorChat { public static void main(String args[]) throws IOException, BindException{ try{ ServerSocket sservidor=new ServerSocket(9000); Socket scliente=null; while (true){ try{ scliente=sservidor.accept(); new ThreadServidor(scliente).start(); } catch(IOException e1) { //Si el mtodo accept() falla se debe a un error de E/S mientras //se esperan conexiones. //Si el constructor del hilo falla se debe a un error temprano //de la conexin. //No se avisa de estos errores ni se registran. //Si ocurre alguna excepcin al intentar cerrar el socket se //ignora porque posiblemente se deba a que fue ya cerrado por el //el cliente. try {scliente.close();} catch (IOException e2) {} } } } catch(IOException e3){ //Los errores al crear un objeto ServerSocket son irrecuperables. JOptionPane.showMessageDialog(null,"Error fatal."+ "\n "+ "No puede arrancarse el servidor.", "Informacin para el usuario", JOptionPane.WARNING_MESSAGE); System.exit(0); } } } class ThreadServidor extends Thread { // Version 1.0 Miguel Angel Abin Septiembre 2002 private Socket s; private BufferedReader entrada; private PrintWriter salida; private String usuario; private static Vector clientesActivos=new Vector(); public ThreadServidor(Socket socket) throws IOException{ s=socket;

salida=new PrintWriter(s.getOutputStream(),true); entrada=new BufferedReader(new InputStreamReader(s.getInputStream())); usuario=s.getInetAddress().getHostName() + ":"+s.getPort(); clientesActivos.addElement(this); try{ String historial="C:"+File.separatorChar+"historial.txt"; PrintWriter salidaArchivo = new PrintWriter(new BufferedWriter (new FileWriter(historial,true))); salidaArchivo.println("Conexin desde la direccin: "+ s.getInetAddress().getHostName() + " por el puerto "+ s.getPort()+ " en la fecha " + new Date()); salidaArchivo.close(); } catch(IOException e2){ JOptionPane.showMessageDialog(null,"Fallo en el archivo de historial", "Informacin para el usuario", JOptionPane.WARNING_MESSAGE); } } private static void escribir(String textoUsuario){ for (int i=0;i<clientesActivos.size();i++){ System.out.println("Enviando a: "+i+textoUsuario); ((ThreadServidor)(clientesActivos.elementAt(i))).salida.println(textoUsuario); } } public void run() { String textoUsuario; try{ while ((textoUsuario=entrada.readLine())!=null) escribir (usuario+"> "+ textoUsuario);} catch (Exception e1){ if (e1.getMessage().equals("Connection reset by peer: JVM_recv" + " in socket input stream read")){ try{ String historial="C:"+File.separatorChar+"historial.txt"; PrintWriter salidaArchivo = new PrintWriter(new BufferedWriter (new FileWriter(historial,true))); salidaArchivo.println("Desconexin desde la direccin: "+ s.getInetAddress().getHostName() + " por el puerto "+s.getPort()+ " en la fecha " + new Date()); salidaArchivo.close(); } catch(IOException e2){ JOptionPane.showMessageDialog(null,"Fallo en el archivo de historial", "Informacin para el usuario", JOptionPane.WARNING_MESSAGE);} }

} finally {clientesActivos.removeElement(this); try{ s.close(); } catch(Exception e3){ JOptionPane.showMessageDialog(null,"No se ha podido cerrar el socket", "Informacin para el usuario", JOptionPane.WARNING_MESSAGE);} } } }

ClienteChat.java
import java.net.*; import java.io.*; import java.awt.*; import javax.swing.*; public class ClienteChat { // Version 1.0 Miguel Angel Abin Septiembre 2002 public static void main(String[] args) throws IOException { Socket scliente=new Socket("localhost",9000); BufferedReader entrada= new BufferedReader(new InputStreamReader(scliente.getInputStream())); PrintWriter salida=new PrintWriter(scliente.getOutputStream(),true); BufferedReader entradaConsola= new BufferedReader(new InputStreamReader(System.in)); new threadCliente(entrada).start(); while(true) salida.println(entradaConsola.readLine()); } } class threadCliente extends Thread{ BufferedReader entrada; public threadCliente (BufferedReader entrada) throws IOException{ this.entrada=entrada; } public void run(){ String linea; try{ while((linea=entrada.readLine())!=null) System.out.println(linea);} catch (IOException e){ JOptionPane.showMessageDialog(null,"Error en la comunicacin", "Informacin para el usuario", JOptionPane.WARNING_MESSAGE);} finally { System.exit(0); } } }

Esta aplicacin de chat por consola consta de dos clases, una se ejecuta en el servidor y la otra en el cliente. La clase ServidorChat contiene un mtodo main() que comienza creando un objeto ServerSocket, unido a un puerto determinado (en este ejemplo el puerto 9000: un homenaje personal a HAL 9000). A continuacin se realiza un bucle infinito, esperado conexiones de los clientes y creando instancias de la clase threadServidor para atender cualquier peticin: el bucle while espera a que el mtodo accept() de ServerSocket devuelva un nuevo Socket por cada conexin de un cliente y entonces lanza un objeto threadServidor asociado a ese cliente. La clase threadServidor desciende de la clase Thread y utiliza un objeto esttico del tipo Vector para conocer los usuarios conectados y poder enviarles los mensajes escritos, mediante el mtodo escribir(). La clase utiliza el objeto PrintWriter salidaArchivo para llevar un registro de las conexiones y desconexiones de los clientes en un archivo llamado historial.txt que se almacena en el directorio raz. La clase ClienteChat contiene un mtodo main() que comienza creando un Socket, unido a un host y a un puerto determinado (en este ejemplo la mquina local localhost- y el puerto 9000) y utiliza un objeto BufferedReader para obtener la entrada por consola de los clientes. A cada cliente se le asocia un hilo (un objeto threadCliente) para poder recibir por consola los mensajes del resto de clientes. La instruccin while permite la lectura continua de la entrada por consola de los clientes.

4.-Un chat en Java con interfaz grfico


El interfaz grfico para el cliente sera as:

El cdigo se presenta a continuacin:

ClienteChat.java
import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.io.*; import java.net.*; public class ClienteChat extends JFrame { // Version 1.0 Miguel Angel Abin Septiembre 2002 public static void main(String[] args) throws IOException, ConnectException{ boolean packFrame = false; try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); ClienteChat f=new ClienteChat(); if (packFrame) { f.pack(); } else { f.validate(); } Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension frameSize = f.getSize(); if (frameSize.height > screenSize.height) { frameSize.height = screenSize.height; } if (frameSize.width > screenSize.width) { frameSize.width = screenSize.width; } f.setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2); f.setVisible(true); } catch(Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(null,"Error en el gestor de apariencia y"+ estilo, "Informacin para el usuario", JOptionPane.WARNING_MESSAGE); System.exit(0); } } JPanel contentPane; JTextField txtMensaje = new JTextField(); JTextArea txtArea = new JTextArea(); JButton btnCerrar = new JButton();

Socket scliente; Sesion sesion; PrintStream salida; BufferedReader entrada; JScrollPane scrollPane = new JScrollPane(); JLabel lblTexto = new JLabel(); public ClienteChat() { enableEvents(AWTEvent.WINDOW_EVENT_MASK); try { iniciar(); scliente=new Socket("localhost", 9000); salida=new PrintStream(scliente.getOutputStream()); entrada= new BufferedReader(new InputStreamReader(scliente.getInputStream())); sesion=new Sesion(this); sesion.start(); } catch(Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(null,"Fallo en la conexin", "Informacin para el usuario", JOptionPane.WARNING_MESSAGE); System.exit(0); } } private void iniciar() throws Exception { contentPane = (JPanel) this.getContentPane(); txtMensaje.setBounds(new Rectangle(117, 49, 256, 29)); txtMensaje.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { txtMensaje_actionPerformed(e); } }); contentPane.setLayout(null); this.setSize(new Dimension(400, 400)); this.setTitle("Chat"); btnCerrar.setText("Cerrar"); btnCerrar.setBounds(new Rectangle(150, 323, 76, 29)); btnCerrar.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { btnCerrar_actionPerformed(e); } }); scrollPane.setBounds(new Rectangle(9, 106, 370, 205)); lblTexto.setBackground(Color.red); lblTexto.setFont(new java.awt.Font("Dialog", 1, 12)); lblTexto.setText("Escriba texto:"); lblTexto.setBounds(new Rectangle(17, 49, 87, 32));

contentPane.add(txtMensaje, null); contentPane.add(btnCerrar, null); contentPane.add(lblTexto, null); contentPane.add(scrollPane, null); scrollPane.getViewport().add(txtArea, null);

protected void processWindowEvent(WindowEvent e) { super.processWindowEvent(e); if (e.getID() == WindowEvent.WINDOW_CLOSING) { salida.close();System.exit(0); } } void txtMensaje_actionPerformed(ActionEvent e) { salida.println(txtMensaje.getText()); } void btnCerrar_actionPerformed(ActionEvent e) { salida.close(); System.exit(0); } }

Sesion.java
import java.io.*; import java.awt.*; import javax.swing.*; public class Sesion extends Thread { // Version 1.0 Miguel Angel Abin Septiembre 2002 private ClienteChat sesion; public Sesion(ClienteChat sesion) throws Exception { this.sesion=sesion; } public void run(){ String linea=""; try{ while ((linea=sesion.entrada.readLine())!=null) sesion.txtArea.setText(sesion.txtArea.getText()+linea+"\n"); } catch (IOException e){ JOptionPane.showMessageDialog(null,"Fallo en la comunicacin", "Informacin para el usuario", JOptionPane.WARNING_MESSAGE); System.exit(0);} }

} Esta aplicacin de chat con interfaz grfico consta de tres clases: la clase ServidorChat es la misma del apartado anterior y se ejecuta en el servidor y las nuevas clases ClienteChat y Sesion, que se ejecutan en el cliente, se encargan del interfaz grfico y de la gestin de la interaccin con el cliente. Una sesin tpica sera similar a esta:

Los errores de conexin daran mensajes de este estilo:

5.-Un chat en Java: Actualizacin a JDK 1.4


En el programa ServidorChat.java a cada cliente se le asigna un hilo. Conforme vaya aumentando el nmero de usuarios del chat, ir aumentando de modo no lineal debido a la naturaleza de los hilos- el tiempo que se tarda en enviar los mensajes a los usuarios. Adems, como cada hilo permanece bloqueado mientras se espera la entrada del usuario con el mtodo readLine() dentro del mtodo run() del hilo, la aplicacin difcilmente ser escalable. Con la aparicin de JDK 1.4 y su nuevo paquete java.nio se puede mejorar la eficiencia de la aplicacin y reducir espectacularmente la carga de trabajo del servidor chat. Este nuevo paquete proporciona canales (channels) y selectores (selectors).Un canal representa una conexin abierta entre entidades capaces de realizar operaciones de entrada y salida de datos. Solamente se van a considerar canales de sockets, que son los nicos necesarios para implementar el chat aprovechando las ventajas de JDK 1.4. Dentro del nuevo paquete existen las clases java.nio.channels.SocketChannel y java.nio.channels.ServerSocketChannel, que pueden considerarse como substitutas modernas de las clases java.net.Socket y java.net.ServerSocket, ya vistas en el apartado 2. Para realizar las operaciones habituales de lectura y escritura con sockets utilizando canales se debera escribir el siguiente cdigo: //Se obtiene una direccin del socket proporcionando el host y el puerto //o solamente el puerto InetSocketAddress direc_socket= new InetSocketAddress(www.aidima.es, 9000); //Se abre un objeto SocketChannel y se conecta al objeto InetSocketAddres

SocketChannel canal = SocketChannel.open(); canal.connect(direc_socket); //Se lee o se escribe en el canal canal.write(....); canal.read(... ); La clase java.nio.channels.SocketChannel, al igual que la clase java.nio.channels.ServerSocketChannel, tiene un mtodo configureBlocking() que permite hacer al canal (y a los sockets asociados, en este caso) de tipo no bloqueado si se le pasa como argumento false. En un canal no bloqueado el programa o el proceso no se bloquear al intentar leer o escribir datos hasta que el proceso de lectura o escritura haya finalizado (como s sucede con el mtodo readLine() del objeto BufferedReader en el programa Servidor.java del apartado 3). Esto supone una gran ventaja en cuanto a mejora del rendimiento de las aplicaciones que hacen uso intensivo de operaciones de E/S y constituye una de las novedades ms significativas de JDK 1.4. Adems, para el caso del chat, evita el tener que asignar un hilo a cada cliente. Si un canal se establece mediante configureBlocking(true) a bloqueado, el comportamiento de los sockets asociados a dicho canal ser el mismo que el de lectura y escritura convencional en un objeto Socket (salvo pequeos matices, que pueden consultarse en la documentacin oficial de Sun) Un objeto Selector controla una serie de canales y lanza un aviso cuando uno de ellos recibe datos. La nueva clase Selector acta permitiendo que cada canal detecte las operaciones de E/S (o eventos de E/S; en este contexto operaciones y eventos son equivalentes). El mtodo esttico open() crea un nuevo Selector y el mtodo select() bloquea el programa hasta que algn canal recibe datos. El mtodo Selector.select() supone para el programador de Java una importante herramienta para aumentar la claridad y velocidad de su cdigo. El mtodo select() se bloquea hasta que uno o ms canales reciban alguna entrada. No es necesario, por tanto, comprobar proceso a proceso como haba que hacer antes de JDK 1.4- si se han recibido entradas de los clientes. Con select() se espera simultneamente cualquier entrada en todos los clientes. Para saber qu eventos E/S de los que estamos interesados se producen en un determinado canal es necesario registrar el canal con el Selector y especificar el tipo o los tipos de eventos que se desea detectar. Esto se realiza mediante el mtodo register() del canal. Siguiendo con el cdigo anterior: //Se obtiene una direccin del socket proporcionando el host y el puerto //o solamente el puerto (9000 en este ejemplo) InetSocketAddress direc_socket= new InetSocketAddress(www.aidima.es, 9000); //Se abre un objeto SocketChannel y se conecta al objeto InetSocketAddress SocketChannel canal = SocketChannel.open(); canal.configureBlocking(false); canal.connect(direc_socket); //Se crea un objeto Selector Selector mi_selector = Selector.open(); //Se registra el canal con mi_selector para que est al tanto de operaciones //de conexiones, lectura y escritura canal.register(mi_selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ |SelectionKey.OP_WRITE);

Los eventos de E/S que puede escuchar un objeto Selector son: a) OP_READ. Este evento se lanza cuando el canal puede leer datos. b) OP_WRITE. Este evento se lanza cuando es posible escribir en un canal. c) OP_ACCEPT. Este evento se lanza cuando llegan conexiones entrantes al socket del servidor. d) OP_CONNECT. Este evento se lanza cuando un socket cliente est preparado para conectarse con un servidor. El mtodo SelectedKeys() proporciona un conjunto (Set) de claves o identificadores correspondientes a los canales que han recibido operaciones de E/S. Cuando uno o ms eventos han sido lanzados por cualquiera de los canales registrados, se puede acceder al conjunto de claves de los canales que han lanzado eventos as: Set claves=selector.selectedKeys(); Se puede recorrer el conjunto claves y procesar cada evento con el siguiente cdigo: Iterator mi_iterador =claves.iterator(); while (mi_iterador.hasNext()){ SelectionKey clave=(SelectionKey)iterador.next(); if (clave.isAcceptable()) { //Cdigo de procesado} if (clave.isReadable()) { //Cdigo de procesado} if (clave.isWritable()) { //Cdigo de procesado} mi_iterador.remove(clave);

Cada objeto SelectionKey representa la clave o identificador de un canal que ha recibido un evento de E/S. Los mtodos isAcceptable(), isReadable() e isWritable() nos dicen qu tipo de evento se ha producido (conexin entrante, recepcin de datos o envo de datos) en el canal seleccionado. Es necesario usar el mtodo remove() tras procesar un canal, puesto que si no hiciera as el evento ya procesado volvera a ser lanzado y tratado- cuando se produjera cualquier otro evento. Retornando al ejemplo concreto del servidor chat, para crear un objeto ServerSocket usando canales y selectores se utiliza un ServerSocketChannel primero y luego se llama a su mtodo socket(). ServerSocketChannel canalss=ServerSocketChannel.open(); canalss.configureBlocking(false); ServerSocket sservidor =canalss.socket(); //Vncula el socket del servidor al puerto 9000 sservidor.bind(new InetSocketAddress(9000)) El corazn del programa ServidorChat.java aprovechando las novedades de JDK 1.4 sera similar a esto:

import java.io.*; import java.nio.*, import java.net.*; import java.nio.channels.*; //Cdigo solo vlido en JDK 1.4 //Se han utilizado los mtodos, ejemplos y lneas de cdigo expuestas //a lo largo del apartado 5 public class ServidorChatJDK14 extends Thread { private Vector clientesActivos=new Vector(); public ServidorChatJDK14(){ System.out.println(Arrancando servidor); } public void run(){ try{ ServerSocketChannel canalss=ServerSocketChannel.open(); canalss.configureBlocking(false); ServerSocket sservidor =canalss.socket(); //El socket servidor escucha peticiones por el puerto 9000 sservidor.bind(new InetSocketAddress(9000)); Selector miselector=Selector.open(); canalss.register(miselector, SelectionKey.OP_ACCEPT); while(true){ //Si ocurre algn evento if (miselector.select()>0){ Set claves=miselector.selectedKeys(); Iterator iterador=claves.iterator(); //Se recorren los canales que han registrado eventos while(iterador.hasNext()){ //Cada objeto clave representa un canal que //tenido un evento SelectionKey clave=(SelectionKey)iterador.next(); if (clave.isAcceptable()) { //Estamos ante una conexin de un //cliente: el socket del servidor ha aceptado la conexin //Se acepta la peticin del cliente Socket socket=sservidor.accept(); clientesActivos.addElement(socket); SocketChannel canalsocket=socket.getChannel(); canalsocket.configureBlocking(false); canalsocket.register(miselector, SelectionKey.OP_READ); miselector.selectedKeys().remove(clave); } if (clave.isReadable()) { //Estamos ante un recepcin de datos por parte // de un cliente SocketChannel canalsocketlectura= (SocketChannel)clave.channel(); miselector.selectedKeys().remove(clave); String mensaje=leerMensaje(canalsocketlectura);

} } } } }

if (mensaje.length()>0) enviarMensajesATodos(mensaje);

} catch(IOException e){System.out.println(Excepcin: +e);}

El mtodo enviarMensajeATodos(String mensaje) imprimira el mensaje en los terminales de todos los clientes, de modo similar al mtodo escribir() de ServidorChat.java. Para evitar los bloqueos que imponen el mtodo readLine() utilizado hasta ahora, sera preciso elegir clases de entrada y salida del nuevo paquete java.nio para implementar el mtodo enviarMensajeATodos(String mensaje). Como una explicacin en detalle, e incluso somera, de estas nuevas clases de E/S caen fuera del alcance de este artculo, se deja la implementacin de este mtodo en manos del lector. De todos modos, se recomienda utilizar la nueva clase ByteBuffer para leer o escribir datos en el SocketChannel que se obtendr del objeto clave; aunque sta no es la nica solucin posible. Los clientes se conectaran al chat con cdigo similar a este: cliente = SocketChannel.open(); dir_socket = new InetSocketAddress("www.aidima.es",9000); cliente.connect(dir_socket); cliente.configureBlocking(false); enviarMensaje(mensaje); La estructura del programa servidor chat en JDK 1.4 (y en general de cualquier programa servidor aprovechando las nuevas caractersticas de JDK 1.4) obedece los siguientes pasos: a) Se crea un objeto ServerSocketChannel que se usar para escuchar las peticiones de los clientes. b) Se registra el anterior objeto ServerSocketChannel con el Selector apropiado. c) El mtodo select() de Selector aguarda hasta que se producen eventos E/S en algunos de los canales registrados. d) Se usa el mtodo SelectedKeys() de Selector para averiguar qu canales han tenido eventos E/S. Este mtodo devuelve un Set (conjunto) de SelectedKeys que pueden ser inspeccionados para acceder al canal asociado a cada uno. e) El mtodo accept() de ServerSocketChannel acepta las conexiones entrantes y genera objetos del tipo SocketChannel. f) Se registran los SocketChanels que provienen del mtodo accept() con el Selector, tal y como se hizo en b) con ServerSocketChannel. Cada SocketChannel generado se encarga del intercambio de datos con un cliente mientras que el ServerSocketChannel contina esperando nuevas conexiones. g) Se intercambian datos (lectura/escritura) con los clientes mediante SocketChannel y las clases E/S del paquete java.nio.

Potrebbero piacerti anche