From 6d7fabaf1c811378747cb0f8154b159069623fb4 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 19 Mar 2020 22:58:57 +0100 Subject: [PATCH] Add tracker --- src/tools/HostItem.java | 7 + src/tracker/Tracker.java | 47 +++++ src/tracker/TrackerManagementTCP.java | 270 ++++++++++++++++++++++++++ src/tracker/TrackerManagementUDP.java | 221 +++++++++++++++++++++ 4 files changed, 545 insertions(+) create mode 100644 src/tracker/Tracker.java create mode 100644 src/tracker/TrackerManagementTCP.java create mode 100644 src/tracker/TrackerManagementUDP.java diff --git a/src/tools/HostItem.java b/src/tools/HostItem.java index 9bba17b..f2bfbf3 100644 --- a/src/tools/HostItem.java +++ b/src/tools/HostItem.java @@ -98,5 +98,12 @@ public class HostItem { public int getPort() { return port; } + + /** To string + * @return String representation + */ + public String toString() { + return getHostname() + " (port " + getPort() + ")"; + } } diff --git a/src/tracker/Tracker.java b/src/tracker/Tracker.java new file mode 100644 index 0000000..bb82422 --- /dev/null +++ b/src/tracker/Tracker.java @@ -0,0 +1,47 @@ +package tracker; +import tracker.TrackerManagementTCP; +import tracker.TrackerManagementUDP; +import tools.Directories; +import tools.Logger; + +/** Tracker implementation + * First argument of main method is port listened by the tracker, and is mandatory. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class Tracker { + private int port; + private Directories directories; + private Logger logger; + + /** Constructor with portStr containing a port number. + * @param portStr String containing port number of listening. + */ + public Tracker(String portStr) { + port = Integer.valueOf(Integer.parseInt(portStr)); + directories = new Directories("P2P_JAVA_PROJECT_TRACKER_" + port); + logger = new Logger(directories.getDataHomeDirectory() + "tracker.log"); + System.out.println("Tracker will listen on port " + port + " and write logs into " + directories.getDataHomeDirectory()); + directories.askOpenDataHomeDirectory(null); + } + + /** Main program entry point + * first parameter is port number and is mandatory + * to test, run with: java -ea serverP2P.ServerP2P -- + * @param args parameters + */ + public static void main(String [] args) { + Tracker t = new Tracker(args[1]); + TrackerManagementUDP tmudp = new TrackerManagementUDP(t.port, t.logger); + TrackerManagementTCP tmtcp = new TrackerManagementTCP(t.port, t.logger); + Thread tudp = new Thread(tmudp); + tudp.setName("Tracker UDP P2P-JAVA-PROJECT"); + tudp.start(); + Thread ttcp = new Thread(tmtcp); + ttcp.setName("Tracker TCP P2P-JAVA-PROJECT"); + ttcp.start(); + } + +} diff --git a/src/tracker/TrackerManagementTCP.java b/src/tracker/TrackerManagementTCP.java new file mode 100644 index 0000000..91d7dab --- /dev/null +++ b/src/tracker/TrackerManagementTCP.java @@ -0,0 +1,270 @@ +package tracker; +import tools.Logger; +import tools.LogLevel; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import protocolP2P.ProtocolP2PPacketTCP; +import protocolP2P.ProtocolP2PPacket; +import protocolP2P.RequestResponseCode; +import protocolP2P.Payload; +import tools.HostItem; +import java.util.ArrayList; +import java.util.List; +import java.io.IOException; +import exception.LocalException; +import java.util.Map; +import java.util.HashMap; +import protocolP2P.DiscoverRequest; +import protocolP2P.DiscoverResponse; +import protocolP2P.FileList; +import protocolP2P.HashRequest; +import localException.InternalError; + + +/** Tracker management implementation with tcp + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class TrackerManagementTCP implements Runnable { + + private int port; + private Logger logger; + private ServerSocket socket; + private List hostList = new ArrayList<>(); + private Map> fileList = new HashMap<>(); + + /** Constructor with port and logger. + * @param port Port used to listen. + * @param logger Logger object + */ + public TrackerManagementTCP(int port, Logger logger) { + this.port = port; + this.logger = logger; + try { + socket = new ServerSocket(port); + } catch (SocketException e) { + logger.writeTCP("Error: cannot listen on port " + port, LogLevel.Error); + System.exit(-1); + } catch (IOException e) { + logger.writeTCP("Error: cannot openning socket", LogLevel.Error); + System.exit(-2); + } + } + + /** Implementation of runnable. This methods allows to run the server. + */ + public void run() { + logger.writeTCP("Tracker sucessfully started", LogLevel.Info); + do { + try { + Socket s = socket.accept(); + ClientHandler c = new ClientHandler(s); + (new Thread(c)).start(); + } catch (IOException e) { + logger.writeTCP("Error while accepting new connection", LogLevel.Warning); + } + } while(true); + } + + + /** Private runnable class allowing to serve one client. + */ + private class ClientHandler implements Runnable { + private Socket s; + private String addr; + /** Constructor with a socket. + * @param s Socket of this client + */ + public ClientHandler(Socket s) { + this.s = s; + this.addr = "[" +s.getInetAddress().getHostAddress() + "]:" + s.getPort() + " "; + } + + /** Implementation of runnable. This method allow to serve one client. + */ + public void run() { + + boolean end = false; + logger.writeTCP(addr + "New connection", LogLevel.Action); + do { + end = handleRequest(); + } while(!end); + logger.writeTCP(addr + "End of connection", LogLevel.Action); + } + + /** Respond to next request incomming on socket s. + * @param s Socket used to read request and send response + * @return true if cannot expect another request (ie, socket is closed) + */ + private boolean handleRequest() { + try { + ProtocolP2PPacketTCP pd = new ProtocolP2PPacketTCP((Object)socket); + Payload p = pd.getPayload(); + switch (p.getRequestResponseCode()) { + case LOAD_REQUEST: + logger.writeTCP("Received LOAD_REQUEST from host " + pd.getHostItem() + ", sending NOT_FOUND", LogLevel.Action); + sendNotFound(pd); + break; + case LIST_REQUEST: + logger.writeTCP("Received LIST_REQUEST from host " + pd.getHostItem() + ", sending EMPTY_DIRECTORY", LogLevel.Action); + sendEmptyDirectory(pd); + break; + case HASH_REQUEST: + logger.writeTCP("Received HASH_REQUEST from host " + pd.getHostItem() + ", sending NOT_FOUND", LogLevel.Action); + sendNotFound(pd); + break; + case REGISTER: + handleRegister(pd); + break; + case UNREGISTER: + handleUnregister(pd); + break; + case DISCOVER_REQUEST: + handleDiscover(pd); + break; + case LIST_RESPONSE: + handleListResponse(pd); + break; + default: + logger.writeTCP("Received grabbage from host " + pd.getHostItem(), LogLevel.Action); + sendInternalError(pd); + break; + } + } catch (IOException e) { + logger.writeTCP(e, LogLevel.Warning); + return true; + } catch (LocalException e) { + logger.writeTCP(e, LogLevel.Warning); + return true; + } + return false; + } + + /** Send a not found message. + * @param pd ProtocolP2PPacketTCP to respond + */ + private void sendNotFound(ProtocolP2PPacketTCP pd) { + try { + pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.NOT_FOUND))); + } catch (Exception e) { + logger.writeTCP(e, LogLevel.Error); + } + } + + /** Send an empty directory message. + * @param pd ProtocolP2PPacketTCP to respond + */ + private void sendEmptyDirectory(ProtocolP2PPacketTCP pd) { + try { + pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.EMPTY_DIRECTORY))); + } catch (Exception e) { + logger.writeTCP(e, LogLevel.Error); + } + } + + /** Send an internal error message. + * @param pd ProtocolP2PPacketTCP to respond + */ + private void sendInternalError(ProtocolP2PPacketTCP pd) { + logger.writeTCP("Internal Error", LogLevel.Warning); + try { + pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.INTERNAL_ERROR))); + } catch (Exception e) { + logger.writeTCP(e, LogLevel.Error); + } + } + + /** Handle Registering + * @param pd ProtocolP2PPacketTCP to respond + * @throws InternalError + */ + private void handleRegister(ProtocolP2PPacketTCP pd) throws InternalError { + // add host to known host list + HostItem host = pd.getHostItem(); + if (!hostList.contains(host)) { + hostList.add(pd.getHostItem()); + } + // send a list request + try { + pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.LIST_REQUEST))); + logger.writeTCP("Received REGISTER. Adding host " + host + " to list. Sending List request", LogLevel.Action); + } catch (Exception e) { + // remove from list because list request could not be send + hostList.remove(host); + logger.writeTCP("Aborting the add of host " + host, LogLevel.Action); + logger.writeTCP(e, LogLevel.Error); + } + } + + /** Handle Unregistering + * @param pd ProtocolP2PPacketTCP to respond + * @throws InternalError + */ + private void handleUnregister(ProtocolP2PPacketTCP pd) throws InternalError { + HostItem host = pd.getHostItem(); + logger.writeTCP("Received UNREGISTER. Removing host " + host, LogLevel.Action); + hostList.remove(host); + for(String f: fileList.keySet()) { + fileList.get(f).remove(host); + if(fileList.get(f).isEmpty()) { + fileList.remove(f); + } + } + } + + /** Handle Discover request + * @param pd ProtocolP2PPacketTCP to respond + * @throws InternalError + */ + private void handleDiscover(ProtocolP2PPacketTCP pd) throws InternalError { + logger.writeTCP("Received DISCOVER REQUEST from host " + pd.getHostItem(), LogLevel.Action); + Payload p = pd.getPayload(); + assert p instanceof DiscoverRequest : "payload must be an instance of DiscoverRequest"; + if (!(p instanceof DiscoverRequest)) { + sendInternalError(pd); + } else { + String filename = ((HashRequest)p).getFilename(); + try { + pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketTCP((Payload)new DiscoverResponse(filename, fileList.getOrDefault(filename, hostList)))); + } catch (Exception e) { + logger.writeTCP(e, LogLevel.Error); + } + } + } + + /** Handle List Responses + * @param pd ProtocolP2PPacketTCP response + * @throws InternalError + */ + private void handleListResponse(ProtocolP2PPacketTCP pd) throws InternalError { + HostItem host = pd.getHostItem(); + if (!hostList.contains(host)) { + logger.writeTCP("Received LIST RESPONSE from host " + host + " but it is not registered.", LogLevel.Action); + } else { + logger.writeTCP("Received LIST RESPONSE from host " + host + ": registered host.", LogLevel.Action); + Payload p = pd.getPayload(); + assert p instanceof FileList: "payload must be an instance of FileList"; + if (!(p instanceof FileList)) { + sendInternalError(pd); + } else { + String[] f = ((FileList)p).getFileList(); + for (String file: f) { + List h = fileList.get(file); + if (h != null) { + if (!h.contains(host)) { + h.add(host); + } + } else { + List emptyH = new ArrayList<>(); + emptyH.add(host); + fileList.put(file, emptyH); + } + } + } + } + } + } +} diff --git a/src/tracker/TrackerManagementUDP.java b/src/tracker/TrackerManagementUDP.java new file mode 100644 index 0000000..f1448f8 --- /dev/null +++ b/src/tracker/TrackerManagementUDP.java @@ -0,0 +1,221 @@ +package tracker; +import tools.Logger; +import tools.LogLevel; +import java.net.DatagramSocket; +import protocolP2P.ProtocolP2PPacketUDP; +import protocolP2P.ProtocolP2PPacket; +import protocolP2P.RequestResponseCode; +import protocolP2P.Payload; +import tools.HostItem; +import java.util.ArrayList; +import java.util.List; +import java.io.IOException; +import java.net.SocketException; +import exception.LocalException; +import java.util.Map; +import java.util.HashMap; +import protocolP2P.DiscoverRequest; +import protocolP2P.DiscoverResponse; +import protocolP2P.FileList; +import protocolP2P.HashRequest; +import localException.InternalError; + +/** Tracker management implementation with udp + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class TrackerManagementUDP implements Runnable { + + private int port; + private Logger logger; + private DatagramSocket socket; + private List hostList = new ArrayList<>(); + private Map> fileList = new HashMap<>(); + + /** Constructor with port and logger. + * @param port Port used to listen. + * @param logger Logger object + */ + public TrackerManagementUDP(int port, Logger logger) { + this.port = port; + this.logger = logger; + try { + socket = new DatagramSocket(port); + } catch (SocketException e) { + logger.writeUDP("Error: cannot listen on port " + port, LogLevel.Error); + System.exit(-1); + } + } + + /** Implementation of runnable. This methods allows to run the tracker. + */ + public void run() { + logger.writeUDP("Tracker successfully started", LogLevel.Info); + while(true) { + try { + ProtocolP2PPacketUDP pd = new ProtocolP2PPacketUDP((Object)socket); + Payload p = pd.getPayload(); + switch (p.getRequestResponseCode()) { + case LOAD_REQUEST: + logger.writeUDP("Received LOAD_REQUEST from host " + pd.getHostItem() + ", sending NOT_FOUND", LogLevel.Action); + sendNotFound(pd); + break; + case LIST_REQUEST: + logger.writeUDP("Received LIST_REQUEST from host " + pd.getHostItem() + ", sending EMPTY_DIRECTORY", LogLevel.Action); + sendEmptyDirectory(pd); + break; + case HASH_REQUEST: + logger.writeUDP("Received HASH_REQUEST from host " + pd.getHostItem() + ", sending NOT_FOUND", LogLevel.Action); + sendNotFound(pd); + break; + case REGISTER: + handleRegister(pd); + break; + case UNREGISTER: + handleUnregister(pd); + break; + case DISCOVER_REQUEST: + handleDiscover(pd); + break; + case LIST_RESPONSE: + handleListResponse(pd); + break; + default: + logger.writeUDP("Received grabbage from host " + pd.getHostItem(), LogLevel.Action); + sendInternalError(pd); + break; + } + } catch (IOException e) { + logger.writeUDP(e, LogLevel.Warning); + } catch (LocalException e) { + logger.writeUDP(e, LogLevel.Warning); + } + } + } + + /** Send a not found message. + * @param pd ProtocolP2PPacketUDP to respond + */ + private void sendNotFound(ProtocolP2PPacketUDP pd) { + try { + pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.NOT_FOUND))); + } catch (Exception e) { + logger.writeUDP(e, LogLevel.Error); + } + } + + /** Send an empty directory message. + * @param pd ProtocolP2PPacketUDP to respond + */ + private void sendEmptyDirectory(ProtocolP2PPacketUDP pd) { + try { + pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.EMPTY_DIRECTORY))); + } catch (Exception e) { + logger.writeUDP(e, LogLevel.Error); + } + } + + /** Send an internal error message. + * @param pd ProtocolP2PPacketUDP to respond + */ + private void sendInternalError(ProtocolP2PPacketUDP pd) { + logger.writeUDP("Internal Error", LogLevel.Warning); + try { + pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))); + } catch (Exception e) { + logger.writeUDP(e, LogLevel.Error); + } + } + + /** Handle Registering + * @param pd ProtocolP2PPacketUDP to respond + * @throws InternalError + */ + private void handleRegister(ProtocolP2PPacketUDP pd) throws InternalError { + // add host to known host list + HostItem host = pd.getHostItem(); + if (!hostList.contains(host)) { + hostList.add(pd.getHostItem()); + } + // send a list request + try { + pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.LIST_REQUEST))); + logger.writeUDP("Received REGISTER. Adding host " + host + " to list. Sending List request", LogLevel.Action); + } catch (Exception e) { + // remove from list because list request could not be send + hostList.remove(host); + logger.writeUDP("Aborting the add of host " + host, LogLevel.Action); + logger.writeUDP(e, LogLevel.Error); + } + } + + /** Handle Unregistering + * @param pd ProtocolP2PPacketUDP to respond + * @throws InternalError + */ + private void handleUnregister(ProtocolP2PPacketUDP pd) throws InternalError { + HostItem host = pd.getHostItem(); + logger.writeUDP("Received UNREGISTER. Removing host " + host, LogLevel.Action); + hostList.remove(host); + for(String f: fileList.keySet()) { + fileList.get(f).remove(host); + if(fileList.get(f).isEmpty()) { + fileList.remove(f); + } + } + } + + /** Handle Discover request + * @param pd ProtocolP2PPacketUDP to respond + * @throws InternalError + */ + private void handleDiscover(ProtocolP2PPacketUDP pd) throws InternalError { + logger.writeUDP("Received DISCOVER REQUEST from host " + pd.getHostItem(), LogLevel.Action); + Payload p = pd.getPayload(); + assert p instanceof DiscoverRequest : "payload must be an instance of DiscoverRequest"; + if (!(p instanceof DiscoverRequest)) { + sendInternalError(pd); + } else { + String filename = ((HashRequest)p).getFilename(); + try { + pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketUDP((Payload)new DiscoverResponse(filename, fileList.getOrDefault(filename, hostList)))); + } catch (Exception e) { + logger.writeUDP(e, LogLevel.Error); + } + } + } + + /** Handle List Responses + * @param pd ProtocolP2PPacketUDP response + * @throws InternalError + */ + private void handleListResponse(ProtocolP2PPacketUDP pd) throws InternalError { + HostItem host = pd.getHostItem(); + if (!hostList.contains(host)) { + logger.writeUDP("Received LIST RESPONSE from host " + host + " but it is not registered.", LogLevel.Action); + } else { + logger.writeUDP("Received LIST RESPONSE from host " + host + ": registered host.", LogLevel.Action); + Payload p = pd.getPayload(); + assert p instanceof FileList: "payload must be an instance of FileList"; + if (!(p instanceof FileList)) { + sendInternalError(pd); + } else { + String[] f = ((FileList)p).getFileList(); + for (String file: f) { + List h = fileList.get(file); + if (h != null) { + if (!h.contains(host)) { + h.add(host); + } + } else { + List emptyH = new ArrayList<>(); + emptyH.add(host); + fileList.put(file, emptyH); + } + } + } + } + } +}