Chat Java con Eclipse



Comments



Description

[código] Conexiones Cliente-Servidor mediante sockets en Java3tweets retweet Por falta de tiempo me es imposible explicar este código detalladamente, tal como me gustaría, pero no tengo otra opción que simplemente ponerlo. Aún así es de recordar que pueden dejar todos los comentarios que deseen, con sus dudas y sugerencias (que no pedidos de tareas). Aún 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 conversación. Si se escribe “TERMINAR” (sin comillas), la conexión se cierra. El resultado El código El código 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() { err. contenedor.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. } // Paso 5: cerrar la conexión. BorderLayout. while ( true ) { try { esperarConexion(). // Paso 4: procesar la conexión.getActionCommand() ). // Paso 3: obtener flujos de entrada y sal procesarConexion().add( campoIntroducir. 100 ).println( "El servidor terminó la conexión" ). ++contador. } finally { cerrarConexion(). servidor = new ServerSocket( 12345. } } ).CENTER ). // Paso 2: esperar una conexión.add( new JScrollPane( areaPantalla ). // crear areaPantalla areaPantalla = new JTextArea().setText( "" ). contenedor. BorderLayout. } // procesar excepción EOFException cuando el cliente cierre la conexi catch ( EOFException excepcionEOF ) { System. setSize( 300. . obtenerFlujos(). } // fin del constructor de Servidor // configurar y ejecutar el servidor public void ejecutarServidor() { // configurar servidor para que reciba conexiones.NORTH ). procesar las conexiones try { // Paso 1: crear un objeto ServerSocket. setVisible( true ). 150 ). campoIntroducir. 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 instrucción while } // fin del bloque try // procesar problemas con E/S catch ( IOException excepcionES ) { excepcionES.printStackTrace(). . mostrarMensaje( "\nSe recibieron los flujos de E/S\n" ). salida. } // procesar la conexión con el cliente private void procesarConexion() throws IOException { // enviar mensaje de conexión exitosa al cliente String mensaje = "Conexión exitosa".accept(). enviarDatos( mensaje ). // permitir al servidor aceptar la conexión mostrarMensaje( "Conexión " + contador + " recibida de: " + conexion.flush(). } } // fin del método ejecutarServidor // esperar que la conexión llegue.getHostName() ). conexion = servidor.readObject().getInputStream() ). // habilitar campoIntroducir para que el usuario del servidor pueda enviar establecerCampoTextoEditable( true ).getOutputStream() ). // vaciar búfer de salida para enviar información de encabe // establecer flujo de entrada para los objetos entrada = new ObjectInputStream( conexion. } // obtener flujos para enviar y recibir datos private void obtenerFlujos() throws IOException { // establecer flujo de salida para los objetos salida = new ObjectOutputStream( conexion. do { // procesar los mensajes enviados por el cliente // leer el mensaje y mostrarlo en pantalla try { mensaje = ( String ) entrada.getInetAddress(). después mostrar información de la conexión private void esperarConexion() throws IOException { mostrarMensaje( "Esperando una conexión\n" ). establecerCampoTextoEditable( false ). } } // enviar mensaje al cliente private void enviarDatos( String mensaje ) { // enviar objeto al cliente try { salida. // deshabilitar campoIntroducir try { salida.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 ).flush(). } } while ( !mensaje.close().append( "\nError al escribir objeto" ). } } // método 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 ejecución despachador de eventos . } // fin del método procesarConexion // cerrar flujos y socket private void cerrarConexion() { mostrarMensaje( "\nFinalizando la conexión\n" ). conexion.writeObject( "SERVIDOR>>> " + mensaje ).equals( "CLIENTE>>> TERMINAR" ) ). mostrarMensaje( "\nSERVIDOR>>> " + mensaje ). entrada. } // procesar problemas que pueden ocurrir al enviar el objeto catch ( IOException excepcionES ) { areaPantalla. } // atrapar problemas que pueden ocurrir al tratar de leer del cliente catch ( ClassNotFoundException excepcionClaseNoEncontrada ) { mostrarMensaje( "\nSe recibió un tipo de objeto desconocido" ). salida.printStackTrace(). } catch( IOException excepcionES ) { excepcionES.close().close(). 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 SwingUtilities.EXIT_ON_CLOSE ). aplicacion. } } // fin de la clase Servidor El código fuente de Cliente.ejecutarServidor(). } } // fin de la clase interna ).setDefaultCloseOperation( JFrame. aplicacion.invokeLater( new Runnable() { // clase interna para asegurar que la GUI se actualice public void run() // actualiza areaPantalla { areaPantalla. // fin de la llamada a SwingUtilities.io.invokeLater } public static void main( String args[] ) { JFrame.append( mensajeAMostrar ).setDefaultLookAndFeelDecorated(true).*.length() ).invokeLater( new Runnable() { // clase interna para asegurar que la GUI se actualice public void run() // establece la capacidad de modificar a campoIntro { campoIntroducir. Servidor aplicacion = new Servidor().invokeLater } // método 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 ejecución despachador de eventos SwingUtilities. // fin de la llamada a SwingUtilities.setCaretPosition( areaPantalla.java: ? 001 import java.getText(). } } // fin de la clase interna ). areaPantalla. .setEditable( editable ). campoIntroducir.add( campoIntroducir. private String servidorChat. private String mensaje = "". // crear campoIntroducir y registrar componente de escucha campoIntroducir = new JTextField(). } // fin del constructor de Cliente . contenedor.*. public class Cliente extends JFrame { private JTextField campoIntroducir. servidorChat = host. campoIntroducir.*.*. BorderLayout.CENTER ). // crear areaPantalla areaPantalla = new JTextArea().swing. java. 150 ).net.setEditable( false ).event.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. java.awt. } } ).add( new JScrollPane( areaPantalla ).addActionListener( new ActionListener() { // enviar mensaje al servidor public void actionPerformed( ActionEvent evento ) { enviarDatos( evento. // inicializar servidorChat y configurar GUI public Cliente( String host ) { super( "Cliente" ).*.setText( "" ). private ObjectInputStream entrada. javax. contenedor.awt. campoIntroducir. BorderLayout. setVisible( true ).getActionCommand() ). // establecer el servidor al que se va a conectar este Container contenedor = getContentPane(). private Socket cliente.NORTH ). private ObjectOutputStream salida. private JTextArea areaPantalla. setSize( 300. // Paso 4: cerrar la conexión } } // fin del método ejecutarCliente // conectarse al servidor private void conectarAServidor() throws IOException { mostrarMensaje( "Intentando realizar conexión\n" ). // crear Socket para realizar la conexión con el servidor cliente = new Socket( InetAddress. } // procesar los problemas que pueden ocurrir al comunicarse con el servidor catch ( IOException excepcionES ) { excepcionES.err. .getByName( servidorChat ). } finally { cerrarConexion(). // Paso 3: procesar la conexión } // el servidor cerró la conexión catch ( EOFException excepcionEOF ) { System. salida. // Paso 2: obtener los flujos de entrada y salida procesarConexion().flush().getHostName() ). // vacíar búfer de salida para enviar información de encabe // establecer flujo de entrada para los objetos entrada = new ObjectInputStream( cliente.getInputStream() ). obtener flujos. procesar la conexión try { conectarAServidor().getInetAddress().printStackTrace(). } // obtener flujos para enviar y recibir datos private void obtenerFlujos() throws IOException { // establecer flujo de salida para los objetos salida = new ObjectOutputStream( cliente. 12345 ).println( "El cliente termino la conexión" ). // mostrar la información de la conexión mostrarMensaje( "Conectado a: " + cliente.getOutputStream() ).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. // Paso 1: crear un socket para realizar la conexió obtenerFlujos(). } // fin del método procesarConexion // cerrar flujos y socket private void cerrarConexion() { mostrarMensaje( "\nCerrando conexión" ). mostrarMensaje( "\n" + mensaje ).close().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" ). } } while ( !mensaje.close(). } // procesar la conexión con el servidor private void procesarConexion() throws IOException { // habilitar campoIntroducir para que el usuario del cliente pueda enviar m establecerCampoTextoEditable( true ). } } // enviar mensaje al servidor private void enviarDatos( String mensaje ) { // enviar objeto al servidor try { salida.close().equals( "SERVIDOR>>> TERMINAR" ) ). establecerCampoTextoEditable( false ). } catch( IOException excepcionES ) { excepcionES.printStackTrace().readObject().writeObject( "CLIENTE>>> " + mensaje ). // deshabilitar campoIntroducir try { salida. } // atrapar los problemas que pueden ocurrir al leer del servidor catch ( ClassNotFoundException excepcionClaseNoEncontrada ) { mostrarMensaje( "\nSe recibió un objeto de tipo desconocido" ). . cliente. do { // procesar mensajes enviados del servidor // leer mensaje y mostrarlo en pantalla try { mensaje = ( String ) entrada. entrada. length() ). } } // fin de la clase interna ). // fin de la llamada a SwingUtilities. } } // método 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 ejecución de la GUI SwingUtilities.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. } } // fin de la clase interna ).flush().invokeLater } // método 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 ejecución de la GUI SwingUtilities.invokeLater } public static void main( String args[] ) . mostrarMensaje( "\nCLIENTE>>> " + mensaje ).invokeLater( new Runnable() { // clase interna para asegurar que la GUI se actualice public void run() // actualiza areaPantalla { areaPantalla.append( "\nError al escribir el objeto" ). } // procesar los problemas que pueden ocurrir al enviar el objeto catch ( IOException excepcionES ) { areaPantalla. areaPantalla.setEditable( editable ).getText().invokeLater( new Runnable() { // clase interna para asegurar que la GUI se actualice public void run() // establece la capacidad de modificar campoIntrodu { campoIntroducir.append( mensajeAMostrar ). // fin de la llamada a SwingUtilities.setCaretPosition( areaPantalla. setDefaultLookAndFeelDecorated(true).setDefaultCloseOperation( JFrame. aplicacion. Cliente aplicacion.0. else aplicacion = new Cliente( args[ 0 ] ).ejecutarCliente(). } } // fin de la clase Cliente . if ( args. aplicacion.1" ).EXIT_ON_CLOSE ).0.length == 0 ) aplicacion = new Cliente( "127.{ JFrame. . ObjectInputStream. import java. import java. .io.EOFException.Este es el codigo para el servidor: ================================= import java.io.io.IOException. import javax.io.event. import javax.net. campoEscritura. private JTextArea areaCharla.awt. private ServerSocket servicio.net.ActionListener.swing.swing. private ObjectOutputStream salida.BorderLayout.JTextField. private int contador. import java. import javax.JScrollPane.Socket. public class Servidor extends JFrame{ private JTextField campoEscritura. import java.ActionEvent.JFrame.addActionListener( new ActionListener(){ public void actionPerformed(ActionEvent e){ enviarDatos(e.swing. import java.getActionCommand()). private ObjectInputStream entrada.setEditable(false).SwingUtilities.ServerSocket.event. import javax.awt. private Socket conexion. .swing.swing. import java. campoEscritura = new JTextField(). //Configuracion de GUI public Servidor(){ super("Servidor"). import javax.awt. campoEscritura. import java.ObjectOutputStream.import java.JTextArea. add(new JScrollPane(areaCharla). while(true){ try{ esperarConexion(). }finally{ cerrarConexion(). contador++. 100). getFlujos(). } } ). BorderLayout. } } }catch(IOException io){ .setText("").campoEscritura.SOUTH). }catch(EOFException eof){ mostrarMensaje("Servidor termino la conexion").150). add(campoEscritura. BorderLayout. } public void iniciarServidor(){ try{ servicio = new ServerSocket(12345. procesarConexion(). setSize(300.CENTER). areaCharla = new JTextArea(). setVisible(true). } private void getFlujos() throws IOException{ salida = new ObjectOutputStream(conexion. }catch(ClassNotFoundException cnfe){ mostrarMensaje("n Tipo de Objeto desconocido fue recibido"). } } private void esperarConexion() throws IOException{ mostrarMensaje("Esperando Conexiones"). enviarDatos(mensaje).getOutputStream()). } private void procesarConexion() throws IOException{ String mensaje = "Conexion Establecida". salida. conexion = servicio. mostrarMensaje("n"+mensaje). mostrarMensaje("n Corren los Flujos de E/S n").getInputStream()).accept().printStackTrace().io.readObject(). setTextFieldEditable(true).getInetAddress().flush().getHostName()). do{ try{ mensaje = (String) entrada. . entrada = new ObjectInputStream(conexion. mostrarMensaje("Conección "+contador+" recibida de: "+ conexion. } catch(IOException io){ areaCharla. } } private void mostrarMensaje(final String mensajeAmostrar){ SwingUtilities.equals("CLIENT>>> FINITO")).printStackTrace().append("nError al escribir el objeto"). } } private void enviarDatos(String mensaje){ try{ salida. } catch(IOException io){ io.close().} } while(!mensaje.flush(). salida.close(). entrada. mostrarMensaje("nSERVER>>> "+ mensaje).close(). conexion. } private void cerrarConexion(){ mostrarMensaje("nConexión cerradan").invokeLater( new Runnable(){ .writeObject("nSERVER>>> "+mensaje). setTextFieldEditable(false). try{ salida. // create server servidor. .EXIT_ON_CLOSE ). } private void setTextFieldEditable(final boolean editable){ SwingUtilities. public class PruebaServidor{ public static void main( String args[] ){ Servidor servidor = new Servidor(). } } ). } } ).setDefaultCloseOperation( JFrame.public void run(){ areaCharla. } } este el codigo para probar el Servidor: ======================================== import javax.setEditable(editable).append(mensajeAmostrar).swing.JFrame.invokeLater( new Runnable(){ public void run(){ campoEscritura. // run server application } // end main } Este el codigo para un Cliente para el chat =========================================== import java.io.JScrollPane.Socket. public class Cliente extends JFrame{ private JTextField campoTexto. import javax. .net. import java.ObjectInputStream.ActionListener.swing. import java.ObjectOutputStream.JFrame.net.event.swing.servidor. import javax.swing.awt.EOFException. import java. private JTextArea areaCharla.SwingUtilities. import java. import javax.swing. private String mensaje = "".io. import javax.io. import java.JTextArea.BorderLayout.IOException.awt.JTextField.swing.ActionEvent.iniciarServidor(). import java.awt.InetAddress.io. private ObjectOutputStream output.event. import javax. import java. import java. private ObjectInputStream input. setText( "" ). .getActionCommand() ). setSize( 300. BorderLayout. 150 ). ipServidor = host.setEditable( false ).private String ipServidor. } public void correrCliente(){ try{ conectarAservidor(). } } ).addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent event ){ enviarDatos( event. public Cliente( String host ){ super( "Cliente" ).CENTER ). BorderLayout. campoTexto. campoTexto = new JTextField(). add( campoTexto. add( new JScrollPane( areaCharla ). private Socket cliente. procesarConexion(). areaCharla = new JTextArea(). conseguirFlujo(). campoTexto. setVisible( true ). campoTexto.SOUTH ). getInputStream() ). mostrarMensaje( "n" + mensaje ).getInetAddress(). }finally { cerrarConexion(). mostrarMensaje( "Connected to: " + cliente. 12346 ). }catch ( IOException ioException ){ ioException. input = new ObjectInputStream( cliente. } } private void conectarAservidor() throws IOException{ mostrarMensaje( "Esperando conectarse" ). }catch ( ClassNotFoundException classNotFoundException ){ . } private void procesarConexion() throws IOException{ setTextFieldEditable( true ).getByName( ipServidor ). mostrarMensaje( "nExiste conexion con Servidorn" ). output. cliente = new Socket( InetAddress.flush(). } private void conseguirFlujo() throws IOException { output = new ObjectOutputStream( cliente. do{ try{ mensaje = ( String ) input.getOutputStream() ).readObject().getHostName() ).}catch ( EOFException eofException ) { mostrarMensaje( "nClient terminated connection" ).printStackTrace(). append( "nNo se puede enviar el mensaje" ).close(). } catch ( IOException ioException ) { areaCharla. output. mostrarMensaje( "nCLIENTE>>> " + sms ).writeObject( "CLIENTE>>> " + sms ).mostrarMensaje( "nUnknown object type received" ). } catch ( IOException ioException ){ ioException. input. } } private void enviarDatos( String sms ) { try{ output. } } while ( !mensaje. } private void cerrarConexion(){ mostrarMensaje( "nCerrando la conexion" ). cliente. .flush(). setTextFieldEditable( false ).close(). try{ output.equals( "SERVER>>> FINITO" ) ).close().printStackTrace(). } } . //} //} //). } } ).append( mensajeToDisplay ).setEditable( editable ).invokeLater( // new Runnable() //{ // public void run() //{ areaCharla.invokeLater( new Runnable() { public void run() // sets campoTexto’s editability { campoTexto. } private void setTextFieldEditable( final boolean editable ) { SwingUtilities.} } private void mostrarMensaje( final String mensajeToDisplay ) { //SwingUtilities. 0.correrCliente().1 =========================================== import javax. es importante que primero ejecutes el Servidor.EXIT_ON_CLOSE ).JFrame.0. esto se debe a que se esta usando TCP.1" ).0. } } Para probar esto. recuerda que se hace uso de tu servidor interno LOCALHOST 127. public class ClientTest { public static void main( String args[] ) { Cliente cliente.swing. cliente.Este el codigo para ejecutar Cliente. cliente.setDefaultCloseOperation( JFrame. cliente = new Cliente( "127.0. OTROOO . Como la idea es simple y se explica en dos patadas. Socket socket) { . El servidorúnicamente debe meterse en un bucle infinito a la espera de un cliente y cuando llegue.. public HiloDeCliente(DefaultListModel charla. haremos un ejemplo algo más complejo: un chat capaz de atender varios clientes a la vez. En este tutorial vamos a ver cómo un servidor de socket puede atender a la vez a varios clientes creando un hilo con cada uno de ellos. Basta con hacerse una clase susceptible de ser lanzada en un hilo aparte para atender a cada cliente. El código del chat puedes bajártelo y probarlo al final. Atender varios clientes con hilos.Sockets java. Tampoco es un chat muy elaborado.java // Implementa Runnable para poder ser lanzada en un hilo aparte public class HiloDeCliente implements Runnable { // En el constructor recibe y guarda los parámetros que sean necesarios. la idea es muy sencilla. así que si le das caña es posible que tengas algún problema LA IDEA BÁSICA PARA EL SERVIDOR Como he comentado arriba. } . La clase para el hilo encargada de atender a cada cliente puede ser algo parecido a esto HiloDeCliente. Un chat de ejemplo. // En este caso una lista con toda la conversación y el socket que debe // atender. Tampoco me he preocupado de hacerlo robusto. puesto que sólo se pretende dar una idea de cómo atender a varios clientes con hilos.. instanciar una clase de estas y lanzar el hilo. } } } Una vez hecho esto. . private DefaultListModel charla = new DefaultListModel(). // Se instancia una clase para atender al cliente y se lanza en // un hilo aparte.public void run () { while (true) { // Código para atender al cliente. // Bucle infinito while (true) { // Se espera y acepta un nuevo cliente Socket cliente = socketServidor. public ServidorChat() { // Se crea el socket servidor ServerSocket socketServidor = new ServerSocket(5557).java public class ServidorChat { // Para guardar toda la conversación. ServidorChat.accept(). 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. todos se enterarán del añadido y en consecuencia todos enviarán el nuevo texto a su cliente. Como todos los HiloDeCliente que haya en ese momento estarán suscritos a este DefaultListModel. Cada clase HiloDeCliente recibe este DefaultListModel. ListDataListener { public HiloDeCliente(DefaultListModel charla.start(). Cuando llegue un texto del cliente. public void intervalAdded(ListDataEvent e) . lo mete en esta clase.java // Implementa también ListDataListener para poder suscribirse a cambios // en DefaultListModel public class HiloDeCliente implements Runnable. HiloDeCliente.. pero la uso para no tener que hacerme una y liar más el ejemplo. añadiéndolo al final. Socket socket) { . podemos enterarnos y actuar en consecuencia.. Thread hilo = new Thread(nuevoCliente). cliente).Runnable nuevoCliente = new HiloDeCliente(charla. Esta es una clase java que no me gusta mucho. } } } ALGUNOS DETALLES DEL CÓDIGO DE EJEMPLO EN EL SERVIDOR He decidido meter toda la charla en un DefaultListModel. de forma que cuando cualquiera cambie cualquier cosa. La clase DefaultListModel implementa un patrón observador. hilo. incluido el que lo ha añadido. } // Trata el cambio de añadido algo al DefaultListModel. // Se suscribe a los cambios charla.addListDataListener(this). Con esto quiero decir que es posible "suscribirnos" a esa lista. .getElementAt(e.writeUTF(texto). Nos evita también el tener un array o lista con todos los hilos e ir actualizándolo cada vez que se conecta o desconecta un nuevo hilo. } } Usar este patrón 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 añade texto.{ // Obtiene el texto añadido String texto = (String) charla. Vamos ahora con detalles del cliente. // y lo envía por el socket.getIndex0()). dataOutput.
Copyright © 2024 DOKUMEN.SITE Inc.