package es.um.redes.nanoFiles.udp.client; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketTimeoutException; import java.util.Map; import java.util.LinkedHashMap; import es.um.redes.nanoFiles.tcp.client.NFConnector; 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; /** * Cliente con métodos de consulta y actualización específicos del directorio */ public class DirectoryConnector { /** * Puerto en el que atienden los servidores de directorio */ private static final int DIRECTORY_PORT = 6868; /** * Tiempo máximo en milisegundos que se esperará a recibir una respuesta por el * socket antes de que se deba lanzar una excepción SocketTimeoutException para * recuperar el control */ private static final int TIMEOUT = 1000; /** * Número de intentos máximos para obtener del directorio una respuesta a una * solicitud enviada. Cada vez que expira el timeout sin recibir respuesta se * cuenta como un intento. */ private static final int MAX_NUMBER_OF_ATTEMPTS = 5; /** * Socket UDP usado para la comunicación con el directorio */ private DatagramSocket socket; /** * Dirección de socket del directorio (IP:puertoUDP) */ private InetSocketAddress directoryAddress; /** * Nombre/IP del host donde se ejecuta el directorio */ private String directoryHostname; public static class DownloadedFile { public final String filename; public final long filesize; public final byte[] data; public final String filehash; public DownloadedFile(String filename, long fsize, byte[] data, String filehash) { this.filename = filename; this.filesize = fsize; this.data = data; this.filehash = filehash; } } public DirectoryConnector(String hostname) throws IOException { // Guardamos el string con el nombre/IP del host directoryHostname = hostname; /* * DONE: (Boletín SocketsUDP) Convertir el string 'hostname' a InetAddress y * guardar la dirección de socket (address:DIRECTORY_PORT) del directorio en el * atributo directoryAddress, para poder enviar datagramas a dicho destino. */ /* * DONE: (Boletín SocketsUDP) Crea el socket UDP en cualquier puerto para enviar * datagramas al directorio */ directoryAddress = new InetSocketAddress(InetAddress.getByName(hostname), DIRECTORY_PORT); socket = new DatagramSocket(6767); } /** * Método para enviar y recibir datagramas al/del directorio * * @param requestData los datos a enviar al directorio (mensaje de solicitud) * @return los datos recibidos del directorio (mensaje de respuesta) */ private byte[] sendAndReceiveDatagrams(byte[] requestData) { byte responseData[] = new byte[DirMessage.PACKET_MAX_SIZE]; byte response[] = null; if (directoryAddress == null) { System.err.println("DirectoryConnector.sendAndReceiveDatagrams: UDP server destination address is null!"); System.err.println( "DirectoryConnector.sendAndReceiveDatagrams: make sure constructor initializes field \"directoryAddress\""); System.exit(-1); } if (socket == null) { System.err.println("DirectoryConnector.sendAndReceiveDatagrams: UDP socket is null!"); System.err.println( "DirectoryConnector.sendAndReceiveDatagrams: make sure constructor initializes field \"socket\""); System.exit(-1); } /* * DONE¿?: (Boletín SocketsUDP) Enviar datos en un datagrama al directorio y * recibir una respuesta. El array devuelto debe contener únicamente los datos * recibidos, *NO* el búfer de recepción al completo. */ DatagramPacket pktToServer = new DatagramPacket(requestData, requestData.length, directoryAddress); DatagramPacket pktFromServer = new DatagramPacket(responseData, responseData.length); int intentos = 0; boolean recibido = false; while (intentos < MAX_NUMBER_OF_ATTEMPTS && !recibido) { try { socket.send(pktToServer); socket.setSoTimeout(TIMEOUT); socket.receive(pktFromServer); recibido = true; } catch (IOException e) { intentos++; if (intentos == MAX_NUMBER_OF_ATTEMPTS) { System.err.println("DirectoryConnector.sendAndReceiveDatagrams: socket.receive()"); System.exit(-1); } } } String servResp = new String(responseData, 0, pktFromServer.getLength()); response = servResp.getBytes(); System.out.println("Hemos recibido " + servResp); /* * done: (Boletín SocketsUDP) Una vez el envío y recepción asumiendo un canal * confiable (sin pérdidas) esté terminado y probado, debe implementarse un * mecanismo de retransmisión usando temporizador, en caso de que no se reciba * respuesta en el plazo de TIMEOUT. En caso de salte el timeout, se debe volver * a enviar el datagrama y tratar de recibir respuestas, reintentando como * máximo en MAX_NUMBER_OF_ATTEMPTS ocasiones. */ /* * DONE: (Boletín SocketsUDP) Las excepciones que puedan lanzarse al * leer/escribir en el socket deben ser capturadas y tratadas en este método. Si * se produce una excepción de entrada/salida (error del que no es posible * recuperarse), se debe informar y terminar el programa. */ /* * NOTA: Las excepciones deben tratarse de la más concreta a la más genérica. * SocketTimeoutException es más concreta que IOException. */ if (response != null && response.length == responseData.length) { System.err.println("Your response is as large as the datagram reception buffer!!\n" + "You must extract from the buffer only the bytes that belong to the datagram!"); } return response; } /** * Método para probar la comunicación con el directorio mediante el envío y * recepción de mensajes sin formatear ("en crudo") * * @return verdadero si se ha enviado un datagrama y recibido una respuesta */ public boolean testSendAndReceive() { /* * DONE: (Boletín SocketsUDP) Probar el correcto funcionamiento de * sendAndReceiveDatagrams. Se debe enviar un datagrama con la cadena "ping" y * comprobar que la respuesta recibida empieza por "pingok". En tal caso, * devuelve verdadero, falso si la respuesta no contiene los datos esperados. */ boolean success = false; byte[] reqData = new String("ping").getBytes(); byte[] resp = sendAndReceiveDatagrams(reqData); if (resp != null) { String respStr = new String(resp, 0 , resp.length); if (respStr.startsWith("pingok")) { success = true; } } return success; } public String getDirectoryHostname() { return directoryHostname; } /** * Método para "hacer ping" al directorio, comprobar que está operativo y que * usa un protocolo compatible. Este método no usa mensajes bien formados. * * @return Verdadero si */ public boolean pingDirectoryRaw() { boolean success = false; byte[] reqData = new String("ping&" + NanoFiles.PROTOCOL_ID).getBytes(); byte[] resp = sendAndReceiveDatagrams(reqData); if (resp != null) { String recv = new String(resp, 0, resp.length); if (recv.equals("welcome")) { success = true; } } /* * done: (Boletín EstructuraNanoFiles) Basándose en el código de * "testSendAndReceive", contactar con el directorio, enviándole nuestro * PROTOCOL_ID (ver clase NanoFiles). Se deben usar mensajes "en crudo" (sin un * formato bien definido) para la comunicación. * * PASOS: 1.Crear el mensaje a enviar (String "ping&protocolId"). 2.Crear un * datagrama con los bytes en que se codifica la cadena : 4.Enviar datagrama y * recibir una respuesta (sendAndReceiveDatagrams). : 5. Comprobar si la cadena * recibida en el datagrama de respuesta es "welcome", imprimir si éxito o * fracaso. 6.Devolver éxito/fracaso de la operación. */ return success; } /** * Método para "hacer ping" al directorio, comprobar que está operativo y que es * compatible. * * @return Verdadero si el directorio está operativo y es compatible */ public boolean pingDirectory() { boolean success = false; /* * done: (Boletín MensajesASCII) Hacer ping al directorio 1.Crear el mensaje a * enviar (objeto DirMessage) con atributos adecuados (operation, etc.) NOTA: * Usar como operaciones las constantes definidas en la clase DirMessageOps : * 2.Convertir el objeto DirMessage a enviar a un string (método toString) * 3.Crear un datagrama con los bytes en que se codifica la cadena : 4.Enviar * datagrama y recibir una respuesta (sendAndReceiveDatagrams). : 5.Convertir * respuesta recibida en un objeto DirMessage (método DirMessage.fromString) * 6.Extraer datos del objeto DirMessage y procesarlos 7.Devolver éxito/fracaso * de la operación */ DirMessage ping = new DirMessage(DirMessageOps.OPERATION_PING); String pingStr = ping.toString(); byte[] pingBytes = pingStr.getBytes(); byte[] resp = sendAndReceiveDatagrams(pingBytes); String respStr = new String(resp, 0, resp.length); DirMessage respPing = DirMessage.fromString(respStr); success = (respPing.getOperation().equals(DirMessageOps.OPERATION_PING_OK)); return success; } /** * Método para dar de alta como servidor de ficheros en el puerto indicado. * * @param serverPort El puerto TCP en el que este peer sirve ficheros a otros * @return Verdadero si el directorio tiene registrado a este peer como servidor * y acepta la lista de ficheros, falso en caso contrario. */ public boolean registerFileServer(int serverPort) { boolean success = false; // TODO: Ver TODOs en pingDirectory y seguir esquema similar /* * done: (Boletín MensajesASCII) Hacer ping al directorio 1.Crear el mensaje a * enviar (objeto DirMessage) con atributos adecuados (operation, etc.) NOTA: * Usar como operaciones las constantes definidas en la clase DirMessageOps : * 2.Convertir el objeto DirMessage a enviar a un string (método toString) * 3.Crear un datagrama con los bytes en que se codifica la cadena : 4.Enviar * datagrama y recibir una respuesta (sendAndReceiveDatagrams). : 5.Convertir * respuesta recibida en un objeto DirMessage (método DirMessage.fromString) * 6.Extraer datos del objeto DirMessage y procesarlos 7.Devolver éxito/fracaso * de la operación */ DirMessage serve = new DirMessage(DirMessageOps.OPERATION_SERVE); return success; } /** * Método para obtener la lista de ficheros alojados en el directorio. Para cada * fichero se debe obtener un objeto FileInfo con nombre, tamaño y hash. * * @return Los ficheros disponibles en el directorio, o null si el directorio no * pudo satisfacer nuestra solicitud */ public FileInfo[] getFileList() { FileInfo[] filelist = new FileInfo[0]; DirMessage dirfiles = new DirMessage(DirMessageOps.OPERATION_DIRFILES); byte[] dirfilesBytes = dirfiles.toString().getBytes(); byte[] resp = sendAndReceiveDatagrams(dirfilesBytes); DirMessage respDirfiles = DirMessage.fromString(resp.toString()); return filelist; } public Map getPeerList() { Map peers = new LinkedHashMap(); return peers; } public Map searchFilesByHash(String hashSubstring) { Map results = new LinkedHashMap(); return results; } public DownloadedFile downloadFileFromDirectory(String hashSubstring) { byte[] fileData = null; String filename = null; long filesize = -1; String filehash = null; return new DownloadedFile(filename, filesize, fileData, filehash); } /** * Método para darse de baja como servidor de ficheros. * * @return Verdadero si el directorio tiene registrado a este peer como servidor * y ha dado de baja sus ficheros. */ public boolean unregisterFileServer() { boolean success = false; return success; } }