Files
nanofiles/es/um/redes/nanoFiles/tcp/server/NFServer.java

282 lines
9.4 KiB
Java

package es.um.redes.nanoFiles.tcp.server;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import es.um.redes.nanoFiles.application.NanoFiles;
import es.um.redes.nanoFiles.tcp.client.NFConnector;
import es.um.redes.nanoFiles.tcp.message.PeerMessage;
import es.um.redes.nanoFiles.tcp.message.PeerMessageOps;
import es.um.redes.nanoFiles.util.FileInfo;
public class NFServer implements Runnable {
public static final int PORT = 10000;
private NFServerState state = new NFServerState();
private ServerSocket serverSocket = null;
public ServerSocket getServerSocket() {
return serverSocket;
}
public NFServer() throws IOException {
/*
* done: (Boletín SocketsTCP) Crear una direción de socket a partir del puerto
* especificado (PORT)
*/
/*
* done: (Boletín SocketsTCP) Crear un socket servidor y ligarlo a la dirección
* de socket anterior
*/
InetSocketAddress serverSocketAddress = new InetSocketAddress(PORT);
serverSocket = new ServerSocket();
serverSocket.bind(serverSocketAddress);
}
/**
* Método para ejecutar el servidor de ficheros en primer plano. Sólo es capaz
* de atender una conexión de un cliente. Una vez se lanza, ya no es posible
* interactuar con la aplicación.
*
*/
public void test() {
if (serverSocket == null || !serverSocket.isBound()) {
System.err.println(
"[fileServerTestMode] Failed to run file server, server socket is null or not bound to any port");
return;
} else {
System.out
.println("[fileServerTestMode] NFServer running on " + serverSocket.getLocalSocketAddress() + ".");
}
while (true) {
/*
* done: (Boletín SocketsTCP) Usar el socket servidor para esperar conexiones de
* otros peers que soliciten descargar ficheros.
*/
/*
* done: (Boletín SocketsTCP) Tras aceptar la conexión con un peer cliente, la
* comunicación con dicho cliente para servir los ficheros solicitados se debe
* implementar en el método serveFilesToClient, al cual hay que pasarle el
* socket devuelto por accept.
*/
boolean connectionOk = false;
Socket socket = null;
try {
socket = serverSocket.accept();
connectionOk = true;
} catch (IOException e) {
// conn refused
}
if (connectionOk) {
System.out.println("accepted");
try {
DataInputStream dis = new DataInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
/* int intNumber = dis.readInt();
System.out.println("received " + intNumber);
int newInt = intNumber + 1;
dos.writeInt(newInt);
System.out.println("sent " + newInt); */
PeerMessage msgIn = PeerMessage.readMessageFromInputStream(dis);
System.out.println("received " + msgIn.getOpcode() + " " + PeerMessageOps.opcodeToOperation(msgIn.getOpcode()));
if (msgIn.getOpcode() == PeerMessageOps.OPCODE_REQUEST_PEER_FILES) {
PeerMessage msgOut = new PeerMessage(PeerMessageOps.OPCODE_PEER_FILE);
msgOut.setLast(true);
msgOut.setFileSize(62);
msgOut.setFileHash("abcdefghijklmnoprqstuvwxyzaaaaaaaaaaaaaa");
msgOut.setFilenameLong((byte) 10);
msgOut.setFilenameVal("prueba.txt");
msgOut.writeMessageToOutputStream(dos);
System.out.println("sent " + PeerMessageOps.opcodeToOperation(msgOut.getOpcode()));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* Método que ejecuta el hilo principal del servidor en segundo plano, esperando
* conexiones de clientes.
*
* @see java.lang.Runnable#run()
*/
public void run() {
boolean stopServer = false; // HAY QUE CAMBIAR ESTO PORQUE NO SÉ DE DÓNDE COJONES SALE (ver TODO l. 147)
/*
* TODO: (Boletín SocketsTCP) Usar el socket servidor para esperar conexiones de
* otros peers que soliciten descargar ficheros
*/
/*
* TODO: (Boletín SocketsTCP) Al establecerse la conexión con un peer, la
* comunicación con dicho cliente se hace en el método
* serveFilesToClient(socket), al cual hay que pasarle el socket devuelto por
* accept
*/
/*
* TODO: (Boletín TCPConcurrente) Crear un hilo nuevo de la clase
* NFServerThread, que llevará a cabo la comunicación con el cliente que se
* acaba de conectar, mientras este hilo vuelve a quedar a la escucha de
* conexiones de nuevos clientes (para soportar múltiples clientes). Si este
* hilo es el que se encarga de atender al cliente conectado, no podremos tener
* más de un cliente conectado a este servidor.
*/
while (!stopServer) {
Socket socket = null;
boolean connectionOk = false;
try {
socket = serverSocket.accept();
connectionOk = true;
} catch (IOException e) {
System.err.println("Connection refused");
}
if (connectionOk) {
NFServerThread serverThread = new NFServerThread(socket, state);
serverThread.start();
int connNum = state.getNumberOfConnections();
InetSocketAddress clientAddr = (InetSocketAddress) socket.getRemoteSocketAddress();
System.out.println("New connection from " + clientAddr);
System.out.println("Total connections = " + connNum);
}
}
}
/*
* TODO: (Boletín SocketsTCP) Añadir métodos a esta clase para: 1) Arrancar el
* servidor en un hilo nuevo que se ejecutará en segundo plano 2) Detener el
* servidor (stopserver) 3) Obtener el puerto de escucha del servidor etc.
*/
/**
* Método de clase que implementa el extremo del servidor del protocolo de
* transferencia de ficheros entre pares.
*
* @param socket El socket para la comunicación con un cliente que desea
* descargar ficheros.
*/
public static void serveFilesToClient(Socket socket) {
/*
* TODO: (Boletín SocketsTCP) Crear dis/dos a partir del socket
*/
/*
* TODO: (Boletín SocketsTCP) Mientras el cliente esté conectado, leer mensajes
* de socket, convertirlo a un objeto PeerMessage y luego actuar en función del
* tipo de mensaje recibido, enviando los correspondientes mensajes de
* respuesta.
*/
/*
* TODO: (Boletín SocketsTCP) Para servir un fichero, hay que localizarlo a
* partir de su hash (o subcadena) en nuestra base de datos de ficheros
* compartidos. Los ficheros compartidos se pueden obtener con
* NanoFiles.db.getFiles(). Los métodos lookupHashSubstring y
* lookupFilenameSubstring de la clase FileInfo son útiles para buscar ficheros
* coincidentes con una subcadena dada del hash o del nombre del fichero. El
* método lookupFilePath() de FileDatabase devuelve la ruta al fichero a partir
* de su hash completo.
*/
InetSocketAddress clientAddr = (InetSocketAddress) socket.getRemoteSocketAddress();
try {
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
DataInputStream dis = new DataInputStream(socket.getInputStream());
while(true) {
PeerMessage msgIn = PeerMessage.readMessageFromInputStream(dis);
System.out.println("nfserver - " + msgIn.getOpcode());
switch (msgIn.getOpcode()) {
case PeerMessageOps.OPCODE_REQUEST_PEER_FILES:
System.out.println("hemos llegado a nfserver");
FileInfo[] archivos = NanoFiles.db.getFiles();
for (int i = 0; i < archivos.length; i++) {
PeerMessage msgOut = new PeerMessage(PeerMessageOps.OPCODE_PEER_FILE);
FileInfo archivo = archivos[i];
System.out.println("Tenemos una lista de archivos de " + archivos.length);
msgOut.setFileHash(archivo.fileHash);
msgOut.setFileSize(archivo.fileSize);
msgOut.setFilenameVal(archivo.fileName);
msgOut.setLast(false);
if (i == archivos.length - 1) { msgOut.setLast(true); System.out.println("Hemos llegado al último archivo"); };
msgOut.writeMessageToOutputStream(dos);
}
break;
case PeerMessageOps.OPCODE_REQUEST_PEER_DL:
String subHash = msgIn.getFileHash();
int chunkNum = msgIn.getChunkNum();
FileInfo[] files = FileInfo.lookupHashSubstring(NanoFiles.db.getFiles(), subHash);
if (files.length == 0) {
PeerMessage msgOut = new PeerMessage(PeerMessageOps.OPCODE_FILE_NOT_FOUND);
msgOut.writeMessageToOutputStream(dos);
break;
} else if (files.length > 1) {
PeerMessage msgOut = new PeerMessage(PeerMessageOps.OPCODE_AMBIGUOUS);
msgOut.writeMessageToOutputStream(dos);
break;
}
String path = NanoFiles.db.lookupFilePath(files[0].fileHash);
try (RandomAccessFile raf = new RandomAccessFile(path, "r")) {
long chunk = (long) chunkNum * NFConnector.CHUNK_SIZE;
int bytesALeer = (int) Math.min(NFConnector.CHUNK_SIZE, raf.length() - chunk);
// si quedan menos de CHUNK_SIZE bytes restantes, readFully se quedaría esperando
// a rellenar el buffer, lo cual no va a pasar
if (bytesALeer > 0) {
byte[] datos = new byte[bytesALeer];
raf.seek(chunk);
raf.readFully(datos);
PeerMessage msgOut = new PeerMessage(PeerMessageOps.OPCODE_PEER_DL);
msgOut.setFileData(datos);
msgOut.writeMessageToOutputStream(dos);
}
} catch (IOException e) {
PeerMessage msgOut = new PeerMessage(PeerMessageOps.OPCODE_PEER_DL_ERROR);
msgOut.writeMessageToOutputStream(dos);
}
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}