práctica 3, falta la última parte

This commit is contained in:
2026-03-04 20:27:45 +01:00
commit aa8b7b30fb
21 changed files with 2705 additions and 0 deletions

View File

@@ -0,0 +1,347 @@
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
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];
// TODO: Ver TODOs en pingDirectory y seguir esquema similar
return filelist;
}
public Map<String, InetSocketAddress> getPeerList() {
Map<String, InetSocketAddress> peers = new LinkedHashMap<String, InetSocketAddress>();
return peers;
}
public Map<String, InetSocketAddress[]> searchFilesByHash(String hashSubstring) {
Map<String, InetSocketAddress[]> results = new LinkedHashMap<String, InetSocketAddress[]>();
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;
}
}