mirror of
https://github.com/binlaab/nanofiles.git
synced 2026-07-01 13:57:21 +02:00
384 lines
13 KiB
Java
384 lines
13 KiB
Java
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
|
|
*/
|
|
|
|
// este trozo hay que cambiarlo porque qué cojones
|
|
String ip = null;
|
|
try {
|
|
socket.connect(directoryAddress);
|
|
ip = socket.getLocalAddress().getHostAddress();
|
|
socket.disconnect();
|
|
} catch(Exception e) {}
|
|
|
|
DirMessage serve = new DirMessage(DirMessageOps.OPERATION_SERVE, NanoFiles.peerNickname, ip, serverPort);
|
|
byte[] serveBytes = serve.toString().getBytes();
|
|
byte[] response = sendAndReceiveDatagrams(serveBytes);
|
|
String respStr = new String(response, 0, response.length);
|
|
DirMessage respServe= DirMessage.fromString(respStr);
|
|
success = respServe.getOperation().equals(DirMessageOps.OPERATION_SERVE_OK);
|
|
|
|
if (success) {
|
|
|
|
}
|
|
|
|
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() {
|
|
// tenemos que empezar a hacer con esta línea
|
|
|
|
DirMessage requestDirfiles = new DirMessage(DirMessageOps.OPERATION_REQUEST_DIRFILES);
|
|
byte[] requestBytes = requestDirfiles.toString().getBytes();
|
|
byte[] response = sendAndReceiveDatagrams(requestBytes);
|
|
|
|
String respStr = new String(response, 0, response.length);
|
|
DirMessage respDirfiles = DirMessage.fromString(respStr);
|
|
return respDirfiles.getFileList();
|
|
// return responseDirfiles.getFileList();
|
|
}
|
|
|
|
public Map<String, InetSocketAddress> getPeerList() {
|
|
// Map<String, InetSocketAddress> peers = new LinkedHashMap<String, InetSocketAddress>();
|
|
DirMessage requestPeers = new DirMessage(DirMessageOps.OPERATION_REQUEST_SERVER_PEERS);
|
|
byte[] requestBytes = requestPeers.toString().getBytes();
|
|
byte[] response = sendAndReceiveDatagrams(requestBytes);
|
|
|
|
String respStr = new String(response, 0, response.length);
|
|
DirMessage respPeers = DirMessage.fromString(respStr);
|
|
return respPeers.getPeers();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|