news, hacks, programme, logs,
tipps und tricks rund um das internet

Artikel / Tipps und Tricks  
Java Redirector - playing with java.nio.*

Mit dem Java Port Redirector lassen sich Ports umleiten. Es ist eine Art Proxy, wobei die Hauptaufgabe das Anzeigen ist. So erfährt man exakt wie viel Bytes übertragen werden. Wer sich den Soruce Code genauer anschaut, merkt dass es nur einen einzigen Thread gibt. Mit java.io geht das nicht deshalb wird java.nio verwendet.

Autor: tom - Date: 11.27.2003 - Size: 9856 chars - Hits / Day: 6.29 - Total Hits: 16164
 Download  Redirect-1.0beta2.jar   Size: 55 kb Datum: 23.11.2003
Der Redirector läuft zur Zeit nur mit dem <b>HTTP</b> Protokoll, denn die Ersetzungen die gemacht werden müssen können nicht in einem Stream gemacht werden, bei dem man nicht weiss wann der zu Ende ist. Das Ende des Streams wird durch ein <b>rnrn</b> gekennzeichnet.

Der Port Redirector ist einfach zu handhaben. Es müssen nur 5 Felder ausgefüllt werden:

1. Remote Host. Hier kann man den gewünschten Host eingeben. Z.B. www.nope.tv
2. Remote Port. Der Port auf den man zugreifen will. Standardmässig ist der www Port auf 80 gesetzt.
3. Local Port. Der Port auf den man lokal zugreifen will. Z.B. 80

Diese Aufgaben könnten auch von einem PortMapper erledigt werden. Da aber viele Domains nur über einen virtuelllen Host erreichbar sind müssen noch einige Ersetzungen gemacht werden. Das Problem ist dass im Header der Anfrage folgendes übertragen wird:

GET / HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 ...
Accept: text/xml,applic ...
Accept-Language: en,de- ...
Accept-Encoding: gzip,d ...
Accept-Charset: ISO-885 ...
Keep-Alive: 300
Connection: keep-alive

Das Problem ist „Host: localhost“. Der Webserver schaut in seinen virtuellen Domains nach ob es eine localhost hat, und wenn nicht, dann kommt irgendeine Seite. Wenn keine virtuellen Domains auf dem Server existieren, kann man sich das Ersetzten sparen. Ansonsten muss folgendes zusätzlich eingetragen werden:

4. Replace this. Das was zu ersetzten ist. Z.B. localhost
5. Replace with. Mit was 4. ersetzt werden soll. Z.B. www.nope.tv

<!i0left>Nachdem man „new“ gedrückt hat, ist diese Ersetzung aktiviert. Nun kann man in einem <b> beibiegen Browser </b> die Seite <!l>http://localhost<!/l> angeschauen. Solange keine Ressourcen (Bilder, externe JavaScript Files, CSS, etc.) auf dem Remote Host absolut angegeben wurden, kann die komplette Seite über localhost angeschaut werden.

Bei "Total Output" und "Total Input" kann man sehen wie viel Bytes tatsächlich übertragen wurden. Ich habe dabei festgestellt dass Mozilla ca. 5kb Header- Informationen schickt, wobei der Internet Explorer bloss 3kb schickt.

Pro Verbindung wird ein Tab geöffnet. Wie man aus dem Bild sehen kann sind bei einer Anfrage gleich zwei Verbindungen aufgemacht worden. Das könnte auch nur eine Verbindung sein oder aber auch mehr als zwei. Das hängt von der Grösse der Seite und von der Geschwindigkeit der Verbindung ab. Unten in der Statusbar sieht man ob die Verbindung noch aktiv ist. Wenn man sich das Verhalten genauer anschaut merkt man, dass nach einigen Sekunden Die Verdingung deaktiviert wird. Das ist das so genannte "Keep-alive". Wird in dieser Zeit noch eine Anfrage gestartet kann man die schon offene Verbindung benutzten und man spart sich den langen Verbindungsaufbau.

Die nächste Seite befasst sich mit der Implementation.<!p>
Das Konzept des select() Operators ist nicht ganz neu: Es ist ein fester Bestandteil des Linux Kernels. Bei diesem Konzept meldet man die gewünschten Events. Sobald dieser Event eintrifft wird man benachrichtigt. Das erlaubt "Non-Blocking" Operationen.

Der Server läuft mit nur einen einzigen Thread und alle Aufrufe sind „Non-Blocking“. Der Aufruf des Threads ist ziemlich simpel:

private void start()
{

sl = new ServerListener(data, dp1, dp2);
sl.start();

}

Danach öffnen wir einen Selektor und informieren den Selektor, dass wir uns für den Event OP_ACCEPT interessieren.

Selector selectorServer = Selector.open();
ServerSocketChannel serverchannel = ServerSocketChannel.open();
serverchannel.configureBlocking(false);
InetSocketAddress address = new InetSocketAddress(this.localport);
ServerSocket ss = serverchannel.socket();
ss.bind(address);
serverchannel.register(selectorServer, SelectionKey.OP_ACCEPT);
socketAddress = new InetSocketAddress(remotehost, remoteport);

Nun kommt der interessante Teil; Die while Schlaufe in unserem Thread. Ich habe aus bequemlichkeit die Kommentare in den Code geschrieben, dadurch erscheint der Code etwas länger ;)

while (running && selectorServer.isOpen())
{
//warte höchstens 1000ms bis was passiert, wenn nichts passiert bleibt num=0
num = selectorServer.select(1000);
//wir iterieren über die gewünschten Events
Iterator it = selectorServer.selectedKeys().iterator();
while (it.hasNext())
{
//Der Event auf den wir warten
SelectionKey key = (SelectionKey) it.next();
//Der Event muss gelöscht werden, denn sonst löscht ihn niemand
it.remove();
if (key.isAcceptable())
{
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
//wir akzeptieren die Verbindung
SocketChannel sc = ssc.accept();
//wir wollen einen „Non Blocking“ channel
sc.configureBlocking(false);
// wir interessieren uns für das was gelesen wird
sc.register(selectorServer, SelectionKey.OP_READ);
//Establish connection to the remote server
SocketChannel channel = SocketChannel.open();
//wir wollen einen “Non Blocking” channel
channel.configureBlocking(false);
//Verbindung zum Remote Host
channel.connect(socketAddress);
//auch hier interessiert uns was gelesen wird.
channel.register(selectorServer, SelectionKey.OP_READ);
}
else if (key.isReadable())
{
// Read the data
SocketChannel sc = (SocketChannel) key.channel();
if (connectionPacket1.containsKey(key.channel()))
{
SocketChannel channel = (SocketChannel) connectionPacket1.get(sc);
proxyClientBuffer.clear();
...
try
{
//IE macht die Verbindung zu
nr = sc.read(proxyClientBuffer);
}
catch (IOException ioe)
{...}
if (nr < 0)
{
//Mozilla sendet ein EOF
...
}
else
{
proxyClientBuffer.flip();
//ersetze den Text
SearchClass searchclass = new SearchClass(hm);
ByteBuffer tmp_Buffer = null;
//create new Buffer only when end is reached
if (searchclass.add(proxyClientBuffer))
tmp_Buffer = searchclass.print();
/*
* Da wir im „Non Blocking“ Modus sind muss es ein Timeout geben,
* sonst könnte es zu einer Endlosschlaufe führen. Ein
* if (sc.isConnectionPending()) sc.finishConnect() genügt nicht da
* sc.finishConnect() in jedem Fall sofort wieder zum Aufrufer
* zurück kehrt (Non Blocking).
*/
waitForConnection(1000, channel);
//write Buffer
channel.write(tmp_Buffer);
}
}
else if (connectionPacket2.containsKey(key.channel()))
{...}
}
}

Was ist nun der Unterschied zwischen java.io.* und java.nio.*? Nun als erstes gibt es „Non Blocking“ Aufrufe. Zweitens ist die Performance enorm gesteigert worden, da nun nicht mehr mit Streams gearbeitet wird, sondern mit Buffers. Buffers sind schneller als Streams da es bei allen modernen OS einen internen (native) Aufruf gibt. Streams konnte man optimieren indem man den Stream Buffern (BufferedInputStream) konnte. Die Performance reicht aber nicht aus um mit Buffers mitzuhalten.

Unterschied Buffer - Streams

OuputStream <- bytes <- InputStream
InputStream -> bytes -> OuputStream

Channel <-> Buffer <-> Channel


Buffers sind ein bisschen gewöhnungsbedürftig. Sie können gelesen <b>und</b> beschrieben werden. Neben read und write gibt es noch flip und clear. Es gibt drei Variablen bei einem Buffer: position, limit, capacity

Bei einem write wir immer in die nächste position geschrieben solange bis limit erreicht ist -> position ++
Bei einem read wird immer solange von einer position gelesen bis limit erreicht ist -> position ++
Bei einem clear wird position auf 0 gesezte und limit auf capacity
Bei einem flip wird limit auf position und position auf 0 gesetzt. Mit flip lässt sich so von read zu write und umgekehrt wechseln.

Zudem gibt es weitere interessante java.nio Anwendungen. So gibt es z.B. auch einen FileChannel. Dadurch lässt sich sogar ein File direkt als Buffer darstellen. Wird was in diesem Buffer verändert, ändert sich auch das File. Ausserdem lassen sich die Buffers direkt über JNI ansprechen, somit kann man verhindern dass eine Kopie der Daten, die Übergeben werden, erstellt wird.

Weitere gute Links und Literatur

<!l>http://developer.java.sun.com/developer/technicalArticles/releases/nio/<!/l>
<!l>http://java.sun.com/j2se/1.4.2/docs/api/java/nio/package-summary.html<!/l>
<!l>http://www.javaworld.com/javaworld/jw-04-2003/jw-0411-select.html<!/l>
<!l>http://www.javaworld.com/javaworld/jw-08-2002/jw-0802-performance.html<!/l>

 Home
Artikel / Tipps und Tricks
Artikel Navigation
Internes
0 Artikel
    Links and Friends
    5 Links
    Artikel erscheinen ungefähr jeden Monat in unregelmässigen Abständen.

    RSS-feed: rss.xml
    Nope bietet keinerlei Garantie für die auf dieser Seite enthaltenen Tipps, Artikel und Programme. Für Schaden, der aus der Anwendung der hier enthaltenen Daten entsteht, übernehmen wir keine Haftung. Die Nope Homepage enthält Links zu anderen Seiten auf deren Inhalte wir keinen Einfluss haben. Wir übernehmen keine Verantwortung für fremde Inhalte und distanzieren uns ausdrücklich davon.© nope.tv. Die Artikel auf dieser Seite, soweit nicht anders im Artikel erwähnt, stehen unter CreativeCommons License. Die Programme auf dieser Seite, soweit nicht anders im Artikel oder Programm erwähnt, stehen unter der GNU Public License (GPL).