Introducción
Escribo esto para que no olvide cómo implementar un socket servidor y uno cliente en Java. Ambos pueden ser conectados en la misma máquina, o conectados a través de una red.
También pueden usar distintos puertos y, como lo dije, distintas IP’s.
Por defecto, el servidor escucha en el puerto 5050 de localhost, aunque dicho puerto puede cambiarse.
Y el cliente, se conecta por defecto a localhost en el puerto 5050, pero este puede conectarse no sólo a localhost, sino a cualquier IP.
Una vez hecha la conexión, se puede “chatear” de ambos lados. Cabe mencionar que esto corre en la terminal, sin interfaz gráfica ni nada de esas cosas que sólo complican al código. Hice el código lo más limpio y corto posible.
Dicho esto, aquí dejo el código.
Código
Servidor
import java.net.*;
import java.io.*;
import java.util.Scanner;
public class Servidor {
private Socket socket;
private ServerSocket serverSocket;
private DataInputStream bufferDeEntrada = null;
private DataOutputStream bufferDeSalida = null;
Scanner escaner = new Scanner(System.in);
final String COMANDO_TERMINACION = "salir()";
public void levantarConexion(int puerto) {
try {
serverSocket = new ServerSocket(puerto);
mostrarTexto("Esperando conexión entrante en el puerto " + String.valueOf(puerto) + "...");
socket = serverSocket.accept();
mostrarTexto("Conexión establecida con: " + socket.getInetAddress().getHostName() + "\n\n\n");
} catch (Exception e) {
mostrarTexto("Error en levantarConexion(): " + e.getMessage());
System.exit(0);
}
}
public void flujos() {
try {
bufferDeEntrada = new DataInputStream(socket.getInputStream());
bufferDeSalida = new DataOutputStream(socket.getOutputStream());
bufferDeSalida.flush();
} catch (IOException e) {
mostrarTexto("Error en la apertura de flujos");
}
}
public void recibirDatos() {
String st = "";
try {
do {
st = (String) bufferDeEntrada.readUTF();
mostrarTexto("\n[Cliente] => " + st);
System.out.print("\n[Usted] => ");
} while (!st.equals(COMANDO_TERMINACION));
} catch (IOException e) {
cerrarConexion();
}
}
public void enviar(String s) {
try {
bufferDeSalida.writeUTF(s);
bufferDeSalida.flush();
} catch (IOException e) {
mostrarTexto("Error en enviar(): " + e.getMessage());
}
}
public static void mostrarTexto(String s) {
System.out.print(s);
}
public void escribirDatos() {
while (true) {
System.out.print("[Usted] => ");
enviar(escaner.nextLine());
}
}
public void cerrarConexion() {
try {
bufferDeEntrada.close();
bufferDeSalida.close();
socket.close();
} catch (IOException e) {
mostrarTexto("Excepción en cerrarConexion(): " + e.getMessage());
} finally {
mostrarTexto("Conversación finalizada....");
System.exit(0);
}
}
public void ejecutarConexion(int puerto) {
Thread hilo = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
levantarConexion(puerto);
flujos();
recibirDatos();
} finally {
cerrarConexion();
}
}
}
});
hilo.start();
}
public static void main(String[] args) throws IOException {
Servidor s = new Servidor();
Scanner sc = new Scanner(System.in);
mostrarTexto("Ingresa el puerto [5050 por defecto]: ");
String puerto = sc.nextLine();
if (puerto.length() <= 0) puerto = "5050";
s.ejecutarConexion(Integer.parseInt(puerto));
s.escribirDatos();
}
}
Cliente
import java.net.*;
import java.io.*;
import java.util.Scanner;
public class Cliente {
private Socket socket;
private DataInputStream bufferDeEntrada = null;
private DataOutputStream bufferDeSalida = null;
Scanner teclado = new Scanner(System.in);
final String COMANDO_TERMINACION = "salir()";
public void levantarConexion(String ip, int puerto) {
try {
socket = new Socket(ip, puerto);
mostrarTexto("Conectado a :" + socket.getInetAddress().getHostName());
} catch (Exception e) {
mostrarTexto("Excepción al levantar conexión: " + e.getMessage());
System.exit(0);
}
}
public static void mostrarTexto(String s) {
System.out.println(s);
}
public void abrirFlujos() {
try {
bufferDeEntrada = new DataInputStream(socket.getInputStream());
bufferDeSalida = new DataOutputStream(socket.getOutputStream());
bufferDeSalida.flush();
} catch (IOException e) {
mostrarTexto("Error en la apertura de flujos");
}
}
public void enviar(String s) {
try {
bufferDeSalida.writeUTF(s);
bufferDeSalida.flush();
} catch (IOException e) {
mostrarTexto("IOException on enviar");
}
}
public void cerrarConexion() {
try {
bufferDeEntrada.close();
bufferDeSalida.close();
socket.close();
mostrarTexto("Conexión terminada");
} catch (IOException e) {
mostrarTexto("IOException on cerrarConexion()");
}finally{
System.exit(0);
}
}
public void ejecutarConexion(String ip, int puerto) {
Thread hilo = new Thread(new Runnable() {
@Override
public void run() {
try {
levantarConexion(ip, puerto);
abrirFlujos();
recibirDatos();
} finally {
cerrarConexion();
}
}
});
hilo.start();
}
public void recibirDatos() {
String st = "";
try {
do {
st = (String) bufferDeEntrada.readUTF();
mostrarTexto("\n[Servidor] => " + st);
System.out.print("\n[Usted] => ");
} while (!st.equals(COMANDO_TERMINACION));
} catch (IOException e) {}
}
public void escribirDatos() {
String entrada = "";
while (true) {
System.out.print("[Usted] => ");
entrada = teclado.nextLine();
if(entrada.length() > 0)
enviar(entrada);
}
}
public static void main(String[] argumentos) {
Cliente cliente = new Cliente();
Scanner escaner = new Scanner(System.in);
mostrarTexto("Ingresa la IP: [localhost por defecto] ");
String ip = escaner.nextLine();
if (ip.length() <= 0) ip = "localhost";
mostrarTexto("Puerto: [5050 por defecto] ");
String puerto = escaner.nextLine();
if (puerto.length() <= 0) puerto = "5050";
cliente.ejecutarConexion(ip, Integer.parseInt(puerto));
cliente.escribirDatos();
}
}
Compilando y probando
Como lo dije hace un momento, quise mantener el código simple, sin necesidad de tenerlo en un paquete de un IDE o cosas así, por lo que para probar simplemente tenemos que compilar cada cosa.
Pequeño aviso: para compilar desde la terminal necesitas tener la carpeta bin de Java en la variable PATH del sistema. Si no sabes cómo, lee este post que he dedicado a ello.
Si no quieres hacerlo por terminal, está bien, pega el código de cada clase en archivos separados del IDE de tu preferencia y ejecútalo normalmente.
Una vez descargados los archivos, abrimos una terminal y navegamos hasta donde los hayamos guardado. Para el servidor, ejecutamos:
javac Servidor.java
Para el cliente, lo mismo pero con diferente archivo:
javac Cliente.java
Una vez compilados, y si no hay errores, podemos ejecutarlos. Para el servidor:
java Servidor
Para el cliente (recuerda abrir otra terminal, ya que deben ser procesos distintos):
java Cliente
Nota: también puedes copiar y pegar las clases en un poderoso IDE como NetBeans o IntelliJ IDEA, y ejecutarlo desde ahí.
Ejecutando archivos
Ahora llegó el momento de demostrar que las cosas realmente funcionan. Seguiré el proceso que dije usando la terminal. Compilaré entonces:
Ahora ejecutaré el servidor…
Ahora en otra terminal el cliente…
Se han conectado y ahora puedo escribir mensajes en ambas terminales:
Conclusión
Recuerda que cuando nos pide los puertos e ip’s podemos cambiarlos a como se nos antoje. Si, por ejemplo, el servidor escucha en 192.55.88.6 en el puerto 8081 entonces debemos introducir estos datos cuando la terminal los requiera.
Si queremos dejar todo como está, sólo presionamos Enter sin escribir nada.
Con esto terminamos por hoy.
y esto lo puedo implementar fuera de localhost? con maquinas remotas?
tengo una duda si quisiera guardar los chat’s como podría hacerlo?.
como puedo saber si el cliente esta conectado o desconectado del servidor, por ejemplo si el cliente no esta conectado que mande un mensaje al servidor diciendo que el cliente se desconecto y cuando se conecte que mande un mensaje diciendo que se ha conectado
Hola, no hay un evento como tal; pero
bufferDeEntrada.readUTF()
lanzará una excepción de tipo EOFException, ponlo en un try catch (justo como está, pero en lugar de salir conSystem.exit(0)
imprime un mensaje) y listo.Por otro lado, el mensaje para indicar que el cliente se ha conectado ya está, justo en donde dice:
socket = serverSocket.accept(); mostrarTexto("Conexión establecida con: " + socket.getInetAddress().getHostName() + "\n\n\n");
Implemente este codigo de sockets para una tarea de sincronizacion con vectores (Gracias!) pero me surge la duda si este Codigo (Servidor) es capaz de atender multiples clientes?
lo he tratado de hacer , pero no resulta.
habria que hacerle algun cambio ?
Gracias.
Me parece que hay que modificar el código para crear un nuevo hilo en cada conexión entrante. Así cada hilo atiende a un cliente