sábado, 16 de marzo de 2013

Chat para java

Buenas amigos, en este nuevo tema vamos a ver como realizar un pequeño chat para que podáis ver como funciona lo anteriormente visto en la clase Socket y ServerSocket, si no has llegado aquí desde el post y te gustaría verlo, pincha aqui.

Bien, vamos a hacer 2 clases, una para el Servidor y otra para el Cliente. Cabe decir, que no va a ser multitarea, ya que aún no he hecho un tema sobre hilos y no quiero complicar mucho el tema, aunque aviso, de que en este ejemplo que voy a hacer si que habrá hilos.

Servidor:

Los paso a seguir para montar el servidor son:

  1. Aceptar escuchas
  2. Crear un canal para flujos o stream de datos
  3. Leer los datos que nos envían los clientes.
  4. Mostrarlos por pantalla
  5. Cerrar los flujos y la conexión.

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;


public class Server{
/**
* @param args
* @throws IOException 
*/

private Socket s;
private ServerSocket ss;
private DataInputStream ois=null;
private DataOutputStream oos=null;
Scanner teclado=new Scanner(System.in);

public void ejecutarConexion() {
Thread t=new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
try {
levantarConexion();//Levantamos
flujos();//Abrimos flujos
       recibirDatos();//Recibimos datos
}finally{
cerrarConexion();//Cerramos conexiones
}
}
}

});t.start();
}

public void levantarConexion() {
   try{
    ss=new ServerSocket(5050);//Creamos el ServerSocket
}catch(BindException be){
mostrarTexto("El servicio ya esta en funcionamiento....\n");
System.exit(0);
}catch(UnknownHostException uhe){
mostrarTexto("Host Desconocido \n");
System.exit(0);
} catch (IOException e) {
// TODO Auto-generated catch block
mostrarTexto("Error al crear el Servidor");
System.exit(0);
}
mostrarTexto("Esperando conexión entrante....\n");
try {
s=ss.accept();//Aceptamos conexiones
} catch (IOException e) {
// TODO Auto-generated catch block
mostrarTexto("Error al aceptar al cliente");
}
mostrarTexto("Conexión establecida con :"+s.getInetAddress().getHostName()+"\n");
}

public void flujos() {
try {
ois=new DataInputStream(s.getInputStream());//Abro el flujo de entrada
oos=new DataOutputStream(s.getOutputStream());//Abro el flujo de salida
oos.flush();//limpio el flujo
} catch (IOException e) {
// TODO Auto-generated catch block
mostrarTexto("Error en la apertura de flujos");
}
}

public void recibirDatos()  {
String st="";
try {
do{
st=(String)ois.readUTF();//Leo y almaceno los datos que me envian
if(!st.equals("CLIENTE: fin"))mostrarTexto(st+"\n");//Muestro por pantalla
}while(!st.equals("CLIENTE: fin"));
} catch (IOException e) {
// TODO Auto-generated catch block
cerrarConexion();
}
}

public void enviar(String s){
try {
oos.writeUTF("SERVER: "+s);//Mando los datos al cliente
oos.flush();//Limpio el flujo de salida
} catch (IOException e) {
// TODO Auto-generated catch block
mostrarTexto("Se ha producido un error al enviar los datos....\n");
}
}

public void mostrarTexto( String s){
System.out.print(s);
}

public void cerrarConexion() {
try {
ois.close();//Cierro el flujo de entrada
oos.close();//Cierro el flujo de salida
s.close();//Cierro el Socket
mostrarTexto("Conversación finalizada....\n");
System.exit(0);
} catch (IOException e) {
// TODO Auto-generated catch block
mostrarTexto("Conversación finalizada....\n");
System.exit(0);
}
}
public void escribirDatos(){
while(true){
enviar(teclado.nextLine());//Mando los datos leidos por teclado
}
}
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
Server s=new Server();
s.ejecutarConexion();
s.escribirDatos();
}
}

Bien, aquí tenéis la clase entera, expliquemosla por partes y veamos que atributos necesitaremos:

  • Socket: Para recibir y enviar flujos de datos por la red
  • ServerSocket: Para aceptar las conexiones del cliente.
  • DataInputStream: Flujo de entrada para procesar los datos enviados por el cliente
  • DataOutputStream: Flujo de salida que procesa los datos que se enviarán al cliente.
  • Scanner: Para leer datos desde teclado.

Lo primero que tenemos que hacer es poner a la escucha a ServerSocket para poder aceptar, para ello levantamos la conexión.


 Lo importante en este trozo de código es lo siguiente:
  • Creamos un ServerSocket escuchando en el puerto 5050
    • ss=new ServerSocket(5050);
  • Indicamos al Socket que acepte la entrada de cualquier cliente conectado a este puerto
    • s=ss.accept();
El resto solo son excepciones lanzadas por la clase ServerSocket que he capturado:
  • BindException: Es producida cuando el Socket ya se está ejecutando.
  • UnknowHostException: Es producida cuando no se reconoce el host.
  • IOException: Es producida cuando hay un error en el intercambio de información. También es lanzada por la clase Socket, como bien podemos apreciar posteriormente.
Indicaros que el método mostrarTexto() solo escribe por consola el texto que le pasamos por parámetros.


Bien, ya tenemos el ServerSocket a la escucha en el puerto indicado, ahora tenemos que abrir los flujos de datos para procesar la información que recibimos y enviamos.


Instanciamos la clase DataInputStream y DataOutputStream y le pasamos al constructor de estas clases los flujos de entrada y salida del socket:
  • s.getInputStream();
  • s.getOutputStream();

 Utilizamos el método flush() para limpiar cualquier tipo de información que nos quede en el objeto de la clase DataOutputStream una vez mandado la información.

Ya tenemos abiertos nuestros flujos, ahora tenemos que procesar la información que nos llega por parte del cliente. Para ello leeremos la información como si de un archivo fuese.


Creamos una variable local de tipo String para almacenar los datos. Creamos un bucle do/while para estar leyendo y escribiendo datos hasta que lea un texto determinado y finalice de leer. Utilizamos el método readUTF() de la clase DataInputStream que nos permite leer el texto en su forma unicode y nos lo devuelve como String, previo casteo, claro (String).

Ahora ya tenemos un socket escuchando, los flujos abiertos y estamos recibiendo los datos, para escribir los datos nada más fácil que leer los datos que escribimos por pantalla y los enviamos.


Creo un método al que le paso por parámetro una cadena, le pasamos la cadena al método writeUTF de DataOutputStream  para que los lea el cliente y limpiamos cualquier resto de información con el método flush(). Todo ello envuelto con un bloque try/catch para capturar IOException  y mostrar un mensaje en caso de que salte la excepción.

Para leer los datos por consola, creo un método que este continuamente leyendo y enviando los datos que escribo por consola.
Con un bucle infinito while(true){} estamos continuamente leyendo lo que escribimos y enviándoselo continuamente al cliente.


Hecho todo esto y como final, cerramos los flujos y el socket.


Ya teniendo todos los componentes necesarios para comunicarnos con el cliente, lo metemos todo en un método para ejecutar la conexión.


Bueno, anteriormente vimos como creábamos los métodos para llegar a este punto, ejecutar la conexión.  Creo un Thread o hilo para que la ejecución este continuamente realizando el intercambio de información con el cliente y al mismo tiempo pueda estar leyendo datos del teclado. 

En el método run de la interfaz Runnable creamos un bucle infinito while(true){} donde levantamos la conexión, abrimos flujos y recibimos datos continuamente. Todo ello envuelto por un bloque try/catch/finally. En el bloque finally cerramos la conexión y la ejecución del programa, ya que el bloque finally siempre se ejecuta ocurra o no la excepción.

Solo nos queda ejecutar todo en la Main.


Como vemos, instanciamos nuestra clase, en mi caso llamada Server, y ejecutamos la conexión con el método ejecutarConexion() y con el método escribirDatos() estamos continuamente leyendo por teclado y enviando la información.

Cliente:

El concepto de Cliente es similar al de servidor, exceptuando que este no crea un servidor a la escucha, si no que busca un servidor que lo escuche.

Los pasos a seguir son:
    1. Conectarse al Servidor
    2. Crear un canal para flujos o stream de datos
    3. Leer los datos que nos envía el Servidor.
    4. Mostrarlos por pantalla
    5. Cerrar los flujos y la conexión.


import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;


public class Cliente  {

/**
* @param args
* @throws IOException 
*/
private Socket s;
private DataInputStream ois=null;
private DataOutputStream oos=null;
private Scanner teclado=new Scanner(System.in);

public void ejecutarConexion(){
Thread t=new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub 
try {
levantarConexion();//Levantamos la conexion
flujos();//Abrimos los flujos
       recibirDatos();//Reciibimos datos
}finally{
cerrarConexion();//Cerramos conexiones
}
}
});t.start();
}
public void levantarConexion() {
try{
s= new Socket("Dell-PC",5050);//Conectamos el socket al Servidor
mostrarTexto("Conectado a: "+s.getInetAddress().getHostName()+"\n");
}catch(java.net.ConnectException ce){
mostrarTexto("No hay ningún servicio disponible....\n");
System.exit(0);
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
mostrarTexto("Host desconocido....\n");
System.exit(0);
} catch (IOException e) {
// TODO Auto-generated catch block
mostrarTexto("Error al conectar el socket....\n");
System.exit(0);
}
}
public void flujos() {
try {
ois=new DataInputStream(s.getInputStream());//Abro flujo de entrada
oos=new DataOutputStream(s.getOutputStream());//Abro flujo de salida
oos.flush();//Limpio información residual
} catch (IOException e) {
mostrarTexto("Error al abrir los flujos...\n");
}
}
public void enviar(String s){
try {
oos.writeUTF("CLIENTE: "+s);//Mando datos al Servidor
oos.flush();//Limpio información residual
} catch (IOException e) {
// TODO Auto-generated catch block
mostrarTexto("Ocurrio un problema al enviar el texto...\n");
}
}
public void mostrarTexto(String s){
System.out.print(s);//Escribo por pantalla
}
public void cerrarConexion(){
try {
ois.close();//Cierro flujo de entrada
oos.close();//Cierro flujo de salida
s.close();//Cierro socket
mostrarTexto("Conversación finalizada....\n");
System.exit(0);
} catch (IOException e) {
// TODO Auto-generated catch block
mostrarTexto("Se proujo un error al intentar cerrar las conexión...\n");
System.exit(0);
}
}
public void recibirDatos() {
String st="";

try {
do{
st=(String)ois.readUTF();//Leo y almaceno los datos enviados por el servidor
if(!st.equals("SERVER: fin"))mostrarTexto(st+"\n");//Muestro los datos por pantalla
}while(!st.equals("SERVER: fin"));
} catch (IOException e) {
// TODO Auto-generated catch block
cerrarConexion();   
}
}
public void escribirDatos(){
while(true){
enviar(teclado.nextLine());//Leo por teclado y envio la información
}
}
public static void main(String[] args)  {
// TODO Auto-generated method stub
Cliente c=new Cliente();
c.ejecutarConexion();
c.escribirDatos();
}
}


Bueno, como la anterior clase, comenzaremos por los objetos que vamos a utilizar:

La única diferencia con el servidor, es que aquí no vamos a implementar ServerSocket, el resto igual.

Lo primero, levantar la conexión y conectarnos al Servidor.

Aquí la única linea importante es la conexión al servidor:
    • s=new Socket("Dell-PC",5050);
Con esta linea conectamos el socket del cliente con el socket del servidor, indicándole solamente el host y el puerto en el que está escuchando, en nuestro caso el 5050. OJO!!!! Dell-PC es mi nombre de host, el vuestro tiene que aparecer en las propiedades del equipo. El resto de lineas son excepciones lanzadas y capturadas.

Una vez conectados, abrimos los flujos. Estos se abren exactamente igual que en el servidor.

Es exactamente igual, instanciamos nuestras clases de entrada y salida (DataInputStream y DataOutputStream) y le pasamos la información del socket para que la procese.

Recibimos los datos del servidor procesados por DataInputStream y los mostramos por pantalla:


Aquí otro método igual al de la clase Server, leo y almaceno los datos recibidos por parte del servidor en la variable local de tipo String y lo muestro por pantalla.

Enviar datos al servidor tiene la misma mecánica


mandamos los datos que se nos pasa por argumento al servidor y posteriormente con el método flush() limpiamos cualquier tipo de información residual que pueda quedar.

Ya solo nos falta cerrar los flujos y el socket cuando sea necesario.


Teniendo todo el código para hacerlo funcionar, vamos a meterlo todo en un método que lance estros procesos en un hilo para que puedan estar realizando chequeos de información para enviar o recibir continuamente.

Con esto garantizamos que aparte de estar realizando esta acción continuamente, nos permita leer lo que escribimos por teclado:


En un bucle infinito while(true) leemos y enviamos continuamente información del teclado.

Ya para terminar con esta clase, el método main ejecutando todo el proceso.


Y fin!!! Tenemos un chat, aquí el resultado:



Bien amigos, hay muchas formas de hacer un chat, lo mejor es utilizar Swing y AWT, pero de esos hay muchos por la red, se pueden encontrar a patadas buscando en google, así que por esa razón lo hice de esta manera.

Seguramente tendréis algunas dudas de como hacer esto y lo otro, yo me he intentado explicar lo mejor que he podido, podéis complementar la información que aquí os doy con cualquier otro tipo de información de la red.

Un saludo.

3 comentarios:

  1. No puede estar mejor explicado
    Gracias!!!

    ResponderEliminar
  2. hola solo una pregunta este código que nos proporcionaste es únicamente para hacerlo en un red local o es para hacerlo mediante dos dispositivos de distintas redes?

    ResponderEliminar
  3. Bien amigos, hay muchas formas de hacer un chat, lo mejor es utilizar Swing y AWT, pero de esos hay muchos por la red, se pueden encontrar a patadas buscando en google, así que por esa razón lo hice de esta manera. coaching-mastery.com/es-posible-engrosar-el-pene/

    ResponderEliminar