Praxis-Tutorial JXTA, Teil 1

Peer-to-Peer

Markus Oliver Junginger

Die steigende Relevanz von Peer-to-Peer Systemen erkannte Sun schon früh und rief unter der Führung von Bill Joy das Open Source-Projekt JXTA ins Leben. Ähnlich wie einst TCP/IP, ist JXTA heute ein standardisiertes Protokoll für eine neue Infrastruktur und bietet darüber hinaus eine vollständige Plattform für moderne verteilte Systeme. Mit JXTA als Middleware können Sie mit geringem Aufwand die Vorteile von Peer-to-Peer Systemen nutzen und eine verbesserte Skalierbarkeit, Ressourcenverfügbarkeit und Fehlertoleranz gegenüber Client/Server-Systemen erzielen.

Überblick

Dieser und der im folgenden Java Magazin erscheinende Artikel werden Ihnen einen Einstieg in die Java-Referenzimplementierung des JXTA-Netzwerksprotokolls geben und Ihnen die Vorteile von Peer-to-Peer-Konzepten näher bringen. Dieser erste Teil vermittelt anhand einer Chat-Anwendung (Abb. 1) zentrale Konzepte wie PeerGroups und Pipes. Die Betrachtung wiederverwendbarer Komponenten, welche die Verwaltung von Peer-Gruppen beziehungsweise die Chat-Funktionalität übernehmen, werden dabei im Mittelpunkt stehen. Das Ergebnis wird ein gruppenorientierter Chat sein: eine Anwendung, die gerade im kommunikationsintensiven Peer-to-Peer-Bereich immer wieder in komplexere Applikationen einfließt. Dies zeigt sich auch im zweiten Teil des Tutorials, in dem wir die hier vorgestellten Komponenten wiederverwenden werden. Ziel wird dann die Entwicklung einer Arbeitsfläche (collaborative workbench) sein, auf der mehrere Peers gleichzeitig an einer gemeinsamen Aufgabe arbeiten können.

Abb. 1: Screenshot der Chat-Anwendung

Unsere Chat-Anwendung wird es den Benutzern erlauben in verschiedenen Chat-Räumen Nachrichten auszutauschen. Jeder Raum wird durch eine eigene PeerGroup repräsentiert, in der die Chat-Nachrichten mit Hilfe einer PropagatePipe an die Chat-Teilnehmer übermittelt werden. Schauen wir uns jetzt die dafür nötigen Schritte im Detail an.

Ausgangspunkt aller JXTA-Anwendungen ist die Initialisierung der globalen NetPeerGroup. Hiermit wird das JXTA-Netzwerk auf dem lokalen Rechner gestartet und der Zugriff auf weitere Funktionen ermöglicht. Der Anwender wird zudem entweder mit dem Konfigurations- oder dem Login-Dialog konfrontiert. Auslöser für diese komplexen Vorgänge ist folgende, an sich harmlose, Zeile Code:

PeerGroup netGroup=PeerGroupFactory.newNetPeerGroup();

Sollte bei der Ausführung ein Problem auftreten, wird eine PeerGroupException geworfen, weshalb die obige Anweisung in einem try/catch-Block stehen muss. Über das zurückgegebene PeerGroup-Objekt können beispielsweise neue PeerGroups erzeugt sowie weitere Services angefordert werden.

Nachdem die NetPeerGroup initialisiert wurde, empfiehlt sich das Warten auf eine Verbindung zu einem Rendezvous-Peer, damit sichergestellt wird, dass der Peer mit dem Netzwerk verbunden ist und die JXTA Services erwartungsgemäß arbeiten können. Der folgende Quellcode erreicht dies, indem solange gewartet wird, bis die Liste der verbundenen Rendezvous-Peers nicht mehr leer ist:

RendezVousService rdv=netGroup.getRendezVousService();
while(!rdv.getConnectedRendezVous().hasMoreElements())
{
try {Thread.sleep(1000);}
catch(Exception ex) {}
}

Unsere Chat-Anwendung erfordert das Verwalten von PeerGroups sowie Mitgliedschaften zu diesen PeerGroups. Da es sich hierbei um eine regelmäßig wiederkehrende Aufgabe handelt, wurde die wiederverwendbare Groups-Komponente entwickelt. Sie vereinfacht die Handhabung mit PeerGroups enorm und stellt dem Anwender ein GUI zur Verfügung. Das GUI (rechter Teil der Abpictureung) stellt bereits vorhandene Gruppen in einer Liste dar und erlaubt das Beitreten sowie Anlegen einer Gruppe. Die Verwendung der Groups-Komponente erfolgt nach folgendem Muster:

  • Erzeugen einer neuen Instanz von biz.junginger.jxta.Groups.
  • Optional: Beschreibenden Text für neu angelegte Gruppen mit setNewGroupDescription definieren.
  • Mit addListener einen Listener registrieren.
  • Die über getJComponent verfügbare Swing-Komponente einem Container hinzufügen.
  • Auf die Listener-Ereignisse reagieren.

Der linke Teil der Abpictureung zeigt das GUI der ChatBoard-Komponente, das für die Chat-Funktionalität zuständig ist. Es besteht aus dem Textfeld für empfangene Nachrichten sowie jeweils ein Eingabefeld für den Namen des Chatters und die zu verschickende Nachricht. Sobald die Nachricht mit Return bestätigt wird, erfolgt das Versenden an alle anderen Mitglieder der Chat-Gruppe, welche die Nachrichten dem Textfeld hinzufügen.

Der wesentliche Quellcode der ChatBoard-Klasse ist in Listing 1 abgepictureet. Für uns am interessantesten ist die durch eine PropagatePipe realisierte Kommunikation unter den Chat-Beteiligten. Eine PropagatePipe bietet sich in diesem Fall besonders an, da sie mehrere Sender und Empfänger erlaubt. So sendet ein Chat-Teilnehmer Nachrichten an die PropagatePipe, welche die Nachricht an alle Chat-Teilnehmer weiterleitet.

public class ChatBoard implements Groups.Listener, PipeMsgListener
{
//JXTA
private PeerGroup group;
private PipeService pipes;
private InputPipe inPipe;
private OutputPipe outPipe;
private MimeMediaType mimeType=new MimeMediaType("text", "xml");

//Swing
private JPanel panel;
private JScrollPane scrollPane;
private JTextArea board;
private JTextField input;
private JTextField nickname;

/**
* @see biz.junginger.jxta.Groups.Listener#groupJoined(PeerGroup)
*/
public void joinedGroup(PeerGroup group)
{
this.group=group;
pipes=group.getPipeService();
PipeAdvertisement adv=
(PipeAdvertisement) AdvertisementFactory.newAdvertisement(PipeAdvertisement.getAdvertisementType());
PipeID pid=getChatPipeID(group.getPeerGroupID());
adv.setPipeID(pid);
adv.setType(PipeService.PropagateType);
adv.setName("ChatPipe");
DiscoveryService discovery=group.getDiscoveryService();
try
{
discovery.publish(adv, DiscoveryService.ADV);
discovery.remotePublish(adv, DiscoveryService.ADV);
connectPipe(adv);
board.append("nWelcome to group '" + group.getPeerGroupName() + "'n");
} catch (Exception ex)
{
ex.printStackTrace();
}
}

/**
*  @see biz.junginger.jxta.Groups.Listener#createdGroup(net.jxta.peergroup.PeerGroup)
*/
public void createdGroup(PeerGroup group)
{}

private PipeID getChatPipeID(PeerGroupID groupID)
{
byte[] seed=new byte[16];
for (int i=0; i connectPipe(PipeAdvertisement adv) throws java.io.IOException
{
if (inPipe !=null) inPipe.close();
inPipe=null;
if (outPipe !=null) outPipe.close();
outPipe=null;

inPipe=pipes.createInputPipe(adv, this);
outPipe=pipes.createOutputPipe(adv, -1);
}

private void sendMessage() throws IOException
{
if (outPipe==null)
{
board.append("Not connected yet.n");
return;
}
String text=input.getText();
if (text==null || text.trim().length()==0)
return;
input.setText("");
sendMessage(nickname.getText(), text);
}

private void sendMessage(String sender, String text) throws IOException
{
StructuredDocument doc=StructuredDocumentFactory.newStructuredDocument(mimeType, "JXTA-Tutorial:ChatMsg");
doc.appendChild(doc.createElement("Text", text));
doc.appendChild(doc.createElement("Sender", nickname.getText()));

Message msg=pipes.createMessage();
msg.addElement(msg.newMessageElement("ChatMsg", mimeType, doc.getStream()));
outPipe.send(msg);
}

/**
* @see net.jxta.pipe.PipeMsgListener#pipeMsgEvent(PipeMsgEvent)
*/
public void pipeMsgEvent(PipeMsgEvent msg)
{
MessageElement element=msg.getMessage().getElement("ChatMsg");
StructuredDocument doc;
try
{
doc=StructuredDocumentFactory.newStructuredDocument(mimeType, element.getStream());
} catch (Exception ex)
{
ex.printStackTrace();
return;
}

String nick=null;
String text=null;
Enumeration enum=doc.getChildren();
while (enum.hasMoreElements())
{
Element el=(Element) enum.nextElement();
if (el.getKey().equals("Sender"))
nick=(String) el.getValue();
if (el.getKey().equals("Text"))
text=(String) el.getValue();
}
appendMessage(nick, text);
}

private void appendMessage(String nick, String text)
{
board.append("  " + text + "n");
JViewport port=scrollPane.getViewport();
int y=board.getHeight()-board.getVisibleRect().height;
port.setViewPosition(new Point(0, y));
}
}

Die Klasse implementiert zwei Interfaces: Den Listener der zuvor besprochenen Groups-Komponente und den PipeMsgListener, welcher Pipe-Nachrichten entgegennimmt. Die Methode joinedGroup ist Bestandteil des Interface Groups.Listeners, diese kann aber auch explizit aufgerufen werden, womit eine Verwendung ohne die Groups-Komponente möglich wird. Die für die Initialisierung relevante joinedGroup-Methode bereitet in erster Linie die PropagatePipe für die Benutzung vor. Dazu wird zunächst mit Hilfe der AdvertisementFactory ein PipeAdvertisement erstellt, dem nachfolgend Daten hinzugefügt werden. Die PipeID wird durch die Hilfsmethode getChatPipeID erzeugt, um sicherzustellen, dass alle Chat-Teilnehmer innerhalb einer PeerGroup dieselbe PipeID verwenden. Dieser Schritt ist wichtig, da JXTA-Ressourcen durch die ID identifiziert werden. Würde dagegen die ID beispielsweise mit der net.jxta.id.IDFactory erzeugt, würde jeder Chat-Teilnehmer eine neue PropagatePipe erzeugen. In diesem Fall wären alle PropagatePipes vollständig unabhängig von einander und jeder Chat-Teilnehmer würde nur die eigenen Nachrichten empfangen können. Deshalb erzeugt die Methode getChatPipeID die PipeID aus der PeerGroupID und einem statischen Wert.

Nachdem die PipeID, der Typ und der Name für das PipeAdvertisement gesetzt wurden, wird es mit Hilfe des DiscoveryService sowohl lokal als auch im Netzwerk veröffentlicht. Das Verbinden der InputPipe- und OutputPipe-Kanäle der PropagatePipe wird an die Methode connectPipe delegiert. Diese Methode schließt eventuell bestehende Kanäle und kreiert dann neue Kanäle mit den Methoden createInputPipe und createOutputPipe des PipeService, die das vorbereitete PipeAdvertisement als Parameter erwarten. Die Methode createInputPipe benötigt zusätzlich noch einen PipeMsgListener, der aufgerufen wird, wenn eine neue Nachricht eintrifft. Die Methode createOutputPipe benötigt unter Umständen längere Zeit für die Abarbeitung, weshalb ein Timeout-Parameter mitgeliefert wird, der angibt, nach welcher Zeit der Vorgang abgebrochen werden soll. Der hier verwendete Wert (-1) gibt an, dass kein Abbruch erfolgen soll.

Nachdem die OutputPipe initialisiert ist, können Nachrichten versendet werden. Dieser Vorgang wird durch die überladenen Methoden sendMessage realisiert. Die parameterlose Variante wird über einen ActionListener (der in Listing 1 nicht vorhanden ist) aufgerufen, sobald eine Nachricht bereit für den Versand ist. Nachdem die sendMessage-Methode die Existenz der OutputPipe überprüft hat, extrahiert sie den Namen des Chatters und die Nachricht aus dem GUI. Die Daten werden der zweiten sendMessage-Methode übergeben, welche diese in eine JXTA Message verpackt und versendet.

Eine Message besteht aus beliebig vielen MessageElement-Objekten, die jeweils einen eigenen MIME-Typ haben können. In unserem Beispiel erstellen wir eine Message mit einem MessageElement im XML-Format unter Zuhilfenahme der Klasse StructuredDocument aus dem net.jxta.document Package. Die zu versendenden Daten werden als Element dem Dokument mit der Methode appendChild hinzugefügt. Dem mit Hilfe des PipeService erzeugten Message-Objekts wird ein neues MessageElement hinzugefügt. Schließlich kann die Message mit dem Aufruf outPipe.send verschickt werden.

Das Empfangen von Nachrichten kann mit der InputPipe prinzipiell auf zwei Arten erfolgen: Synchron mit den Methoden poll und waitForMessage oder asynchron mit Hilfe eines PipeMsgListener. Das ChatBoard zieht die asynchrone Vorgehensweise einem sonst nötigen und weniger eleganten Polling-Thread vor. Die für den PipeMsgListener nötige Methode pipeMsgEvent extrahiert die Daten aus dem PipeMsgEvent und stellt sie im GUI dar. Als erster Schritt wird aus dem MessageElement der Message ein StructuredDocument erzeugt, welches den Zugriff auf Kindknoten mit der Methode getChildren ermöglicht. Die Enumeration, welche diese Methode zurückgibt, wird in einer Schleife auf relevante Daten hin untersucht. Die gefundenen Daten werden danach an die Methode appendMessage übergeben, die für den Update des GUI zuständig ist. Im Einzelnen wird die Nachricht einem JtextArea-Objekt hinzugefügt und sichergestellt, dass sie im JViewport sichtbar ist. Damit ist der wesentliche Ablauf unserer Chat-Anwendung abgeschlossen.

JSplitPane splitPane=new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, board.getJComponent(), groups.getJComponent());
frame.getContentPane.add(splitPane,BorderLayout.CENTER);

Für Aufgaben, die eine direkte Verwendung der JXTA PeerGroup-Klassen erfordern, sei im Folgenden erklärt, wie dies in der Groups-Komponente realisiert wurde (Listing 2). Das innere Interface Listener dient zur Benachrichtigung, wenn einer PeerGroup beigetreten oder eine neue PeerGroup angelegt wurde. An diesen Meldungen interessierte Komponenten (wie das vorgestellte ChatBoard) müssen einen Listener über die Methode addListener registrieren.

Das Beitreten zu einer JXTA PeerGroup übernimmt die Methode joinGroup. Zunächst wird anhand der Variable membership überprüft, ob noch eine Mitgliedschaft besteht und diese gegebenenfalls aufgelöst. Danach wird aus dem PeerGroupAdvertisement ein PeerGroup-Objekt erstellt, welches Zugriff auf den gruppeneigenen MembershipService bietet. Obwohl bereits ein PeerGroup-Objekt existiert, sind weitere Vorkehrungen für das Beitreten der PeerGroup notwendig. Der im folgenden beschriebene Prozess geht von dem relativ einfachen Fall aus, dass keine Mitgliedschaftsbeschränkungen bestehen. Zuerst wird ein AuthenticationCredential-Objekt erzeugt, mit dem Angaben bezüglich der gewünschten Authentifizierungsmethode gemacht werden können. In unserem Fall übergeben wir null, da damit eine einfache Authentifizierung für unbeschränkte PeerGroups realisiert wird. Mit dem AuthenticationCredential-Objekt wird die Methode membership.apply aufgerufen, die zunächst überprüft, ob die Authentifizierungsmethode für die PeerGroup erlaubt ist. Ist dies der Fall, wird ein Authenticator-Objekt übergeben, sonst wird eine Exception geworfen. Benötigt die Authentifizierungsmethode weitere Daten, werden diese dem Authenticator-Objekt hinzugefügt. Dieses Vorgehen wird in unserem Fall nicht benötigt, weshalb das Authenticator-Objekt unverändert der Methode membership.join übergeben wird. Tritt hier keine Exception auf, wurde der PeerGroup erfolgreich beigetreten. Der letzte Vorgang ist das Aufrufen der joinedGroup-Methode aller registrierten Listener-Objekte.

Die nächste Methode createNewGroup erstellt und veröffentlicht eine neue PeerGroup. Das hierfür benötigte Advertisement wird mit der Methode getAllPurposePeerGroupImplAdvertisement generiert. Das Adververtisement-Objekt wird zusammen mit anderen Parametern der Methode newGroup übergeben, welche eine PeerGroup-Instanz erzeugt. Wird wie hier null als PeerGroupID angegeben, wird eine neue ID für die PeerGroup generiert. Anschließend wird das PeerGroupAdvertisement mit Hilfe des DiscoveryService veröffentlicht. Analog zur vorausgehenden joinGroup-Methode werden schließlich alle registrierten Listener verständigt – an dieser Stelle wird allerdings die Methode createdGroup aufgerufen.

Die letzte vorgestellte Methode der Groups-Klasse ist newAdvertisementFound, welche das Interface biz.junginger.jxta.AdvertisementDiscovery.Listener implementiert. Diese Methode wird von der Hilfsklasse AdvertisementDiscovery aufgerufen, wenn ein neues Advertisement gefunden wurde. Hier wird auf das Ereignis damit reagiert, dass die PeerGroup den internen Datenstrukturen hinzugefügt wird.

public class Groups implements AdvertisementDiscovery.Listener
{
public interface Listener
{
void joinedGroup(PeerGroup group);
void createdGroup(PeerGroup group);
}

public PeerGroup joinGroup(PeerGroupAdvertisement adv) throws PeerGroupException, ProtocolNotSupportedException,IOException
{
//Leave old group
if(membership!=null)
{
membership.resign();
membership=null;
}

//Init and join the group
PeerGroup pg=netGroup.newGroup(adv);
membership=pg.getMembershipService();

AuthenticationCredential authCred=new AuthenticationCredential(pg,null,null);
Authenticator auth=membership.apply(authCred);
Credential myCred=membership.join(auth);

Iterator iter=listeners.iterator();
while (iter.hasNext())
{
Listener listener=(Listener) iter.next();
listener.joinedGroup(pg);
}

return pg;
}

public PeerGroup createNewGroup(String name, String description) throws Exception
{
ModuleImplAdvertisement implAdv=netGroup.getAllPurposePeerGroupImplAdvertisement();
PeerGroup pg=netGroup.newGroup(null, implAdv,name,description);
PeerGroupAdvertisement adv=pg.getPeerGroupAdvertisement();
DiscoveryService discovery=netGroup.getDiscoveryService();
discovery.remotePublish(adv, DiscoveryService.GROUP);

Iterator iter=listeners.iterator();
while (iter.hasNext())
{
Listener listener=(Listener) iter.next();
listener.createdGroup(pg);
}
return pg;
}

public synchronized void newAdvertisementFound(Advertisement adv)
{
PeerGroupAdvertisement groupAdv=(PeerGroupAdvertisement) adv;
String displayStr=groupAdv.getName().substring(prefix.length());
model.addElement(displayStr);
groups.add(groupAdv);
}
}

Die Klasse biz.junginger.jxta.AdvertisementDiscovery (Listing 2) vereinfacht das Auffinden von PeerGroups beziehungsweise entsprechender Advertisements. Diese Klasse wird zwar von der Groups-Komponente verwendet, besitzt aber selbst keine Abhängigkeiten zu weiteren Klassen. Somit kann sie auch einzeln in weiteren Anwendungen benutzt werden.

Grob betrachtet, wird zu einem definierten Intervall nach bestimmten Advertisements gesucht und die Resultate in aufbereiteter Form weitergegeben. Kommen wir aber nun zu den Einzelheiten. Auch hier gibt es ein inneres Interface mit dem Namen Listener. Jedoch erlaubt die Klasse nur ein einziges Listener-Objekt, welches schon dem Konstruktor übergeben werden muss. Des Weiteren benötigt der Konstruktor die PeerGroup, in welcher die Suche stattfinden soll, sowie den Typ (bislang wird nur DiscoveryService.GROUP unterstützt) und ein Namenspräfix. Dieses Präfix dient zur Einschränkung der Suche auf Advertisements, deren Namen damit beginnen. Die run-Methode stellt die Hauptschleife dar, die bis zum Aufruf der shutdown-Methode durchlaufen wird. Pro Schleifendurchlauf wird die Methode sendSearchRequest aufgerufen und dann ein vordefiniertes Zeitintervall gewartet. Innerhalb von sendSearchRequest wird die Suche über die Methode getRemoteAdvertisements des DiscoveryService angestoßen. Hierbei wird die Suche durch Parameter eingeschränkt und das this-Objekt als DiscoveryListener übergeben. Die Methode discoveryEvent sorgt für die Implementierung des DiscoveryListener und wird von JXTA aufgerufen, sobald passende Advertisements gefunden wurden. Die Methode ist hier so implementiert, dass sie lediglich die Enumeration aus dem DiscoveryEvent-Objekt extrahiert. Diese Enumeration, in der Advertisements als String-Objekte gespeichert sind, wird der Methode checkAdvertisements übergeben. Die Methode checkLocal verhält sich ähnlich, bedient sich allerdings der getLocalAdvertisements-Methode des DiscoveryService, um nach lokal gecacheten Advertisements zu suchen, die in einer Enumeration zurückgegeben werden. Dennoch kann auch hier die checkAdvertisements-Methode aufgerufen werden, da sie sowohl String als auch Advertisement-Objekte verarbeiten kann. Intern wird in der checkAdvertisements mit Advertisement-Objekten gearbeitet, die gegebenenfalls aus String-Objekten erzeugt werden. Dieses Vorgehen ist sinnvoll, da somit die Verarbeitung der gefundenen Advertisements unabhängig des Datentyps an einer einzigen Stelle stattfindet. Handelt es sich um ein PeerGroupAdvertisement, überprüft die checkAdvertisements-Methode, ob die PeerGroupID bereits im foundAdvertisementIDs Set enthalten ist. Ist das der Fall, wird das Advertisment nicht weiter untersucht, ansonsten die PeerGroupID dem Set hinzugefügt. Enthält der Name der PeerGroup den gewünschten prefix String, so entspricht das Advertisement allen gewünschten Kriterien und die newAdvertisementFoundMethode des Listeners wird aufgerufen.

public class AdvertisementDiscovery extends Thread implements DiscoveryListener
{
public interface Listener
{
public void newAdvertisementFound(Advertisement adv);
}

public void run()
{
try{
while(!shutdown)
{
sendSearchRequest();
sleep(searchInterval);
}
}
catch(Exception ex) {ex.printStackTrace();}
}

public void shutdown() {shutdown=true;}

public void sendSearchRequest()
{
try
{
DiscoveryService discovery=peerGroup.getDiscoveryService();
discovery.getRemoteAdvertisements( null,type,"Name",prefix+"*", 20,this);
}
catch(Exception ex) {ex.printStackTrace();}
}

public void discoveryEvent(DiscoveryEvent event)
{
DiscoveryResponseMsg response=event.getResponse();
Enumeration enum=response.getResponses();
checkAdvertisements(enum);
}

public void checkLocal() throws Exception
{
DiscoveryService discovery=peerGroup.getDiscoveryService();
Enumeration enum=discovery.getLocalAdvertisements(type,"Name",prefix+"*");
checkAdvertisements(enum);
localSearched=true;
}

private void checkAdvertisements(Enumeration enum)
{
while (enum.hasMoreElements())
{
try
{
Object obj=enum.nextElement();
Advertisement adv=null;

if(obj instanceof String)
{
InputStream is=new ByteArrayInputStream(((String)obj).getBytes());
adv=(Advertisement) AdvertisementFactory.newAdvertisement(new MimeMediaType("text/xml"),is);
}
else if(obj instanceof Advertisement) adv=(Advertisement) obj;

if(adv instanceof PeerGroupAdvertisement)
{
PeerGroupAdvertisement groupAdv=(PeerGroupAdvertisement)adv;
PeerGroupID gid=groupAdv.getPeerGroupID();
synchronized (this)
{
if (foundAdvertisementIDs.contains(gid)) continue;
foundAdvertisementIDs.add(gid);
}
if(!groupAdv.getName().startsWith(prefix)) continue;

listener.newAdvertisementFound(groupAdv);
}
//Other Advertisements could be handled here
}
catch (Exception ex)
{
System.err.println("Exception occured while parsing advertisement:");
ex.printStackTrace();
}
}
}
}

Die in diesem Tutorial verwendete JXTA-Version ist die aktuelle Stable Release vom 24.09.2002. Die für Ende Januar geplant nächste stabile Version soll einige Verbesserungen bieten. Jedoch werden diese Versionen nicht miteinander kommunizieren können, da das Protokoll modifiziert wurde.

Bei der hier verwendeten Version können je nach Konfiguration Fehler auftreten. Zum einen kann manchmal keine Verbindung zu einem Rendezvous hergestellt werden und zum anderen werden die Messages nur an einen einzigen PropagatePipe Listener gesendet. Diese Fehler treten vor allem dann auf, wenn der Rechner mehrere IP-Adressen besitzt. Hier hilft in vielen Fällen, die öffentliche IP-Adresse explizit in der JXTA-Konfiguration anzugeben. Um den Konfigurationsdialog erneut sichtbar zu machen, muss eine Datei mit dem Namen reconf im .jxta-Ordner erzeugt werden. Bei Verbindungsproblemen mit einem lokalen Rendezvous kann ein Neustart des Rendezvous helfen.

JXTA Konzepte

Der Quellcode der vorgestellten Chat-Anwendung sowie ausführbare Dateien sind online sowie auf der Magazin-CD verfügbar. Soll die Anwendung auf einem lokalen Rechner getestet werden, muss JXTA speziell konfiguriert werden. Es ist bei der Konfiguration erforderlich, allen Peers unterschiedliche Ports zuzuweisen, um Konflikte zu vermeiden. Des Weiteren ist es nötig, dass alle Peers in einem eigenen Verzeichnis ausgeführt werden und der Rendezvous Peer zuerst gestartet wird. Weitere Hilfestellungen hierzu finden Sie in der Readme-Datei des downloadbaren Paketes.

Damit ist der erste Teil des JXTA-Tutorials abgeschlossen. Der zweite Teil im nächsten Java Magazin wird auf die Problematik komplexerer verteilter Anwendung eingehen.

Geschrieben von
Markus Oliver Junginger
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.