package es.um.redes.nanoFiles.udp.server; import java.io.File; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.SocketException; import java.util.Arrays; import java.util.LinkedHashMap; import es.um.redes.nanoFiles.application.Directory; import es.um.redes.nanoFiles.application.NanoFiles; import es.um.redes.nanoFiles.udp.message.DirMessage; import es.um.redes.nanoFiles.udp.message.DirMessageOps; import es.um.redes.nanoFiles.util.FileInfo; import es.um.redes.nanoFiles.util.NickGenerator; public class NFDirectoryServer { /** * Número de puerto UDP en el que escucha el directorio */ public static final int DIRECTORY_PORT = 6868; /** * Socket de comunicación UDP con el cliente UDP (DirectoryConnector) */ private DatagramSocket socket = null; /* * TODO: Añadir aquí como atributos las estructuras de datos que sean necesarias * para mantener en el directorio cualquier información necesaria para la * funcionalidad del sistema nanoFilesP2P: ficheros alojados, servidores * registrados, etc. */ /** * Lista de ficheros alojados en el directorio. */ private FileInfo[] directoryFiles; /** * Lista de servidores registrados (IP, puerto TCP). */ private LinkedHashMap registeredPeers; /** * Probabilidad de descartar un mensaje recibido en el directorio (para simular * enlace no confiable y testear el código de retransmisión) */ private double messageDiscardProbability; public NFDirectoryServer(double corruptionProbability, String directoryFilesPath) throws SocketException { /* * Guardar la probabilidad de pérdida de datagramas (simular enlace no * confiable) */ messageDiscardProbability = corruptionProbability; /* * Cargar los ficheros del directorio compartido. */ File dir = new File(directoryFilesPath); if (!dir.exists()) { dir.mkdirs(); } directoryFiles = FileInfo.loadFilesFromFolder(directoryFilesPath); System.out.println("* Directory loaded " + directoryFiles.length + " files from " + directoryFilesPath); /* * DONE: (Boletín SocketsUDP) Inicializar el atributo socket: Crear un socket * UDP ligado al puerto especificado por el argumento directoryPort en la * máquina local, */ socket = new DatagramSocket(DIRECTORY_PORT); /* * DONE: (Boletín SocketsUDP) Inicializar atributos que mantienen el estado del * servidor de directorio: peers registrados, etc.) */ registeredPeers = new LinkedHashMap(); if (NanoFiles.testModeUDP) { if (socket == null) { System.err.println("[testMode] NFDirectoryServer: code not yet fully functional.\n" + "Check that all TODOs in its constructor and 'run' methods have been correctly addressed!"); System.exit(-1); } } } public DatagramPacket receiveDatagram() throws IOException { DatagramPacket datagramReceivedFromClient = null; boolean datagramReceived = false; while (!datagramReceived) { /* * DONE?: (Boletín SocketsUDP) Crear un búfer para recibir datagramas y un * datagrama asociado al búfer (datagramReceivedFromClient) */ /* * DONE: (Boletín SocketsUDP) Recibimos a través del socket un datagrama */ byte[] recvBuf = new byte[DirMessage.PACKET_MAX_SIZE]; datagramReceivedFromClient = new DatagramPacket(recvBuf, recvBuf.length); socket.receive(datagramReceivedFromClient); // Vemos si el mensaje debe ser ignorado (simulación de un canal no confiable) double rand = Math.random(); if (rand < messageDiscardProbability) { System.err.println("Directory ignored datagram from " + datagramReceivedFromClient.getSocketAddress()); } else { datagramReceived = true; } } return datagramReceivedFromClient; } public void runTest() throws IOException { System.out.println("[testMode] Directory starting..."); System.out.println("[testMode] Attempting to receive 'ping' message..."); DatagramPacket rcvDatagram = receiveDatagram(); sendResponseTestMode(rcvDatagram); System.out.println("[testMode] Attempting to receive 'ping&PROTOCOL_ID' message..."); rcvDatagram = receiveDatagram(); sendResponseTestMode(rcvDatagram); } private void sendResponseTestMode(DatagramPacket pkt) throws IOException { /* * DONE?: (Boletín SocketsUDP) Construir un String partir de los datos recibidos * en el datagrama pkt. A continuación, imprimir por pantalla dicha cadena a * modo de depuración. */ /* * DONE: (Boletín SocketsUDP) Después, usar la cadena para comprobar que su * valor es "ping"; en ese caso, enviar como respuesta un datagrama con la * cadena "pingok". Si el mensaje recibido no es "ping", se informa del error y * se envía "invalid" como respuesta. */ /* * done: (Boletín Estructura-NanoFiles) Ampliar el código para que, en el caso * de que la cadena recibida no sea exactamente "ping", comprobar si comienza * por "ping&" (es del tipo "ping&PROTOCOL_ID", donde PROTOCOL_ID será el * identificador del protocolo diseñado por el grupo de prácticas (ver * NanoFiles.PROTOCOL_ID). Se debe extraer el "protocol_id" de la cadena * recibida y comprobar que su valor coincide con el de NanoFiles.PROTOCOL_ID, * en cuyo caso se responderá con "welcome" (en otro caso, "denied"). */ String messageFromClient = new String(pkt.getData(), 0, pkt.getLength()); String send; if (messageFromClient.equals("ping")) { send = "pingok"; } else { if (messageFromClient.startsWith("ping&")) { String protocol = messageFromClient.substring(5); if (protocol.equals(NanoFiles.PROTOCOL_ID)) { send = "welcome"; } else { send = "denied"; } } else { send = "invalid"; } } InetSocketAddress sender = (InetSocketAddress) pkt.getSocketAddress(); byte[] sendData = send.getBytes(); DatagramPacket resp = new DatagramPacket(sendData, sendData.length, sender); socket.send(resp); } public void run() throws IOException { System.out.println("Directory starting..."); while (true) { // Bucle principal del servidor de directorio DatagramPacket rcvDatagram = receiveDatagram(); sendResponse(rcvDatagram); } } private void sendResponse(DatagramPacket pkt) throws IOException { /* * TODO?: (Boletín MensajesASCII) Construir String partir de los datos recibidos * en el datagrama pkt. A continuación, imprimir por pantalla dicha cadena a * modo de depuración. Después, usar la cadena para construir un objeto * DirMessage que contenga en sus atributos los valores del mensaje. A partir de * este objeto, se podrá obtener los valores de los campos del mensaje mediante * métodos "getter" para procesar el mensaje y consultar/modificar el estado del * servidor. */ String receivedData = new String(pkt.getData(), 0, pkt.getLength()); System.out.println("Hemos recibido: \n" + receivedData); DirMessage receivedMsg = DirMessage.fromString(receivedData); /* * done: Una vez construido un objeto DirMessage con el contenido del datagrama * recibido, obtener el tipo de operación solicitada por el mensaje y actuar en * consecuencia, enviando uno u otro tipo de mensaje en respuesta. */ String operation = DirMessageOps.OPERATION_INVALID; // TODO: Cambiar! if (receivedMsg != null) { operation = receivedMsg.getOperation(); } DirMessage msgToSend = new DirMessage(DirMessageOps.OPERATION_INVALID); /* * TODO: (Boletín MensajesASCII) Construir un objeto DirMessage (msgToSend) con * la respuesta a enviar al cliente, en función del tipo de mensaje recibido, * leyendo/modificando según sea necesario el "estado" guardado en el servidor * de directorio (atributos files, etc.). Los atributos del objeto DirMessage * contendrán los valores adecuados para los diferentes campos del mensaje a * enviar como respuesta (operation, etc.) */ switch (operation) { // TODO: creo que hay getters y setters para esto????? case DirMessageOps.OPERATION_PING: { /* * done: (Boletín MensajesASCII) Comprobamos si el protocolId del mensaje del * cliente coincide con el nuestro. */ msgToSend = new DirMessage(operation); String protocolId = receivedMsg.getProtocolId(); System.out.println(protocolId.equals(NanoFiles.PROTOCOL_ID)); if (protocolId.equals(NanoFiles.PROTOCOL_ID)) { operation = DirMessageOps.OPERATION_PING_OK; } else { operation = DirMessageOps.OPERATION_PING_BAD; } /* * done: (Boletín MensajesASCII) Construimos un mensaje de respuesta que indique * el éxito/fracaso del ping (compatible, incompatible), y lo devolvemos como * resultado del método. */ /* * done: (Boletín MensajesASCII) Imprimimos por pantalla el resultado de * procesar la petición recibida (éxito o fracaso) con los datos relevantes, a * modo de depuración en el servidor */ break; } case DirMessageOps.OPERATION_REQUEST_DIRFILES : { msgToSend = new DirMessage(DirMessageOps.OPERATION_DIRFILES, this.directoryFiles); break; } case DirMessageOps.OPERATION_SERVE: { if (registeredPeers.put(receivedMsg.getNick(), new InetSocketAddress(receivedMsg.getIP(), receivedMsg.getPort())) == null) { System.out.println("NFDS - añadido peer"); msgToSend = new DirMessage(DirMessageOps.OPERATION_SERVE_OK); } else { msgToSend = new DirMessage(DirMessageOps.OPERATION_SERVE_ERROR); } break; } case DirMessageOps.OPERATION_REQUEST_SERVER_PEERS: { msgToSend = new DirMessage(DirMessageOps.OPERATION_SERVER_PEERS); msgToSend.setPeers(registeredPeers); break; } default: System.err.println("Unexpected message operation: \"" + operation + "\""); System.exit(-1); } /* * done: (Boletín MensajesASCII) Convertir a String el objeto DirMessage * (msgToSend) con el mensaje de respuesta a enviar, extraer los bytes en que se * codifica el string y finalmente enviarlos en un datagrama */ String msgToSendStr = msgToSend.toString(); byte[] dataToSend = msgToSendStr.getBytes(); InetSocketAddress clientAddr = (InetSocketAddress) pkt.getSocketAddress(); DatagramPacket pktToClient = new DatagramPacket(dataToSend, dataToSend.length, clientAddr); socket.send(pktToClient); } }