Sunteți pe pagina 1din 28

[cdigo] Conexiones Cliente-Servidor mediante sockets en Java

3tweets retweet Por falta de tiempo me es imposible explicar este cdigo detalladamente, tal como me gustara, pero no tengo otra opcin que simplemente ponerlo. An as es de recordar que pueden dejar todos los comentarios que deseen, con sus dudas y sugerencias (que no pedidos de tareas). An as explico un poco el resultado del programa el programa es un chat simple (muy simple), que usa Sockets para crear conexiones de red. Consta de un servidor (Servidor.java) y un cliente (Cliente.java), mediante los cuales se puede establecer una conversacin. Si se escribe TERMINAR (sin comillas), la conexin se cierra.

El resultado

El cdigo
El cdigo fuente de Servidor.java: 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027
import import import import import java.io.*; java.net.*; java.awt.*; java.awt.event.*; javax.swing.*;

public class Servidor extends JFrame { private JTextField campoIntroducir; private JTextArea areaPantalla; private ObjectOutputStream salida; private ObjectInputStream entrada; private ServerSocket servidor; private Socket conexion; private int contador = 1; // configurar GUI public Servidor() { super( "Servidor" ); Container contenedor = getContentPane(); // crear campoIntroducir y registrar componente de escucha campoIntroducir = new JTextField(); campoIntroducir.setEditable( false ); campoIntroducir.addActionListener( new ActionListener() {

028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074

// enviar mensaje al cliente public void actionPerformed( ActionEvent evento ) { enviarDatos( evento.getActionCommand() ); campoIntroducir.setText( "" ); } } ); contenedor.add( campoIntroducir, BorderLayout.NORTH ); // crear areaPantalla areaPantalla = new JTextArea(); contenedor.add( new JScrollPane( areaPantalla ), BorderLayout.CENTER ); setSize( 300, 150 ); setVisible( true ); } // fin del constructor de Servidor // configurar y ejecutar el servidor public void ejecutarServidor() { // configurar servidor para que reciba conexiones; procesar las conexiones try { // Paso 1: crear un objeto ServerSocket. servidor = new ServerSocket( 12345, 100 ); while ( true ) {

try { esperarConexion(); // Paso 2: esperar una conexin. obtenerFlujos(); // Paso 3: obtener flujos de entrada y sal procesarConexion(); // Paso 4: procesar la conexin. }

// procesar excepcin EOFException cuando el cliente cierre la conexi catch ( EOFException excepcionEOF ) { System.err.println( "El servidor termin la conexin" ); } finally { cerrarConexion(); ++contador; }

// Paso 5: cerrar la conexin.

075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121

} // fin de instruccin while } // fin del bloque try // procesar problemas con E/S catch ( IOException excepcionES ) { excepcionES.printStackTrace(); } } // fin del mtodo ejecutarServidor // esperar que la conexin llegue, despus mostrar informacin de la conexin private void esperarConexion() throws IOException { mostrarMensaje( "Esperando una conexin\n" ); conexion = servidor.accept(); // permitir al servidor aceptar la conexin mostrarMensaje( "Conexin " + contador + " recibida de: " + conexion.getInetAddress().getHostName() ); }

// obtener flujos para enviar y recibir datos private void obtenerFlujos() throws IOException { // establecer flujo de salida para los objetos salida = new ObjectOutputStream( conexion.getOutputStream() ); salida.flush(); // vaciar bfer de salida para enviar informacin de encabe // establecer flujo de entrada para los objetos entrada = new ObjectInputStream( conexion.getInputStream() ); mostrarMensaje( "\nSe recibieron los flujos de E/S\n" ); } // procesar la conexin con el cliente private void procesarConexion() throws IOException { // enviar mensaje de conexin exitosa al cliente String mensaje = "Conexin exitosa"; enviarDatos( mensaje ); // habilitar campoIntroducir para que el usuario del servidor pueda enviar establecerCampoTextoEditable( true ); do { // procesar los mensajes enviados por el cliente // leer el mensaje y mostrarlo en pantalla try { mensaje = ( String ) entrada.readObject();

122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168

mostrarMensaje( "\n" + mensaje ); } // atrapar problemas que pueden ocurrir al tratar de leer del cliente catch ( ClassNotFoundException excepcionClaseNoEncontrada ) { mostrarMensaje( "\nSe recibi un tipo de objeto desconocido" ); } } while ( !mensaje.equals( "CLIENTE>>> TERMINAR" ) ); } // fin del mtodo procesarConexion // cerrar flujos y socket private void cerrarConexion() { mostrarMensaje( "\nFinalizando la conexin\n" ); establecerCampoTextoEditable( false ); // deshabilitar campoIntroducir try { salida.close(); entrada.close(); conexion.close(); } catch( IOException excepcionES ) { excepcionES.printStackTrace(); } } // enviar mensaje al cliente private void enviarDatos( String mensaje ) { // enviar objeto al cliente try { salida.writeObject( "SERVIDOR>>> " + mensaje ); salida.flush(); mostrarMensaje( "\nSERVIDOR>>> " + mensaje ); } // procesar problemas que pueden ocurrir al enviar el objeto catch ( IOException excepcionES ) { areaPantalla.append( "\nError al escribir objeto" ); } } // mtodo utilitario que es llamado desde otros subprocesos para manipular a // areaPantalla en el subproceso despachador de eventos private void mostrarMensaje( final String mensajeAMostrar ) { // mostrar mensaje del subproceso de ejecucin despachador de eventos

169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186

SwingUtilities.invokeLater( new Runnable() { // clase interna para asegurar que la GUI se actualice public void run() // actualiza areaPantalla { areaPantalla.append( mensajeAMostrar ); areaPantalla.setCaretPosition( areaPantalla.getText().length() ); } } // fin de la clase interna

); // fin de la llamada a SwingUtilities.invokeLater } // mtodo utilitario que es llamado desde otros subprocesos para manipular a // campoIntroducir en el subproceso despachador de eventos private void establecerCampoTextoEditable( final boolean editable ) { // mostrar mensaje del subproceso de ejecucin despachador de eventos SwingUtilities.invokeLater( new Runnable() { // clase interna para asegurar que la GUI se actualice

public void run() // establece la capacidad de modificar a campoIntro { campoIntroducir.setEditable( editable ); } } // fin de la clase interna

); // fin de la llamada a SwingUtilities.invokeLater } public static void main( String args[] ) { JFrame.setDefaultLookAndFeelDecorated(true); Servidor aplicacion = new Servidor(); aplicacion.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); aplicacion.ejecutarServidor(); } } // fin de la clase Servidor

El cdigo fuente de Cliente.java:


?

001

import java.io.*;

002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048

import import import import

java.net.*; java.awt.*; java.awt.event.*; javax.swing.*;

public class Cliente extends JFrame { private JTextField campoIntroducir; private JTextArea areaPantalla; private ObjectOutputStream salida; private ObjectInputStream entrada; private String mensaje = ""; private String servidorChat; private Socket cliente; // inicializar servidorChat y configurar GUI public Cliente( String host ) { super( "Cliente" );

servidorChat = host; // establecer el servidor al que se va a conectar este Container contenedor = getContentPane(); // crear campoIntroducir y registrar componente de escucha campoIntroducir = new JTextField(); campoIntroducir.setEditable( false ); campoIntroducir.addActionListener( new ActionListener() { // enviar mensaje al servidor public void actionPerformed( ActionEvent evento ) { enviarDatos( evento.getActionCommand() ); campoIntroducir.setText( "" ); } } ); contenedor.add( campoIntroducir, BorderLayout.NORTH ); // crear areaPantalla areaPantalla = new JTextArea(); contenedor.add( new JScrollPane( areaPantalla ), BorderLayout.CENTER ); setSize( 300, 150 ); setVisible( true ); } // fin del constructor de Cliente

049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095

// conectarse al servidor y procesar mensajes del servidor private void ejecutarCliente() { // conectarse al servidor, obtener flujos, procesar la conexin try { conectarAServidor(); // Paso 1: crear un socket para realizar la conexi obtenerFlujos(); // Paso 2: obtener los flujos de entrada y salida procesarConexion(); // Paso 3: procesar la conexin } // el servidor cerr la conexin catch ( EOFException excepcionEOF ) { System.err.println( "El cliente termino la conexin" ); }

// procesar los problemas que pueden ocurrir al comunicarse con el servidor catch ( IOException excepcionES ) { excepcionES.printStackTrace(); } finally { cerrarConexion(); // Paso 4: cerrar la conexin } } // fin del mtodo ejecutarCliente // conectarse al servidor private void conectarAServidor() throws IOException { mostrarMensaje( "Intentando realizar conexin\n" ); // crear Socket para realizar la conexin con el servidor cliente = new Socket( InetAddress.getByName( servidorChat ), 12345 ); // mostrar la informacin de la conexin mostrarMensaje( "Conectado a: " + cliente.getInetAddress().getHostName() ); }

// obtener flujos para enviar y recibir datos private void obtenerFlujos() throws IOException { // establecer flujo de salida para los objetos salida = new ObjectOutputStream( cliente.getOutputStream() ); salida.flush(); // vacar bfer de salida para enviar informacin de encabe // establecer flujo de entrada para los objetos entrada = new ObjectInputStream( cliente.getInputStream() );

096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142

mostrarMensaje( "\nSe recibieron los flujos de E/S\n" ); }

// procesar la conexin con el servidor private void procesarConexion() throws IOException { // habilitar campoIntroducir para que el usuario del cliente pueda enviar m establecerCampoTextoEditable( true ); do { // procesar mensajes enviados del servidor // leer mensaje y mostrarlo en pantalla try { mensaje = ( String ) entrada.readObject(); mostrarMensaje( "\n" + mensaje ); } // atrapar los problemas que pueden ocurrir al leer del servidor catch ( ClassNotFoundException excepcionClaseNoEncontrada ) { mostrarMensaje( "\nSe recibi un objeto de tipo desconocido" ); } } while ( !mensaje.equals( "SERVIDOR>>> TERMINAR" ) ); } // fin del mtodo procesarConexion // cerrar flujos y socket private void cerrarConexion() { mostrarMensaje( "\nCerrando conexin" ); establecerCampoTextoEditable( false ); // deshabilitar campoIntroducir try { salida.close(); entrada.close(); cliente.close(); } catch( IOException excepcionES ) { excepcionES.printStackTrace(); } } // enviar mensaje al servidor private void enviarDatos( String mensaje ) { // enviar objeto al servidor try { salida.writeObject( "CLIENTE>>> " + mensaje );

143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182

salida.flush(); mostrarMensaje( "\nCLIENTE>>> " + mensaje ); } // procesar los problemas que pueden ocurrir al enviar el objeto catch ( IOException excepcionES ) { areaPantalla.append( "\nError al escribir el objeto" ); } } // mtodo utilitario que es llamado desde otros subprocesos para manipular a // areaPantalla en el subproceso despachador de eventos private void mostrarMensaje( final String mensajeAMostrar ) { // mostrar mensaje del subproceso de ejecucin de la GUI SwingUtilities.invokeLater( new Runnable() { // clase interna para asegurar que la GUI se actualice public void run() // actualiza areaPantalla { areaPantalla.append( mensajeAMostrar ); areaPantalla.setCaretPosition( areaPantalla.getText().length() ); } } // fin de la clase interna

); // fin de la llamada a SwingUtilities.invokeLater } // mtodo utilitario que es llamado desde otros subprocesos para manipular a // campoIntroducir en el subproceso despachador de eventos private void establecerCampoTextoEditable( final boolean editable ) { // mostrar mensaje del subproceso de ejecucin de la GUI SwingUtilities.invokeLater( new Runnable() { // clase interna para asegurar que la GUI se actualice

public void run() // establece la capacidad de modificar campoIntrodu { campoIntroducir.setEditable( editable ); } } // fin de la clase interna

); // fin de la llamada a SwingUtilities.invokeLater } public static void main( String args[] )

{ JFrame.setDefaultLookAndFeelDecorated(true); Cliente aplicacion; if ( args.length == 0 ) aplicacion = new Cliente( "127.0.0.1" ); else aplicacion = new Cliente( args[ 0 ] ); aplicacion.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); aplicacion.ejecutarCliente(); } } // fin de la clase Cliente

Este es el codigo para el servidor: ================================= import java.io.EOFException; import java.io.IOException; import java.io.ObjectInputStream;

import java.io.ObjectOutputStream; import java.net.ServerSocket; import java.net.Socket; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.SwingUtilities; public class Servidor extends JFrame{ private JTextField campoEscritura; private JTextArea areaCharla; private ObjectOutputStream salida; private ObjectInputStream entrada; private ServerSocket servicio; private Socket conexion; private int contador; //Configuracion de GUI public Servidor(){ super("Servidor"); campoEscritura = new JTextField(); campoEscritura.setEditable(false); campoEscritura.addActionListener( new ActionListener(){ public void actionPerformed(ActionEvent e){ enviarDatos(e.getActionCommand());

campoEscritura.setText(""); } } ); add(campoEscritura, BorderLayout.SOUTH); areaCharla = new JTextArea(); add(new JScrollPane(areaCharla), BorderLayout.CENTER); setSize(300,150); setVisible(true); } public void iniciarServidor(){ try{ servicio = new ServerSocket(12345, 100); while(true){ try{ esperarConexion(); getFlujos(); procesarConexion(); }catch(EOFException eof){ mostrarMensaje("Servidor termino la conexion"); }finally{ cerrarConexion(); contador++; } } }catch(IOException io){

io.printStackTrace(); } } private void esperarConexion() throws IOException{ mostrarMensaje("Esperando Conexiones"); conexion = servicio.accept(); mostrarMensaje("Coneccin "+contador+" recibida de: "+ conexion.getInetAddress().getHostName()); } private void getFlujos() throws IOException{ salida = new ObjectOutputStream(conexion.getOutputStream()); salida.flush(); entrada = new ObjectInputStream(conexion.getInputStream()); mostrarMensaje("n Corren los Flujos de E/S n"); } private void procesarConexion() throws IOException{ String mensaje = "Conexion Establecida"; enviarDatos(mensaje); setTextFieldEditable(true); do{ try{ mensaje = (String) entrada.readObject(); mostrarMensaje("n"+mensaje); }catch(ClassNotFoundException cnfe){ mostrarMensaje("n Tipo de Objeto desconocido fue recibido");

} } while(!mensaje.equals("CLIENT>>> FINITO")); } private void cerrarConexion(){ mostrarMensaje("nConexin cerradan"); setTextFieldEditable(false); try{ salida.close(); entrada.close(); conexion.close(); } catch(IOException io){ io.printStackTrace(); } } private void enviarDatos(String mensaje){ try{ salida.writeObject("nSERVER>>> "+mensaje); salida.flush(); mostrarMensaje("nSERVER>>> "+ mensaje); } catch(IOException io){ areaCharla.append("nError al escribir el objeto"); } } private void mostrarMensaje(final String mensajeAmostrar){ SwingUtilities.invokeLater( new Runnable(){

public void run(){ areaCharla.append(mensajeAmostrar); } } ); } private void setTextFieldEditable(final boolean editable){ SwingUtilities.invokeLater( new Runnable(){ public void run(){ campoEscritura.setEditable(editable); } } ); } }

este el codigo para probar el Servidor: ======================================== import javax.swing.JFrame; public class PruebaServidor{ public static void main( String args[] ){ Servidor servidor = new Servidor(); // create server servidor.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

servidor.iniciarServidor(); // run server application } // end main }

Este el codigo para un Cliente para el chat =========================================== import java.io.EOFException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.InetAddress; import java.net.Socket; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.SwingUtilities; public class Cliente extends JFrame{ private JTextField campoTexto; private JTextArea areaCharla; private ObjectOutputStream output; private ObjectInputStream input; private String mensaje = "";

private String ipServidor; private Socket cliente; public Cliente( String host ){ super( "Cliente" ); ipServidor = host; campoTexto = new JTextField(); campoTexto.setEditable( false ); campoTexto.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent event ){ enviarDatos( event.getActionCommand() ); campoTexto.setText( "" ); } } ); add( campoTexto, BorderLayout.SOUTH ); areaCharla = new JTextArea(); add( new JScrollPane( areaCharla ), BorderLayout.CENTER ); setSize( 300, 150 ); setVisible( true ); } public void correrCliente(){ try{ conectarAservidor(); conseguirFlujo(); procesarConexion();

}catch ( EOFException eofException ) { mostrarMensaje( "nClient terminated connection" ); }catch ( IOException ioException ){ ioException.printStackTrace(); }finally { cerrarConexion(); } } private void conectarAservidor() throws IOException{ mostrarMensaje( "Esperando conectarse" ); cliente = new Socket( InetAddress.getByName( ipServidor ), 12346 ); mostrarMensaje( "Connected to: " + cliente.getInetAddress().getHostName() ); } private void conseguirFlujo() throws IOException { output = new ObjectOutputStream( cliente.getOutputStream() ); output.flush(); input = new ObjectInputStream( cliente.getInputStream() ); mostrarMensaje( "nExiste conexion con Servidorn" ); } private void procesarConexion() throws IOException{ setTextFieldEditable( true ); do{ try{ mensaje = ( String ) input.readObject(); mostrarMensaje( "n" + mensaje ); }catch ( ClassNotFoundException classNotFoundException ){

mostrarMensaje( "nUnknown object type received" ); } } while ( !mensaje.equals( "SERVER>>> FINITO" ) ); } private void cerrarConexion(){ mostrarMensaje( "nCerrando la conexion" ); setTextFieldEditable( false ); try{ output.close(); input.close(); cliente.close(); } catch ( IOException ioException ){ ioException.printStackTrace(); } } private void enviarDatos( String sms ) { try{ output.writeObject( "CLIENTE>>> " + sms ); output.flush(); mostrarMensaje( "nCLIENTE>>> " + sms ); } catch ( IOException ioException ) { areaCharla.append( "nNo se puede enviar el mensaje" );

} } private void mostrarMensaje( final String mensajeToDisplay ) { //SwingUtilities.invokeLater( // new Runnable() //{ // public void run() //{ areaCharla.append( mensajeToDisplay ); //} //} //); } private void setTextFieldEditable( final boolean editable ) { SwingUtilities.invokeLater( new Runnable() { public void run() // sets campoTextos editability { campoTexto.setEditable( editable ); } } ); } }

Este el codigo para ejecutar Cliente, recuerda que se hace uso de tu servidor interno LOCALHOST 127.0.0.1 =========================================== import javax.swing.JFrame; public class ClientTest { public static void main( String args[] ) { Cliente cliente; cliente = new Cliente( "127.0.0.1" ); cliente.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); cliente.correrCliente(); } }

Para probar esto, es importante que primero ejecutes el Servidor, esto se debe a que se esta usando TCP,

OTROOO

Sockets java. Atender varios clientes con hilos. Un chat de ejemplo. En este tutorial vamos a ver cmo un servidor de socket puede atender a la vez a varios clientes creando un hilo con cada uno de ellos. Como la idea es simple y se explica en dos patadas, haremos un ejemplo algo ms complejo: un chat capaz de atender varios clientes a la vez. El cdigo del chat puedes bajrtelo y probarlo al final. Tampoco es un chat muy elaborado, puesto que slo se pretende dar una idea de cmo atender a varios clientes con hilos. Tampoco me he preocupado de hacerlo robusto, as que si le das caa es posible que tengas algn problema

LA IDEA BSICA PARA EL SERVIDOR Como he comentado arriba, la idea es muy sencilla. Basta con hacerse una clase susceptible de ser lanzada en un hilo aparte para atender a cada cliente. El servidornicamente debe meterse en un bucle infinito a la espera de un cliente y cuando llegue, instanciar una clase de estas y lanzar el hilo. La clase para el hilo encargada de atender a cada cliente puede ser algo parecido a esto HiloDeCliente.java // Implementa Runnable para poder ser lanzada en un hilo aparte public class HiloDeCliente implements Runnable { // En el constructor recibe y guarda los parmetros que sean necesarios. // En este caso una lista con toda la conversacin y el socket que debe // atender. public HiloDeCliente(DefaultListModel charla, Socket socket) { ... }

public void run () { while (true) { // Cdigo para atender al cliente. } } } Una vez hecho esto, el servidor simplemente tiene que meterse en un bucle infinito para aceptar conexiones y crear una de estas clases en un hilo aparte cada vez que se conecte un cliente nuevo. ServidorChat.java public class ServidorChat { // Para guardar toda la conversacin. private DefaultListModel charla = new DefaultListModel(); public ServidorChat() { // Se crea el socket servidor ServerSocket socketServidor = new ServerSocket(5557); // Bucle infinito while (true) { // Se espera y acepta un nuevo cliente Socket cliente = socketServidor.accept(); // Se instancia una clase para atender al cliente y se lanza en // un hilo aparte.

Runnable nuevoCliente = new HiloDeCliente(charla, cliente); Thread hilo = new Thread(nuevoCliente); hilo.start(); } } } ALGUNOS DETALLES DEL CDIGO DE EJEMPLO EN EL SERVIDOR He decidido meter toda la charla en un DefaultListModel. Esta es una clase java que no me gusta mucho, pero la uso para no tener que hacerme una y liar ms el ejemplo. La clase DefaultListModel implementa un patrn observador. Con esto quiero decir que es posible "suscribirnos" a esa lista, de forma que cuando cualquiera cambie cualquier cosa, podemos enterarnos y actuar en consecuencia. Cada clase HiloDeCliente recibe este DefaultListModel. Cuando llegue un texto del cliente, lo mete en esta clase, aadindolo al final. Como todos los HiloDeCliente que haya en ese momento estarn suscritos a este DefaultListModel, incluido el que lo ha aadido, todos se enterarn del aadido y en consecuencia todos enviarn el nuevo texto a su cliente. HiloDeCliente.java // Implementa tambin ListDataListener para poder suscribirse a cambios // en DefaultListModel public class HiloDeCliente implements Runnable, ListDataListener { public HiloDeCliente(DefaultListModel charla, Socket socket) { ... // Se suscribe a los cambios charla.addListDataListener(this); } // Trata el cambio de aadido algo al DefaultListModel. public void intervalAdded(ListDataEvent e)

{ // Obtiene el texto aadido String texto = (String) charla.getElementAt(e.getIndex0()); // y lo enva por el socket. dataOutput.writeUTF(texto); } } Usar este patrn observador es una forma elegante orientada a objetos de evitar que haya alguien que sepa todos los hilos que hay en marcha y tenga que avisarlos uno a uno cada vez que se aade texto. Nos evita tambin el tener un array o lista con todos los hilos e ir actualizndolo cada vez que se conecta o desconecta un nuevo hilo. Vamos ahora con detalles del cliente.

S-ar putea să vă placă și