En esta ocasión vamos a ver como hacer un chat en java capaz de atender múltiples clientes. Hacer un chat para comunicarse entre 2 es sencillo siempre y cuando se entienda bien como funcionan los sockets que dicho sea de paso no tienen mucha complicación pero hacer que se pueda mantener una comunicación entre más de dos hace que sean necesarias algunas cosas más puesto que un socket se comunica con un único socket y hay esta el obstáculo ¿Como poder enviar un mensaje que se envía entre 2 sockets a un tercero? pues voy a dar un ejemplo de como solucionar esto de una forma bastante sencilla.
Para hacer el chat vamos a hacer 2 aplicaciones independientes, una actuara de servidor y la otra de cliente.
La función del servidor de forma resumida se puede decir que es mantenerse en un bucle infinito a la espera de nuevas conexiones y cuando se produzca una nueva conexión se crea un hilo para atenderla donde dentro de otro bucle infinito se recibirán los mensajes enviados por los clientes y se renviarán. Y el cliente lo que hace es crear un bucle infinito para recibir los mensajes del servidor (previamente enviados por un cliente) y una función para poder enviar mensajes al servidor.
Visto que es lo que tienen que hacer el servidor y el cliente «solo» queda escribir el código java, que no será muy complicado puesto que el problema tampoco lo es y se puede explicar en 1 minuto.
El Servidor para el chat
En el código de la clase principal del servidor hay poco que explicar puesto que únicamente se crea el ServerSocket y un bucle infinito en el que se esperan conexiones y cuando se producen se crea una ConexionCliente y se pone a correr el hilo para que atienda la conexión con el cliente.
package servidorchat;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
/**
* Servidor para el chat.
*
* @author Ivan Salas Corrales <http://programandoointentandolo.com>
*/
public class ServidorChat {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// Carga el archivo de configuracion de log4J
PropertyConfigurator.configure("log4j.properties");
Logger log = Logger.getLogger(ServidorChat.class);
int puerto = 1234;
int maximoConexiones = 10; // Maximo de conexiones simultaneas
ServerSocket servidor = null;
Socket socket = null;
MensajesChat mensajes = new MensajesChat();
try {
// Se crea el serverSocket
servidor = new ServerSocket(puerto, maximoConexiones);
// Bucle infinito para esperar conexiones
while (true) {
log.info("Servidor a la espera de conexiones.");
socket = servidor.accept();
log.info("Cliente con la IP " + socket.getInetAddress().getHostName() + " conectado.");
ConexionCliente cc = new ConexionCliente(socket, mensajes);
cc.start();
}
} catch (IOException ex) {
log.error("Error: " + ex.getMessage());
} finally{
try {
socket.close();
servidor.close();
} catch (IOException ex) {
log.error("Error al cerrar el servidor: " + ex.getMessage());
}
}
}
}
En el código anterior se crea un objeto (de la clase MensajesChat) que será el que se utilizará para permitir que se puedan intercambiar mensajes entre múltiples clientes que es la idea de un chat.
Esta clase es muy sencilla porque únicamente tiene un set y get aunque puesto que hereda de Observable hay que saber de que va el patrón Observer.
En el patrón observer hay 2 tipos de elementos los observadores y los observados (MensajesChat es un observado). Si un objeto quiere observar a otro se apunta a su lista de observadores para avisarle de que quiere saber cuando cambia su estado para realizar alguna acción, por ejemplo mostrar el cambio, y el objeto observado lo que hace es informar a todos los objetos que lo están observando para decirles que su estado ha cambiado.
Una vez entendido el funcionamiento del patrón observer únicamente hay que decir que mediante la función setChanged() se indica que el estado del objeto observable a cambiado y además hay que notificárselo a sus observadores con notifyObservers(Object o) que le pasará al objeto observador el objeto o que se quiera enviar, en nuestro caso un mensaje de un cliente.
package servidorchat;
import java.util.Observable;
/**
* Objeto observable del patron observer.
*
* @author Ivan Salas Corrales <http://programandoointentandolo.com>
*/
public class MensajesChat extends Observable{
private String mensaje;
public MensajesChat(){
}
public String getMensaje(){
return mensaje;
}
public void setMensaje(String mensaje){
this.mensaje = mensaje;
// Indica que el mensaje ha cambiado
this.setChanged();
// Notifica a los observadores que el mensaje ha cambiado y se lo pasa
// (Internamente notifyObservers llama al metodo update del observador)
this.notifyObservers(this.getMensaje());
}
}
Y finalmente la clase ConexionCliente que solo tiene dos métodos, el método run que hereda de Thread y que es el que se encarga de recibir los mensajes del cliente y el método update que es necesario implementar por implementar la interfaz Observer y que es el encargado de enviar el mensaje al cliente.
package servidorchat;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Observable;
import java.util.Observer;
import org.apache.log4j.Logger;
/**
* Esta clase gestiona el envio de datos entre el servidor y el cliente al que atiende.
*
* @author Ivan Salas Corrales <http://programandoointentandolo.com>
*/
public class ConexionCliente extends Thread implements Observer{
private Logger log = Logger.getLogger(ConexionCliente.class);
private Socket socket;
private MensajesChat mensajes;
private DataInputStream entradaDatos;
private DataOutputStream salidaDatos;
public ConexionCliente (Socket socket, MensajesChat mensajes){
this.socket = socket;
this.mensajes = mensajes;
try {
entradaDatos = new DataInputStream(socket.getInputStream());
salidaDatos = new DataOutputStream(socket.getOutputStream());
} catch (IOException ex) {
log.error("Error al crear los stream de entrada y salida : " + ex.getMessage());
}
}
@Override
public void run(){
String mensajeRecibido;
boolean conectado = true;
// Se apunta a la lista de observadores de mensajes
mensajes.addObserver(this);
while (conectado) {
try {
// Lee un mensaje enviado por el cliente
mensajeRecibido = entradaDatos.readUTF();
// Pone el mensaje recibido en mensajes para que se notifique
// a sus observadores que hay un nuevo mensaje.
mensajes.setMensaje(mensajeRecibido);
} catch (IOException ex) {
log.info("Cliente con la IP " + socket.getInetAddress().getHostName() + " desconectado.");
conectado = false;
// Si se ha producido un error al recibir datos del cliente se cierra la conexion con el.
try {
entradaDatos.close();
salidaDatos.close();
} catch (IOException ex2) {
log.error("Error al cerrar los stream de entrada y salida :" + ex2.getMessage());
}
}
}
}
@Override
public void update(Observable o, Object arg) {
try {
// Envia el mensaje al cliente
salidaDatos.writeUTF(arg.toString());
} catch (IOException ex) {
log.error("Error al enviar mensaje al cliente (" + ex.getMessage() + ").");
}
}
}
En el método run hay que tener en cuenta 2 detalles, el primero es que hay que apuntarse a la lista de observadores de mensajes para que posteriormente cuando mensajes contenga un nuevo mensaje sea notificado y pueda enviar el mensaje al cliente gracias al método update, independientemente de si el mensaje lo ha enviado el cliente que es atendido por ese mismo hilo o por otro distinto. Y lo segundo es que hay que hacer uso de setMensaje para que mensajes cambie su estado y lo notifique a sus observadores.
En el método run aunque teóricamente hay que tener un bucle infinito para recibir mensajes, en la practica no es así puesto que si el cliente que es atendido por ese hilo se desconecta este hilo se continuaría ejecutando indefinidamente de forma absurda porque estaría lanzando una IOException continuamente por lo que cuando se lance esta excepción cerraremos el bucle y se terminara la ejecución del hilo liberando al servidor de una tarea absurda.
Con estas 3 clases ya tenemos un servidor capaz de permitir que se puedan conectar múltiples clientes y que cuando uno envié un mensaje todos los reciban.
El cliente para el chat
El servidor aunque es sencillo, puede ser más difícil de comprender sobretodo por el uso del patrón observer que mezclado con hilos y sockets por lo que es un buen ejemplo del uso de ese patrón aunque no era esa la idea, pero en el cliente no hay ninguna dificultad especial y si se entiende el servidor el cliente es trivial.
He hecho que clienteChat herede de JFrame por lo que será una ventana, en el constructor se crean y se colocan los componentes necesarios (JTextArea, JTextField y JButton) y un JScrollPane para que se pueda ver toda la conversación aunque sea muy larga y para colocar las cosas he usado un GridBagLayout que me parece que es bastante sencillo de usar para colocar las cosas de una forma bastante sencilla y no tener que recurrir a meter varios JPanels incluso para casos como este en el que solo hay 3 elementos, si no lo has usado nunca quizás el tener que usar GridBagConstraints puede parecer algo engorroso aunque con un par de veces que lo usas ya te acostumbras.
Después de colocar los componentes en la línea 80 se crea una VentanaConfiguracion que no es más que un JDialog para pedir un nombre de usuario y el puerto y el host del servidor por si se quieren modificar. Y finalmente se crea el socket para conectar con el servidor del chat y en la ultima línea se añade un actionListener al botón para que cuando se pulse enviar se envié el mensaje al servidor.
Y en el main se instancia un ClienteChat y se llama al método recibirMensajesServidor() que dentro de un bucle infinito recibe los mensajes enviados por el servidor y los va añadiendo al JTextArea para mostrarlos.
package clientechat;
import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.swing.*;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
/**
* Clase principal del cliente del chat
*
* @author Ivan Salas Corrales <http://programandoointentandolo.com>
*/
public class ClienteChat extends JFrame {
private Logger log = Logger.getLogger(ClienteChat.class);
private JTextArea mensajesChat;
private Socket socket;
private int puerto;
private String host;
private String usuario;
public ClienteChat(){
super("Cliente Chat");
// Elementos de la ventana
mensajesChat = new JTextArea();
mensajesChat.setEnabled(false); // El area de mensajes del chat no se debe de poder editar
mensajesChat.setLineWrap(true); // Las lineas se parten al llegar al ancho del textArea
mensajesChat.setWrapStyleWord(true); // Las lineas se parten entre palabras (por los espacios blancos)
JScrollPane scrollMensajesChat = new JScrollPane(mensajesChat);
JTextField tfMensaje = new JTextField("");
JButton btEnviar = new JButton("Enviar");
// Colocacion de los componentes en la ventana
Container c = this.getContentPane();
c.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(20, 20, 20, 20);
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = 2;
gbc.weightx = 1;
gbc.weighty = 1;
gbc.fill = GridBagConstraints.BOTH;
c.add(scrollMensajesChat, gbc);
// Restaura valores por defecto
gbc.gridwidth = 1;
gbc.weighty = 0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(0, 20, 20, 20);
gbc.gridx = 0;
gbc.gridy = 1;
c.add(tfMensaje, gbc);
// Restaura valores por defecto
gbc.weightx = 0;
gbc.gridx = 1;
gbc.gridy = 1;
c.add(btEnviar, gbc);
this.setBounds(400, 100, 400, 500);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Ventana de configuracion inicial
VentanaConfiguracion vc = new VentanaConfiguracion(this);
host = vc.getHost();
puerto = vc.getPuerto();
usuario = vc.getUsuario();
log.info("Quieres conectarte a " + host + " en el puerto " + puerto + " con el nombre de ususario: " + usuario + ".");
// Se crea el socket para conectar con el Sevidor del Chat
try {
socket = new Socket(host, puerto);
} catch (UnknownHostException ex) {
log.error("No se ha podido conectar con el servidor (" + ex.getMessage() + ").");
} catch (IOException ex) {
log.error("No se ha podido conectar con el servidor (" + ex.getMessage() + ").");
}
// Accion para el boton enviar
btEnviar.addActionListener(new ConexionServidor(socket, tfMensaje, usuario));
}
/**
* Recibe los mensajes del chat reenviados por el servidor
*/
public void recibirMensajesServidor(){
// Obtiene el flujo de entrada del socket
DataInputStream entradaDatos = null;
String mensaje;
try {
entradaDatos = new DataInputStream(socket.getInputStream());
} catch (IOException ex) {
log.error("Error al crear el stream de entrada: " + ex.getMessage());
} catch (NullPointerException ex) {
log.error("El socket no se creo correctamente. ");
}
// Bucle infinito que recibe mensajes del servidor
boolean conectado = true;
while (conectado) {
try {
mensaje = entradaDatos.readUTF();
mensajesChat.append(mensaje + System.lineSeparator());
} catch (IOException ex) {
log.error("Error al leer del stream de entrada: " + ex.getMessage());
conectado = false;
} catch (NullPointerException ex) {
log.error("El socket no se creo correctamente. ");
conectado = false;
}
}
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// Carga el archivo de configuracion de log4J
PropertyConfigurator.configure("log4j.properties");
ClienteChat c = new ClienteChat();
c.recibirMensajesServidor();
}
}
La VentanaConfiguracion como decía es un JDialog con un campo de texto para cada cosa que queremos saber y un botón para «aceptar» los datos introducidos aunque realmente lo que hace el botón es ocultar la ventana ya que es una ventana modal y mientras se esté mostrando no se podrá acceder a la ventana principal. Y para obtener los valores introducidos basta con unos simples gets que recojan los valores de los JTextFields en el formato adecuado.
package clientechat;
import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import org.apache.log4j.Logger;
/**
* Una sencilla ventana para configurar el chat
*
* @author Ivan Salas Corrales <http://programandoointentandolo.com/>
*/
public class VentanaConfiguracion extends JDialog{
private Logger log = Logger.getLogger(VentanaConfiguracion.class);
private JTextField tfUsuario;
private JTextField tfHost;
private JTextField tfPuerto;
/**
* Constructor de la ventana de configuracion inicial
*
* @param padre Ventana padre
*/
public VentanaConfiguracion(JFrame padre) {
super(padre, "Configuracion inicial", true);
JLabel lbUsuario = new JLabel("Usuario:");
JLabel lbHost = new JLabel("Host:");
JLabel lbPuerto = new JLabel("Puerto:");
tfUsuario = new JTextField();
tfHost = new JTextField("localhost");
tfPuerto = new JTextField("1234");
JButton btAceptar = new JButton("Aceptar");
btAceptar.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setVisible(false);
}
});
Container c = this.getContentPane();
c.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(20, 20, 0, 20);
gbc.gridx = 0;
gbc.gridy = 0;
c.add(lbUsuario, gbc);
gbc.gridx = 0;
gbc.gridy = 1;
c.add(lbHost, gbc);
gbc.gridx = 0;
gbc.gridy = 2;
c.add(lbPuerto, gbc);
gbc.ipadx = 100;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridx = 1;
gbc.gridy = 0;
c.add(tfUsuario, gbc);
gbc.gridx = 1;
gbc.gridy = 1;
c.add(tfHost, gbc);
gbc.gridx = 1;
gbc.gridy = 2;
c.add(tfPuerto, gbc);
gbc.gridx = 0;
gbc.gridy = 3;
gbc.gridwidth = 2;
gbc.insets = new Insets(20, 20, 20, 20);
c.add(btAceptar, gbc);
this.pack(); // Le da a la ventana el minimo tamaño posible
this.setLocation(450, 200); // Posicion de la ventana
this.setResizable(false); // Evita que se pueda estirar la ventana
this.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); // Deshabilita el boton de cierre de la ventana
this.setVisible(true);
}
public String getUsuario(){
return this.tfUsuario.getText();
}
public String getHost(){
return this.tfHost.getText();
}
public int getPuerto(){
return Integer.parseInt(this.tfPuerto.getText());
}
}
Y la ultima clase es ConexionServidor que implementa ActionListener y que es la clase que se llama al pulsar el botón enviar y que simplemente inicializa un DataOutputStream en el constructor para poder recibir datos y que en actionPerformed (que se ejecuta cuando se hace click en el botón y de igual forma se podría ejecutar por ejemplo al pulsar intro en el JTextField aunque esto no esta implementado) envía el texto al servidor del chat precedido del nombre del usuario para que luego se muestre en el chat y borra el contenido del campo de texto para que se pueda escribir el siguiente mensaje sin tener que borrarlo manualmente.
package clientechat;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import javax.swing.JTextField;
import org.apache.log4j.Logger;
/**
* Esta clase gestiona el envio de datos entre el cliente y el servidor.
*
* @author Ivan Salas Corrales <http://programandoointentandolo.com>
*/
public class ConexionServidor implements ActionListener {
private Logger log = Logger.getLogger(ConexionServidor.class);
private Socket socket;
private JTextField tfMensaje;
private String usuario;
private DataOutputStream salidaDatos;
public ConexionServidor(Socket socket, JTextField tfMensaje, String usuario) {
this.socket = socket;
this.tfMensaje = tfMensaje;
this.usuario = usuario;
try {
this.salidaDatos = new DataOutputStream(socket.getOutputStream());
} catch (IOException ex) {
log.error("Error al crear el stream de salida : " + ex.getMessage());
} catch (NullPointerException ex) {
log.error("El socket no se creo correctamente. ");
}
}
@Override
public void actionPerformed(ActionEvent e) {
try {
salidaDatos.writeUTF(usuario + ": " + tfMensaje.getText() );
tfMensaje.setText("");
} catch (IOException ex) {
log.error("Error al intentar enviar un mensaje: " + ex.getMessage());
}
}
}
Y con esto ya esta finalizado tanto el servidor como el cliente del chat. Aquí puedes descargar el código fuente tanto del servidor como del cliente y también están incluidos los jar para que puedas probar su funcionamiento sin necesidad de un IDE o de línea de comandos, aunque debes de tener en cuenta que el servidor puesto que no tiene parte grafica cuando lo ejecutes parecerá que no pasa nada pues no hay nada que mostrar y que únicamente puede haber un servidor, bueno si cambias el puerto puede haber todos los que quieras pero si lo intentas ejecutar con el mismo puerto te dará un error porque no se pueden crear varios serverSockets para atender un mismo puerto, el cliente como es normal lo puedes ejecutar tantas veces como quieras y en los ordenadores, tablets, móviles, etc. que quieras teniendo en cuenta que entonces tienes que ver cual es la IP del ordenador en el que se ejecuta el servidor para poder conectarte.
Se te agradece la generosidad al facilitar el código fuente. Estoy en la recta final del curso de Tecnología en Análisis de Sistemas. Infelizmente codificar no es lo mío; basta decir que estoy penando para sacarme de encima esas molestas materias tales como Java, Delphi (Free Pascal), Persistencia, etc. (PHP es pasable). Lo mío es arquitectura de computadores, multiseats, redes y servidores. Bueno, gracias de nuevo. Me sirve para zafar de un trabajo de «Aplicaciones basadas en Objetos Distribuídos».
Excelente amigo solo tengo una pregunta ¿como lo pongo en linea ?
Puedes usarlo en diferentes ordenadores si estan conectados a la misma red o ponerlo a funcionar en un servidor y conectarse por internet aunque este ejemplo no esta pensado para eso porque se podría hacer un chat con una aplicación web bastante facil y sería mas apropiado.
Edito: No olvides arrancar el servidor antes que los clientes porque sino no funcionará.
podrías subir un ejemplo de un chat con aplicación web, seria similar o tendría algunos cambios sobre esté
En principio sería empezar de 0 y habría que decir si se
hace usando web services, se hace una aplicación web sencillita con servlets y
jsp, usar jms, usar algún framework para no lidiar con servlets y jsp
directamente, bases de datos o que es exactamente lo que se quiere hacer, si
los clientes del chat serán una aplicación de escritorio o una web… hay
bastantes factores y dependiendo hay que usar unas cosas u otras y luego saber
cual es la mejor opción.
Por ejemplo no seria muy complicado hacer un servlet que recoja los mensajes y
que el cliente sea una pagina jsp que envié y reciba los mensajes mediante
ajax, pero si no dominas java ee, javascript y ajax pues deja de ser fácil.
muchas gracias por responder pero aun no encuentro como o ponerlo en red como lo mencionaste o configurar lo en un servidor soy nuevo y pues no creo poder hacer el chat con aplicación web
Si tanto el servidor del chat como los clientes estan conectados a la misma red no hace falta un servidor y entonces basta con cambiar en la ventana de configuración del cliente localhost por la IP local del ordenador en el que este corriendo el servidor, que sera del estilo 192.168.1.2 y si los ordenadores no estan conectados a la misma red pues ya hace falta un servidor y habria que poner en lugar de localhost la url del servidor o en su defecto la IP pero en este caso no la local.
Me acabo de fijar que en la ventana de configuración las etiquetas de los 3 campos ponia usuario y en realidad es usuario, host y puerto y aunque no afecta al funcionamiento pues quizas podía crear confunsión de que erá lo que se estaba confugurando pero ya esta corregido en el articulo y en el proyecto para descargar.
muchas gracias estoy buscado también ponerlo fuera de la red en este caso ya en un servidor si lo logro hacer te escribo y subo un tutorial de como hacerlo
Oye amigo, tengo un codigo que hacer y no tengo mucha experiencia con esto de sockets y thread, me podrias ayudar con mi code, nose explicar y así…se trata de tener un servidor y que se le puedan conectar n clientes, y que el servidor tenga una matriz de digamos 10 por 10, y entonces el servidor vaya asignando tareas de ir resolviendo la matriz a cada cliente, de modo que a mayor numero de clientes mas rapido es resuleta la matriz…
Muy buen aporte solo una duda…. que se le puede agregar para que muestre en un area a los usuarios que esten conectados??
Saludos!
Gracias, pues puedes poner una lista (List, ArrayList, array, etc.) y cuando alguien se concecte añadirlo a la lista y cuando se desconcecte eliminarlo y para que se muestren en todos los clientes usar el patrón observer exactamente igual que para los mensajes y finalmente poner un area de texto para mostrar los usuarios conectados.
no se puede hacer lo mismo como cuando recibe el mensaje??
si se puede porque que importa que lo que estes enviando sea un mensaje o el nombre de un usuario, pero supongo que querras que cuando un usuario se conecte se le muestre la lista completa de usuarios que ya estaban conectados y con los mensajes eso no sucede por eso decia lo de meterlos en una lista.
Oye amigo, tengo un codigo que hacer y no tengo mucha experiencia con esto de sockets y thread, me podrias ayudar con mi code, nose explicar y así…se trata de tener un servidor y que se le puedan conectar n clientes, y que el servidor tenga una matriz de digamos 10 por 10, y entonces el servidor vaya asignando tareas de ir resolviendo la matriz a cada cliente, de modo que a mayor numero de clientes mas rapido es resuleta la matriz…
hola… tengo una consulta ami me sale error en la parte de los paquetes org,apache, dice q no existen como puedo hacer para solucionarlo??
Esos paquetes son del log4j. Comprueba que esta el jar de log4j
en el proyecto. Si te lo descargas estan incluidos pero quizas los este buscando en otro sitio, entonces vuelve añadirlos y listo y sino si quieres puedes quitar esos imports porque «solo» son para el logging.
Hola:
Oye una duda, modifiqué el código agregándole una lista de contactos. Pero por el momento todos los clientes conectados pueden ver los mensajes de todos. Como podría hacerle para que solo el contacto seleccionado reciba el mensaje?
has podido solucionar esto?
mensajesChat.append(mensaje + System.lineSeparator());
Bueno haber si alguien me ayuda esta linea me da error, lineSeparator.
No tendría que dar ningún error pero lineseparator lo puedes sustituir por /n y obtienes el mismo resultado, un salto de línea.
oye la forma de unirlo con html como seria y el administrador podra supervisar las platicas e intervenir en ellas o cortarlas porfa
¿Como cambio el color de los mensajes?
Buen tarde Ivan, tengo una duda al correr el ChatCliente, lo he probado en una LAN y no envía los mensajes. Será que es necesario abrir en el Router el puerto 1234 para que el servidor y cliente puedan escuchar por este puerto?
Si, revisa que ni el router ni el firewall esten bloqueando la comunicación.
Un Favor si bajo el Server. mientras clientes están conectados. Luego vuelvo a subir el Server. me sale que el puerto ya esta siendo utilizado. Como cierro el puerto para que todos los clientes queden fuera. El Server se cerro por alguna falla en el Sistema Operativo o alguna cosa ajena a la aplicación.
Si el server se cierra el puerto se debe de liberar solo independientemente de que tenga o no clientes conectados ¿has comprobado que el proceso esta realmente terminado?
Te comento. Si lo hago en windows o en linux funciona. Al cerrar la aplicación. Pero lo estoy realizando en 4690OS. y las aplicaciones java las subo en background. El momento que bajo la aplicación con f8 y enseguida la levanto con f7. ya no quiere levantarse hasta yo bajar los clientes. Ese es mi inconveniente.
hola! necesito editar algo en el programa, el tama;o de ventana. no soy muy bueno en java pero lo comprendo. mi carrera es de redes y esto me ayuda con un proyecto importante y obligatori que mi trabajo por tioempo no me permite desarrollarlo de cero a mi solo.
como podria abrirlo desde eclipse por ejemplo? creo que son dos proyectos o es solo uno con dos archivos padre que son servidor y chat? luego como se hacen los ejecutables? estoy seguro son preguntas muy faciles para ti, gracias!
me ayudas dando una mano ? 🙂 te agradeceria un mundo ivan salas
Si son 2 proyectos y están hechos con NetBeans y no recuerdo si eclipse tiene alguna opción para importar proyectos de NetBeans pero si no la hay puedes crear los proyectos en eclipse vacíos y copiar las clases de los proyectos en la estructura del proyecto de eclipse
ok entonces un proyecto servidor chat y otro cliente cierto? juevo de eso como lo compilo? intentare descargar el netbeans… en el netbeans hay alguna forma de importar o abrirlo para que salga directo el designer y modificarlo esteticamente? mil gracias ivan!
Como puedo hacer que en la parte del chat imprima de abajo hacia arriba?
por que ya me se el metodo de que imprima de derecha a izquierda gracias por la ayuda
Hola buenas. ¿Sería posible tener el esquema UML? Para verlo más claro y mejorar la comprensión del código. Un saludo.
Estoy intentando implementarlo en android studio, el servidor y el cliente pueden estar dentro del mismo paquete apk?
Oye , tengo un codigo que hacer y no tengo mucha experiencia con esto de sockets y thread, me podrias ayudar con mi codigo, no se explicar y así..pero lo intentare es mas o menos esto….se trata de tener un servidor y que se le puedan conectar n clientes, y que el servidor tenga una matriz de digamos 10 por 10, y entonces el servidor vaya asignando tareas de ir resolviendo la matriz a cada cliente, de modo que a mayor numero de clientes mas rapido es resuleta la matriz…
no se muy bien lo que quieres hacer pero lo referente a los sockets y threads no cambia envies mensajes de texto, tareas o lo que tu quieras, lo unico que se hace en el ejemplo es crear un hilo para gestione la conexion con cada cliente.
La complejidad estará en lo que quieres hacer, enviar una tarea seria sencillo con un json, xml o lo que mas te guste y si las tareas son independientes pues no hay mucho mas, si dependen unas de otras pues ya habria que pensar jejeje
Según la documentación, el segundo parámetro admitido en el constructor de la clases «ServerSocket» es el tiempo máximo de espera para aceptar un cliente, no el número máximo de clientes permitidos…
servidor = new ServerSocket(puerto, maximoConexiones);
¿Estoy en lo correcto?
por lo que veo en la documentacion creo que no.
https://docs.oracle.com/javase/7/docs/api/java/net/ServerSocket.html
public ServerSocket(int port, int backlog) throws IOException
Parameters:
port – the port number, or 0 to use a port number that is automatically allocated.
backlog – requested maximum length of the queue of incoming connections.
Hola que tal ivan te hago una consulta esto serviría para conectarse con multiples clientes en chats individuales?
En principio no hay problema, una forma simple que se me ocurre podria ser añadir en ConexionCliente una variable para almacenar un identificador de la conversacion y que los clientes envien una especie de mensaje de configuracion (un texto con un formato raro podria valer ej. ¬chat_conversacion_id=001¬) con la conversacion a la que se quieren conectar para settear dicha varible y mandar ese id con cada mensaje y en el metodo update de ConexionCliente filtrar los mensajes por el id para enviarselo solo a los clientes oportunos o añadir una nueva variable en MensajesChat para almecenar el identificador para no estar sacando del mensaje el id cuando se va a enviar para hacerlo un poco mejor.
Respecto a como adaptar la interfaz para poder usar chats individuales no digo nada porque habria muchas formas dependiendo de como se quiere que se puedan generar los chats individuales o privados para grupos o lo que fuese porque al fin y al cabo no hay ninguna diferencia entre un chat indiviual 1 a 1 o de grupos.
Cómo puedo importar el proyecto en Eclipse? No consigo lograrlo…
No se si hay alguna forma directa de hacerlo pero creo que no, lo que puedes hacer es lo siguiente.
Ej. para el proyecto del cliente:
1. Crea un nuevo proyecto en eclipse con el nombre ClienteChat
2. Copia el contenido de la carpeta src (el paquete clientechat con sus clases) del proyecto en la carpeta src de tu proyecto de eclipse
3. Importa el jar del log4j para que compile, por ejemplo añade una nueva carpeta lib al proyecto de eclipse y copialo (esta en ClienteChatdistlib)
Y ya esta.
O también puedes copiar los archivos .project y .classpath de cualquier proyecto de eclipse que tengas a la raiz del proyecto descargado y cambiar en el .proyect el name a ClienteChat y para las dependencias hacerlo como te decia antes porque es mas sencillo hacerlo desde el eclipse que modificar el .classpath a mano y ya lo puedes importar como un proyecto normal de eclipse
Perfecto. Muchas gracias! Una duda más, como crees que se podría hacer para aplicarle seguridad? (Únicamente cifrar los mensajes) Más que cómo cifrar y descifrar, que eso es fácil, cómo comunicar al primer cliente que se conecte, con los nuevos que se conectan para satisfacer este esquema? https://uploads.disquscdn.com/images/e37e6d7de8c310aba41d9e65c03b3b0fbd2f921d1db6571b13dee4e8fb81bb52.jpg https://uploads.disquscdn.com/images/896e80b6c0d39be7f02e9f704474850860514fc039fb140618a34484a86aee3c.jpg https://uploads.disquscdn.com/images/69127497f960e4795bae64dee1b7ee6c6912b688a2c50a3a8791bfcd8f8142c0.jpg
Es para propósitos académicos. Te estaría muy agradecido.
La verdad es que no me acuerdo muy bien de estas cosas de seguridad, hace bastante desde que lo estudie jejeje
Si me dices cómo se tendrían que enviar las claves, quienes las envían y si el cifrado es cliente a cliente o cliente servidor y servidor clientes pues miro haber si se me ocurre algo aceptable
Te explico un poco como sería el esquema:
El primer cliente (Master, para abreviar) que se conecta, es el encargado de generar una clave de sesión AES 128 btis ECB (a ser posible), con la cual, se cifrarían todos los mensajes que circulen por nuestro sistema. Una vez que se conecte un segundo cliente, el Master, debe proporcionarle dicha clave de sesión para que este nuevo cliente, pueda cifrar los mensajes antes de mandarlos.
Puesto que los clientes nuevos, tienen un par de claves pública y privada RSA, le deben enviar al Master su clave pública (que puede pasar por el servidor sin problemas) para que este cifre la clave de sesión con ella y se la devuelva cifrada a través del servidor. Una vez el cliente nuevo ha obtenido la clave de sesión cifrada con su clave pública, podrá descifrarla con su clave privada RSA, obteniendo así la clave de sesión y pudiendo ahora, cifrar los mensajes antes de mandarlos.
El caso es que todo este proceso se haría para que todo usuario que se conecte, pueda obtener la clave de sesión con la que podrá cifrar los mensajes que quiera enviar y descifrar los mensajes que le lleguen.
Lo que te estoy pidiendo como ayuda, más que «cómo cifrar o descifrar», que eso ya nos lo proporcionan muchas librerías, es cómo gestionar ese envío de claves. Cómo poner en contacto al primer usuario que se conecte (Master), con el nuevo cliente y hacer esta especie de «proceso de negociación».
Muchas gracias por tu atención!
No es algo hiperseguro, pero es lo único que necesitamos, algo así de básico. Pero hemos tenido problemas a la hora de ver quien es el primero que se conecta, como poner en contacto al primero con los nuevos y hacer el paso de claves… etc. La cosa está en que con este esquema, si alguien intenta interceptar un mensaje o una clave escuchando en el servidor, no obtendrá nada puesto que:
-La clave de sesión se pasaría cifrada
-Los mensajes se enviarían cifrados con la clave de sesión, que si no la tienes, pues no puedes hacer nada.
Para hacer las minimas modificaciones posibles se me ocurre lo siguiente:
1. Añadimos una variable ConexionCliente master = null; en el main del servidor y cuando se crea una conexion si es null marcamos esa conexion como master (el primer cliente que se conecta)
Añadimos una nueva propiedad a la clase ConexionCliente isMaster, por ejemplo, para que quede marcada esa conexion y la ponemos a true para el primer cliente, para eso tambien la añadimos al constructor.
2. Se conecta un segundo cliente y envia su clave publica como un mensaje pero utilizando algo que permita identificar al servidor que es la clave para saber que hacer con ella.
En el metodo update de ConexionCliente que es la conexion servidor-cliente comprobamos si es una clave del modo que hubiesemos definido para identificarla y en ese caso de serlo comprobamos tambien si es master para enviarle las claves solo al master.
El master responde con la clave cifrada, para que solo le llegue al cliente que le corresponde tendriamos que añadirle un identificador al objeto ConexionCliente y como antes ponerle algo al mensaje que indique que es la clave cifrada para que el cliente cuando la reciba sepa lo que es. Para que solo se lo enviase al cliente correcto hay que añadir al metodo update otra comprobacion para que cuando sea una clave cifrada se compruebe si el id de la ConexionCliente es el mismo que viene en el mensaje. (El id se lo habriamos pasado antes al master)
3. Para que un cliente pueda saber si es master o no deberia enviar un mensaje de consulta y su id para que el servidor le responsa si lo es o si no lo es, para que sepa que tiene que hacer, en realidad con el id es suficiente porque inicialmente ConexionCliente tendra id=null y por eso sabemos que es el primer mensaje.
Entonces actualizamos el campo id a partir de mensajeRecibido y como la repuesta es para el propio cliente en lugar de hacer el .setMensaje() se hace directamente el salidaDatos.writeUTF(respuesta) para no enviarle el mensaje a todos los clientes conectados.
4. Añadir la logica correspondiente en el cliente para que pueda actuar de ambos modos de forma que en el momento en el que se conecte y envia su id para recibir su rol funcione de uno u otro modo.
En el cliente la recepcion de los mensajes se hace en recibirMensajesServidor() por lo tanto aqui deberiamos hacer las comprobaciones para hacer las respuestas automaticas a las claves y el envio del id para saber si es master o no y todas las inicializaciones correspondientes en base a los mensajes.
Y con eso creo que te puede ser suficiente para tener una idea de como hacerlo o de como no hacerlo
Illo, me acabas de salvar el ojete, te la comia to