/* ** This file is part of Filius, a network construction and simulation software. ** ** Originally created at the University of Siegen, Institute "Didactics of ** Informatics and E-Learning" by a students' project group: ** members (2006-2007): ** André Asschoff, Johannes Bade, Carsten Dittich, Thomas Gerding, ** Nadja Haßler, Ernst Johannes Klebert, Michell Weyer ** supervisors: ** Stefan Freischlad (maintainer until 2009), Peer Stechert ** Project is maintained since 2010 by Christian Eibl <filius@c.fameibl.de> ** and Stefan Freischlad ** Filius is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 2 of the License, or ** (at your option) version 3. ** ** Filius is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied ** warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR ** PURPOSE. See the GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with Filius. If not, see <http://www.gnu.org/licenses/>. */ package filius.software.dateiaustausch; import java.util.LinkedList; import java.util.StringTokenizer; import javax.swing.tree.DefaultMutableTreeNode; import filius.Main; import filius.software.Anwendung; import filius.software.system.Betriebssystem; import filius.software.system.Datei; import filius.software.system.Dateisystem; import filius.software.system.InternetKnotenBetriebssystem; /** * Diese Klasse verwaltet die notwendigen Daten fuer den Dateiaustausch mit * 'Gnutella'. Das ist ein Programm fuer den Dateiaustausch im Internet in einem * Peer-to-Peer-Netzwerk. Fuer die Verarbeitung von Anfragen werden ein * PeerToPeerClient und ein PeerToPeerServer verwendet. * <ul> * <li> Der Server verarbeitet alle eingehenden Anfragen von anderen * Teilnehmern. Und veranlasst gegebenenfalls die Weiterleitung von Ping-, Pong- * und Query-Nachrichten sowie die Verarbeitung von eingehenden * HTTP-GET-Anfragen. </li> * <li> Der Client versendet eigene und fremde Anfragen an andere * PeerToPeerServer und sorgt fuer die Verarbeitung eingehende Antworten auf * diese Anfragen. </li> * </ul> * */ public class PeerToPeerAnwendung extends Anwendung { /** * Liste der Teilnehmer im Peer-to-Peer-Netzwerk, die dem Prozess bekannt * sind. Die Liste enthaelt die IP-Adressen(?????) */ private LinkedList<String> bekanntePeerToPeerTeilnehmer = new LinkedList<String>(); /** Liste eigener Anfragen, die verschickt worden sind */ private LinkedList<Integer> eigeneAnfragen = new LinkedList<Integer>(); /** * Eine Liste der Anfragen, die von anderen Teilnehmern im * Peer-to-Peer-Netzwerk empfangen wurden. wofuer??? */ private LinkedList<Integer> fremdeAnfragen = new LinkedList<Integer>(); /** * Liste bereits eingegangener und weitergeleiteter Anfragen (zur * Verhinderung von Schleifen) */ private LinkedList<Integer> schonmalVerschicktListe = new LinkedList<Integer>(); /** * Liste der Dateien, zu welchen eine Anfrage zum Herunterladen an einen * bestimmten Teilnehmer verschickt wurde und deren Empfang erwartet wird */ private LinkedList<String> erwarteteDateien = new LinkedList<String>(); /** * Liste der im Peer-to-Peer-Netzwerk vorhandenen Dateien, die auf eine * Anfrage von anderen Teilnehmern angeboten wurden */ private LinkedList<String> ergebnisse = new LinkedList<String>(); /** Der Teil der Anwendung, der auf eingehende Anfragen wartet */ private PeerToPeerServer peerToPeerServer; /** Der Teil der Anwendung, der eigene Anfragen verschickt */ private PeerToPeerClient peerToPeerClient; /** * Verzeichnis, in dem die Dateien gespeichert werden, die anderen angeboten * werden und in das heruntergeladene Dateien gespeichert werden */ private DefaultMutableTreeNode verzeichnis; /** maximale Anzahl von Teilnehmern, zu denen eine Verbindung aufgebaut wird */ private int maxTeilnehmerZahl; /** * herkoemmlicher Konstruktor */ public PeerToPeerAnwendung() { super(); Main.debug.println("INVOKED-2 ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), constr: PeerToPeerAnwendung()"); // maximale Teilnehmerzahl mit einem Zufallswert 3, 4 oder 5 belegen setMaxTeilnehmerZahl(Math.round((float) Math.random() * 2) + 3); } /** * Hier wird das Betriebssystem gesetzt und das Standardverzeichnis * initialisiert und wenn noetig im Dateisystem erstellt */ public void setSystemSoftware(InternetKnotenBetriebssystem betriebssystem) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), setSystemSoftware("+betriebssystem+")"); super.setSystemSoftware(betriebssystem); Dateisystem dateisystem = betriebssystem.getDateisystem(); dateisystem.erstelleVerzeichnis(betriebssystem.getDateisystem() .getRoot(), "peer2peer"); verzeichnis = dateisystem.verzeichnisKnoten(dateisystem.holeRootPfad() + Dateisystem.FILE_SEPARATOR + "peer2peer"); } /** * Starten der Anwendung */ public void starten() { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), starten()"); super.starten(); eigeneAnfragen = new LinkedList<Integer>(); fremdeAnfragen = new LinkedList<Integer>(); schonmalVerschicktListe = new LinkedList<Integer>(); peerToPeerServer = new PeerToPeerServer(this); peerToPeerServer.setSystemSoftware(getSystemSoftware()); peerToPeerServer.starten(); peerToPeerClient = new PeerToPeerClient(this); peerToPeerClient.setSystemSoftware(getSystemSoftware()); peerToPeerClient.starten(); } /** * Beenden der Anwendung */ public void beenden() { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), beenden()"); super.beenden(); peerToPeerServer.beenden(); peerToPeerClient.beenden(); } /** * Diese Methode erstellt ein Pong-Paket als Antwort auf ein Ping-Paket * zurueck. Wenn auf das uebergebene Ping-Paket schon geantwortet wurde wird * null zurueck gegeben. * * @param paket * das Ping-Paket, zu dem das zu erzeugende Pong-Paket die * Antwort darstellen soll * @return */ PongPaket erstellePong(PingPaket ping) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), erstellePong("+ping+")"); LinkedList<Datei> dateien; Datei aktuelle; PongPaket pong; long anzahlBytes = 0; Betriebssystem bs; if (schonmalVerschicktListe.contains(ping.getGuid())) { return null; } else { bs = (Betriebssystem) getSystemSoftware(); dateien = bs.getDateisystem().holeDateien(verzeichnis); for (int i = 0; i < dateien.size(); i++) { aktuelle = (Datei) dateien.get(i); anzahlBytes = anzahlBytes + aktuelle.holeGroesse(); } pong = new PongPaket(bs.holeIPAdresse(), 6346, dateien.size(), anzahlBytes); pong.setGuid(ping.getGuid()); pong.setIpAdresse(bs.holeIPAdresse()); return pong; } } /** * Mit dieser Methode wird ein Ping-Paket versendet. Das kann entweder ein * selbst erzeugtes oder ein weiterzuleitendes Paket sein. Die GUID wird, * wenn die TTL nicht abgelaufen ist und das Paket bisher noch nicht * verschickt wurde, durch den Aufruf der entsprechenden Methode des * PeerToPeerClient verschickt, der dann einen Thread startet, der die * Antworten darauf verarbeitet. * * @param ping * @param absender */ void sendePing(PingPaket ping, String absender) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), sendePing("+ping+","+absender+")"); if (ping.getTtl() > 0 && !schonmalVerschicktListe.contains(ping.getGuid())) { schonmalVerschicktListe.add(ping.getGuid()); fremdeAnfragen.add(ping.getGuid()); peerToPeerClient.sendePing("", ping, absender); } } /** * wenn eine neue Anfrage-Nachricht eingetroffen ist, wird diese Operation * aufgerufen. Sie verarbeitet die Anfrage, schickt sie weiter, verwirft sie * oder sendet ein Antwortpaket * * @param pufferElement * die ampfangene Anfrage */ LinkedList<Datei> verarbeiteAnfrage(String absender, QueryPaket anfrage) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), verarbeiteAnfrage("+absender+","+anfrage+")"); LinkedList<Datei> ergebnisListe; Datei ergebnis = null; LinkedList<Datei> dateien; String aktuellerName; String gesuchte; if (!schonmalVerschicktListe.contains(anfrage.getGuid())) { fremdeAnfragen.add(anfrage.getGuid()); schonmalVerschicktListe.add(anfrage.getGuid()); dateien = getSystemSoftware().getDateisystem().holeDateien( verzeichnis); ergebnisListe = new LinkedList<Datei>(); for (int i = 0; i < dateien.size(); i++) { ergebnis = (Datei) dateien.get(i); aktuellerName = ergebnis.getName().toLowerCase(); gesuchte = anfrage.getSuchKriterien().toLowerCase(); if (aktuellerName.contains(gesuchte)) { ergebnisListe.add(ergebnis); } } if (anfrage.getTtl() > 0) { anfrage.setTtl(anfrage.getTtl() - 1); anfrage.setHops(anfrage.getHops() + 1); peerToPeerClient.sendeAnfrage(anfrage, absender); } return ergebnisListe; } else { return null; } } /** * Diese Methode dient dazu, einen Rechner mit einem PeerToPeer-Netzwerk zu * verbinden. Dazu wird die IP-Adresse eines bekannten Teilnehmers * uebergeben. <br /> * Die Liste der bisher bekannten Teilnehmer wird geloescht. <br /> * An die IP-Adresse des bekannten Teilnehmers wird ein Ping-Paket * versendet. Das Ping-Paket wird durch das Netz geflutet, jeder Servant, * der weniger als die jeweilige maximale Anzahl bekannter Nachbarn hat, * antwortet mit einem Pong. Der Teilnehmer fuegt die Absender der ersten * ankommenden Pongs in seine Liste der Nachbarn hinzu, bis die maximale * Anzahl von benachbarten Teilnehmern erreicht ist. * * @param teilnehmerIP * Die IP des Servants, an den das Ping-Paket zuerst geschickt * wird */ public void beitretenNetzwerk(String teilnehmerIP) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), beitretenNetzwerk("+teilnehmerIP+")"); Betriebssystem bs; PingPaket pingPaket; bs = (Betriebssystem) getSystemSoftware(); if (!bs.holeIPAdresse().equals(teilnehmerIP)) { bekanntePeerToPeerTeilnehmer.clear(); benachrichtigeBeobachter(bekanntePeerToPeerTeilnehmer); pingPaket = new PingPaket(); pingPaket.setIp(bs.holeIPAdresse()); eigeneAnfragen.add(pingPaket.getGuid()); peerToPeerClient.sendePing(teilnehmerIP, pingPaket, bs .holeIPAdresse()); } } /** * Methode zum herunterladen einer zuvor von einem anderen Teilnehmer im * Peer-to-Peer-Netzwerk angebotenen Datei. * * @param ergebnisIndex * Der Index der angebotenen Datei in der Liste der Ergebnisse * (Attribut 'ergebnisse'). */ public void herunterladenDatei(int ergebnisIndex) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), herunterladenDatei("+ergebnisIndex+")"); StringTokenizer tempTokenizer; String tmpBesitzer; StringTokenizer tempTokenizerDatei; String tmpDateiname; tempTokenizer = new StringTokenizer(ergebnisse.get(ergebnisIndex), "/"); tmpBesitzer = tempTokenizer.nextToken(); tempTokenizerDatei = new StringTokenizer(tempTokenizer.nextToken(), ":"); tmpDateiname = tempTokenizerDatei.nextToken(); erwarteteDateien.add(tmpDateiname); peerToPeerClient.dateiVomTeilnehmerAnfordern(tmpBesitzer, tmpDateiname); benachrichtigeBeobachter(); } /** * Methode zum Suchen von Dateien im Peer-to-Peer-Netzwerk * * @param datei * der Dateiname der zu suchenden Datei */ public void sucheDatei(String datei) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), sucheDatei("+datei+")"); Betriebssystem bs; QueryPaket anfragePaket; ergebnisse.clear(); anfragePaket = new QueryPaket("1", datei); eigeneAnfragen.add(anfragePaket.getGuid()); bs = (Betriebssystem) getSystemSoftware(); peerToPeerClient.sendeAnfrage(anfragePaket, bs.holeIPAdresse()); } /** Zum Abbruch aller Suchanfragen, die zuvor gestartet worden sind. */ public void abbrechenSuche() { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), abbrechenSuche()"); peerToPeerClient.abbrechenSuche(); } /** Methode zum Zuruecksetzen der Liste mit Suchergebnissen */ public void loescheSuchergebnisse() { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), loescheSuchergebnisse()"); ergebnisse.clear(); } /** * Hilfsmethode fuer den lesenden Zugriff auf die Dateien im * Peer-To-Peer-Verzeichnis des eigenen Rechners. */ Datei holeDatei(String dateiName) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), holeDatei()"); Datei datei; datei = (Datei) getSystemSoftware().getDateisystem().holeDatei( verzeichnis, dateiName); return datei; } /** * Hilfsmethode fuer den schreibenden Zugriff auf die Dateien im * Peer-To-Peer-Verzeichnis des eigenen Rechners. */ void speicherDatei(Datei datei) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), speicherDatei("+datei+")"); getSystemSoftware().getDateisystem().speicherDatei(verzeichnis, datei); } /** * Diese Methode verarbeitet eine eingegangene Nachricht. Sie befindet sich * in der PeerToPeerAnwendung (und nicht im Server oder Client), weil sowohl * Client, als auch Server solche Antwortnachrichten erhalten können * * @param antwortPaket * das eingegangene Antwortpaket */ void verarbeiteQueryHit(QueryHitPaket antwortPaket) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), verarbeiteQueryHit("+antwortPaket+")"); // warte ich selbst auf diese Antwort? if (eigeneAnfragen.contains(antwortPaket.getGuid())) { hinzuErgebnis(antwortPaket); benachrichtigeBeobachter(); } // ich kenne die Anfrage, die Antwort ist nicht fuer mich else if (fremdeAnfragen.contains(antwortPaket.getGuid())) { if (antwortPaket.getTtl() > 0) { antwortPaket.setTtl(antwortPaket.getTtl() - 1); antwortPaket.setHops(antwortPaket.getHops() + 1); peerToPeerServer.sendePaket(antwortPaket); } } } /** * Hier werden eingehende Pong-Pakete verarbeitet. Wenn es sich um die * Antwort auf eine eigene Anfrage handelt, wird die Absenderadresse der * Liste der bekannten Teilnehmer im Peer-To-Peer-Netzwerk hinzugefuegt. * Andernfalls wird das Pong-Paket mit dekrementierter TTL und * inkrementierter Hop-Zahl weitergeleitet. */ void verarbeitePong(PongPaket pongPaket) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), verarbeitePong("+pongPaket+")" + "\n\tPong-Nachricht bei '" + getSystemSoftware().getKnoten().getName() + "' eingetroffen: " + pongPaket.toString()); if (eigeneAnfragen.contains(pongPaket.getGuid())) { hinzuTeilnehmer(pongPaket.getIpAdresse()); } else { pongPaket.setTtl(pongPaket.getTtl() - 1); pongPaket.setHops(pongPaket.getHops() + 1); peerToPeerServer.sendePaket(pongPaket); } } /** Zugriff auf die Liste bekannter Teilnehmer im Peer-To-Peer-Netzwerk. */ public LinkedList<String> holeBekanntePeerToPeerTeilnehmer() { return bekanntePeerToPeerTeilnehmer; } /** * Zugriff auf die Liste der Dateien, die auf eine Anfrage angeboten wurden. * * @return */ public LinkedList<String> holeErgebnisse() { return ergebnisse; } /** * Zugriff auf das Verzeichnis im lokalen Dateisystem, in dem die Dateien * gespeichert sind, die anderen Peer-To-Peer-Teilnehmern angeboten werden. * * @return */ public DefaultMutableTreeNode holeVerzeichnis() { return verzeichnis; } /** * Hier wird der Liste mit Antworten auf eine Suchanfrage im * Peer-To-Peer-Netzwerk ein Eintrag hinzugefuegt, wenn ein Query-Hit-Paket * eingetroffen ist. * * @param ergebnis */ void hinzuErgebnis(QueryHitPaket ergebnis) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), hinzuErgebnis("+ergebnis+")"); String neuesErgebnis; neuesErgebnis = ergebnis.getIpAdresse() + "/" + ergebnis.getErgebnis(); if (!ergebnisse.contains(neuesErgebnis)) { ergebnisse.add(neuesErgebnis); benachrichtigeBeobachter(); } } /** * In dieser Methode wird ein neu gefundener Teilnehmer der Liste bekannter * Teilnehmer des Peer-to-Peer-Netzwerks mit der IP-Adresse hinzugefuegt. * Wenn die IP-Adresse in der Liste bereits vorhanden ist, oder die Anzahl * der bekannten Teilnehmer schon erreicht ist, wird die neue Adresse nicht * mehr hinzugefuegt. * * @param ipAdresse */ void hinzuTeilnehmer(String ipAdresse) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (PeerToPeerAnwendung), hinzuTeilnehmer("+ipAdresse+")"); if (bekanntePeerToPeerTeilnehmer.contains(ipAdresse)) { //Main.debug.println(getClass() + "\n\tAn Rechner " // + getSystemSoftware().getKnoten().getName() // + " ist bereits der Teilnehmer " + ipAdresse // + " eingetragen"); } else if (bekanntePeerToPeerTeilnehmer.size() >= maxTeilnehmerZahl) { //Main.debug //.println(getClass() //+ "\n\tAn Rechner " //+ getSystemSoftware().getKnoten().getName() //+ " ist bereits die maximale Anzahl bekannter Teilnehmer eingetragen." //+ "\n\tTeilnehmer " + ipAdresse //+ " wird nicht mehr eingetragen"); } else { bekanntePeerToPeerTeilnehmer.add(ipAdresse); benachrichtigeBeobachter(); } } /** * Methode fuer den Zugriff auf die Liste mit GUID's zu eigenen Anfragen, * die im Peer-to-Peer-Netzwerk verschickt worden sind. * * @return */ LinkedList<Integer> holeEigeneAnfragen() { return eigeneAnfragen; } /** * Zugriff auf die maximale Anzahl von Teilnehmern, die der Programminstanz * bekannt sind. Eine maximale Anzahl ist notwendig, weil nicht jedem * Teilnehmer immer alle anderen Teilnehmer bekannt sein koennen. Die Anzahl * muss variieren, weil in dem Fall, dass diese Anzahl fuer alle * Programminstanzen identisch ist und die maximale Teilnehmerzahl gerade * erreicht ist, ein neu beitretender Teilnehmer bei keiner Programminstanz * hinzugefuegt werden kann. * * @return */ public int getMaxTeilnehmerZahl() { return maxTeilnehmerZahl; } /** * Methode fuer den Zugriff auf die maximale Anzahl von Teilnehmen im * Peer-to-Peer-Netzwerk, die dieser Programminstanz bekannt sind. */ public void setMaxTeilnehmerZahl(int maxTeilnehmerZahl) { this.maxTeilnehmerZahl = maxTeilnehmerZahl; } }