Servidor TCP (Server.java):
publicclassServerimplementsRunnable {
privateArrayList<ConnectionHandler> connections;
privateFilef = newFile("messages.txt");
privateServerSocketserver;
privatebooleandone;
privateExecutorServicepool;
privateList<String> users = newArrayList<>();
publicServer() {
connections = newArrayList<>();
done = false;
}Atributos:
connections: Lista de manejadores de conexiones activas.f: Archivo donde se guardan los mensajes.server: Socket del servidor.done: Flag para controlar si el servidor está en funcionamiento.pool: Pool de hilos para manejar múltiples conexiones.users: Lista de nicknames de los usuarios conectados.
Constructor: Inicializa la lista de conexiones y el flag
done.@Overridepublicvoidrun() { try { server = newServerSocket(9999); pool = Executors.newCachedThreadPool(); while (!done) { Socketclient = server.accept(); ConnectionHandlerhandler = newConnectionHandler(client); connections.add(handler); pool.execute(handler); } } catch (Exceptione) { shutdown(); } }
run(): Inicia el servidor en el puerto 9999, crea un pool de hilos y acepta conexiones de clientes en un bucle. Cada conexión se maneja en un nuevo hilo usandoConnectionHandler.
publicvoidbroadcast(Stringmessage) {
for (ConnectionHandlerch : connections) {
if (ch != null) {
ch.sendMessage(message);
}
}
saveMessage(message);
}broadcast(String message): Envía un mensaje a todos los clientes conectados y lo guarda en un archivo.
privatevoidsaveMessage(Stringmessage) {
try {
BufferedWriterbw = newBufferedWriter(newFileWriter(f, true));
bw.write(message);
bw.newLine();
bw.close();
} catch (IOExceptione) {
//ignore
}
}saveMessage(String message): Guarda el mensaje en el archivomessages.txt.
publicvoidshutdown() {
try {
done = true;
pool.shutdown();
if (!server.isClosed()) {
server.close();
}
for (ConnectionHandlerch : connections) {
ch.shutdown();
}
} catch (IOExceptione) {
// ignore
}
}shutdown(): Apaga el servidor, cierra el socket del servidor y todas las conexiones activas.
classConnectionHandlerimplementsRunnable {
privateSocketclient;
privateBufferedReaderin;
privatePrintWriterout;
privateStringnickname;
publicConnectionHandler(Socketclient) {
this.client = client;
}Atributos:
client: Socket del cliente.in: Lector de entradas del cliente.out: Escritor de salidas hacia el cliente.nickname: Nickname del usuario.
Constructor: Inicializa el socket del cliente.
@Overridepublicvoidrun() {
try {
out = newPrintWriter(client.getOutputStream(), true);
in = newBufferedReader(newInputStreamReader(client.getInputStream()));
out.println("Hello!...");
out.println("Please enter a nickname : ");
validateUser(out, in);
Stringmessage;
while ((message = in.readLine()) != null) {
if (message.startsWith("/nick ")) {
String[] messageSplit = message.split(" ", 2);
if (messageSplit.length == 2) {
validateRename(messageSplit[1], out);
} else {
out.println("No nickname provided!");
}
} elseif (message.startsWith("/quit")) {
broadcast(nickname + " left the chat!");
System.out.println(nickname + " left the chat");
shutdown();
} else {
broadcast(nickname + ": " + message);
}
}
} catch (SocketExceptione) {
System.out.println("There was an Exception with a client");
} catch (IOExceptione) {
shutdown();
}
}run(): Maneja la interacción con el cliente. Solicita y valida el nickname del usuario. Procesa los comandos/nickpara cambiar el nickname y/quitpara desconectarse. Reenvía los mensajes al resto de usuarios.
validateRename: Valida y cambia el nickname del usuario.
privatevoidvalidateRename(Strings, PrintWriterout) {
if (users.contains(s)) {
out.println("User not available, it's already in use");
} else {
broadcast(nickname + " renamed themselves to " + s);
System.out.println(nickname + " renamed themselves to " + s);
nickname = s;
out.println("Successfully changed nickname to " + nickname);
}
}validateUser: Valida el nickname del usuario al conectarse. Si el usuario solicita ver mensajes anteriores, los muestra.
privatevoidvalidateUser(PrintWriterout, BufferedReaderin) {
try {
do {
nickname = in.readLine();
StringwatchMessages[] = nickname.split(" ");
if (watchMessages.length > 1) {
nickname = watchMessages[0];
Stringmessages = watchMessages[1];
if (messages.equalsIgnoreCase("Y")) {
showPrevoiusMesssages(out);
}
}
if (users.contains(nickname)) {
out.println(nickname + " is not an available username as it is already in use!");
}
} while (users.contains(nickname));
users.add(nickname);
System.out.println(nickname + " connected");
broadcast(nickname + " joined the chat!");
} catch (SocketExceptione) {
System.out.println("No nickname provided");
} catch (IOExceptione) {
thrownewRuntimeException(e);
}
}showPrevoiusMesssages: Muestra los mensajes anteriores al usuario si así lo solicita.
privatevoidshowPrevoiusMesssages(PrintWriterout) {
try {
out.println("------------------ These are the previous messages ------------------");
BufferedReaderbr = newBufferedReader(newFileReader(f));
Stringline;
try {
while ((line = br.readLine()) != null) {
out.println(line);
}
} catch (EOFExceptione) {
br.close();
}
} catch (IOExceptione) {
thrownewRuntimeException(e);
}
}sendMessage: Envía un mensaje al cliente.
publicvoidsendMessage(Stringmessage) {
out.println(message);
}shutdown: Cierra la conexión del cliente y elimina su nickname de la lista de usuarios.
publicvoidshutdown() {
try {
in.close();
out.close();
users.remove(nickname);
if (!client.isClosed()) {
client.close();
}
} catch (IOExceptione) {
// ignore
}
}
}main: Inicia el servidor ejecutando el métodorun.
publicstaticvoidmain(String[] args) {
Serverserver = newServer();
server.run();
}Este código implementa un servidor de chat multiusuario en Java utilizando sockets TCP. El servidor:
- Acepta conexiones de múltiples clientes.
- Valida los nicknames para asegurar que sean únicos.
- Permite a los usuarios cambiar su nickname y desconectarse con comandos específicos.
- Difunde los mensajes a todos los clientes conectados.
- Guarda los mensajes en un archivo para que los nuevos usuarios puedan ver el historial si así lo desean.
Este servidor maneja las conexiones concurrentemente utilizando un pool de hilos, lo que le permite escalar a múltiples usuarios simultáneamente. Claro, a continuación se explica el código del cliente en Java para un chat TCP.
Cliente TCP (Client.java):
publicclassClientimplementsRunnable {
privateSocketclient;
privateBufferedReaderin;
privatePrintWriterout;
privatebooleandone;- Atributos:
client: El socket que se conecta al servidor.in: UnBufferedReaderpara leer mensajes del servidor.out: UnPrintWriterpara enviar mensajes al servidor.done: Un flag booleano para indicar cuándo cerrar la conexión.
@Overridepublicvoidrun() {
try {
client = newSocket("127.0.0.1", 9999);
out = newPrintWriter(client.getOutputStream(), true);
in = newBufferedReader(newInputStreamReader(client.getInputStream()));
InputHandlerinHandler = newInputHandler();
Threadt = newThread(inHandler);
t.start();
StringinMessage;
while((inMessage = in.readLine()) != null){
System.out.println(inMessage);
}
} catch (Exceptione){
shutdown();
}
}run():- Conexión al Servidor: Crea un socket que se conecta al servidor en la dirección 127.0.0.1 y el puerto 9999.
- Inicialización de
outein: Configura los flujos de entrada y salida para comunicarse con el servidor. - Manejo de Entradas del Usuario: Crea y arranca un nuevo hilo
InputHandlerpara gestionar las entradas del usuario desde la consola. - Recepción de Mensajes: Lee mensajes del servidor en un bucle y los imprime en la consola.
- Manejo de Excepciones: Si ocurre una excepción, se llama al método
shutdown.
publicvoidshutdown(){
out.println("/quit");
done = true;
try{
in.close();
out.close();
if (!client.isClosed()){
client.close();
}
}catch (IOExceptione){
// ignore
}
}shutdown():- Envía el Comando de Desconexión: Envía el mensaje
/quital servidor para indicar la desconexión. - Cierra Recursos: Cierra los flujos de entrada y salida y el socket del cliente.
- Manejo de Excepciones: Ignora cualquier excepción de E/S que ocurra durante el cierre de recursos.
- Envía el Comando de Desconexión: Envía el mensaje
classInputHandlerimplementsRunnable {
@Overridepublicvoidrun() {
try {
BufferedReaderinReader = newBufferedReader(newInputStreamReader(System.in));
while(!done){
Stringmessage = inReader.readLine();
if (message.equals("/quit")){
out.println(message);
inReader.close();
shutdown();
} else {
out.println(message);
}
}
} catch (IOExceptione){
shutdown();
}
}
}La subclase InputHandler implementa Runnable y gestiona las entradas del usuario:
- Lectura de la Consola: Lee mensajes desde la entrada estándar (teclado).
- Envío de Mensajes: Envía los mensajes al servidor.
- Comando de Salida: Si el usuario escribe
/quit, cierra los recursos y llama al métodoshutdown.
publicstaticvoidmain(String[] args) {
Clientclient = newClient();
client.run();
}main: Crea una instancia deClienty llama a su métodorunpara iniciar la ejecución del cliente.
Este código implementa un cliente de chat que se conecta a un servidor en una dirección y puerto específicos. Maneja la comunicación bidireccional con el servidor, permitiendo al usuario enviar mensajes desde la consola y recibir mensajes del servidor. Además, gestiona la desconexión limpia del cliente mediante el comando /quit.