Inhalt |
|
|
|
|
|
|
|
|
|
|
|
|
Vielleicht sollten wir zuerst versuchen, Dateien auf der lokalen Festplatte zu finden und zu bearbeiten. Dazu verwendet man ein File-Objekt. Die Behandlung und Darstellung von Dateinamen ist systemspezifisch. Java versucht daher, Details zu abstrahieren. Immerhin bietet die Klasse File dennoch Methoden um nach Dateien zu suchen, sowie Dateinamen darauf zu untersuchen, ob die Dateien les- und schreibbar sind, oder ob der Name gar einem Directory gehört.
Im folgenden Beispiel finden wir alle Dateien mit der Endung .txt. Man könnte dazu auch das Interface FilenameFilter verwenden. Es geht aber auch so. Erstellen Sie zum Ausprobieren eine Datei file.txt mit dem Inhalt
Erste Zeile äöüß
und vielleicht irgend eine zweite Datei mit der Endung .txt.
import java.io.*; public class Test { public static void main (String args[]) { // get the file.txt file File file=new File("file.txt"); if (file.exists() && !file.isDirectory()) { System.out.println("Datei "+file.getName()+" gefunden"); System.out.println("Voller Name: "+file.getAbsolutePath()); } // get all text files File dir=new File("."); String[] list=dir.list(); for (int i=0; i<list.length; i++) if (list[i].endsWith(".txt")) { long length=new File(dir,list[i]).length(); // get length System.out.println(list[i]+" ["+length+"]"); } } }
Beachten Sie, dass das Paket java.io importiert werden muss. Das Programm gibt den vollen Pfad der Testdatei aus und dann alle Textdateien mit ihrer Länge. Dazu verwendet es die list-Methode von File. Diesmal muss der Dateiname ein Directory sein. Wir verwenden das aktuelle Directory, das gewöhnlich mit einem . bezeichnet wird. Zu jedem gefundenen Namen wird dann ein File-Objekt erzeugt, dessen Methode length die Länge der Datei ergibt.
Nachdem wir wissen, dass eine Datei test.txt existiert, versuchen wir ihren Inhalt zu lesen.
Dazu verwenden wir einen InputStream, genauer einen FileInputStream. Wir wollen die Datei zunächst zeilenweise lesen. Dazu erzeugen wir einen Stream, der das kann, nämlich den BufferedReader aus unserem FileInputStream. Der Zwischenschritt InputStreamReader wird weiter unten erklärt. Dieser Reader besitzt eine Methode readLine, die null zurückgibt, wenn die Datei zu Ende ist.
import java.io.*; public class Test { public static void main (String args[]) { try { BufferedReader in= new BufferedReader( new InputStreamReader( new FileInputStream("file.txt"))); while (true) { String s=in.readLine(); if (s==null) break; System.out.println(s); } } catch (Exception e) { System.out.println(e); } } }
Erstellt man die Datei unter Windows und lässt das Programm in einer DOS-Box von Windows 95 laufen, ist die Ausgabe der deutschen Sonderzeichen äöüß nicht korrekt. Dies liegt daran, dass DOS die Zeichen anders kodiert als Windows. Wir werden dieses Manko später beheben.
Die Klasse InputStream (und damit auch FileInputStream) besitzt auch die Methode read zum Lesen eines Bytes. Die Methode available liefert die Anzahl der verfügbaren Zeichen.
import java.io.*; public class Test { public static void main (String args[]) { try { InputStream in=new FileInputStream("file.txt"); while (in.available()>0) { System.out.print((int)in.read()+" "); } in.close(); } catch (Exception e) { System.out.println(e); } System.out.println(); } }
Die Ausgabe ist
69 114 115 116 101 32 90 101 105 108 101 13 10 246 228 252 223 13 10
69 steht dabei für E, das erste Zeichen in der Datei. Das Zeichen 246 ist tatsächlich das korrekte Unicode-Zeichen für ä. Dies liegt aber nur daran, daß die ersten 254 Zeichen des europäischen Zeichensatzes (ISO 8859_1) mit den entsprechenden Unicode-Zeichen übereinstimmen. Erstellt man die gleiche Datei unter DOS, so sieht das Ergebnis anders aus.
69 114 115 116 101 32 90 101 105 108 101 13 10 132 148 129 225 13 10
Dies ist offenbar nicht dasselbe und auch unter Windows nicht korrekt. Benutzt man etwa ein koreanisches System und gibt dort die lokalen Sonderzeichen ein, so ist die Übersetzung in Unicode völlig falsch.
Man beachte auch, dass unter Windows das Zeilenende durch 13 und 10 dargestellt wird. Unter Unix wird nur die 10 verwendet, und auf Apple-Rechnern gar nur die 13.
Mit Java stehen nun Klassen zur Verfügung, die eine Übersetzung der lokalen Codierung in Unicode erlauben. Dies sind die Reader-Klassen. Die Aufgabe wird, wie schon im ersten Beispiel, durch den InputStreamReader erledigt.
import java.io.*; public class Test { public static void main (String args[]) { try { Reader in=new BufferedReader( new InputStreamReader( new FileInputStream("file.txt"))); while (in.ready()) { int c=in.read(); if (c==-1) break; System.out.print(c+" "); } in.close(); } catch (Exception e) { System.out.println(e); } System.out.println(); System.out.println(System.getProperty("file.encoding")); } }
Die Ausgabe dieses Programms ist zunächst absolut identisch. Dies liegt an der Übereinstimmung des europäischen, und damit für uns lokalen Zeichensatzes mit Unicode. Man kann aber nun auch die unter DOS erstellte Datei korrekt lesen, indem man die Kodierung explizit angibt.
{ Reader in=new BufferedReader( new InputStreamReader( new FileInputStream("file.txt"),"Cp850"));
CP850 steht für Codepage 850. Dies ist die unter DOS normalerweise in Deutschland eingestellte Codepage (siehe autoxec.bat).
Zur Ausgabe, und damit zur Übersetzung von Unicode in das lokale Format, verwendet man PrintWriter, der aus einem OutputStreamWriter konstruiert werden kann.. Hier ist ein Beispiel, das die Datei file.txt vom DOS-Format (Codepage 850) in das lokale Format konvertiert. Man kann beim Konstruktor von OutputStreamWriter ein weiteres Argument angeben, das das Encoding bei der Ausgabe spezifiziert. So kann man etwa von Windows nach DOS kopieren.
Die Codepage, die ein deutsches Windows NT verwendet, ist übrigens Cp1252. Sie ist per Default eingestellt.
import java.io.*; public class Test { public static void main (String args[]) { try { InputStream is=new FileInputStream(args[0]); BufferedReader in=new BufferedReader( new InputStreamReader(is,"Cp850")); OutputStream os=new FileOutputStream(args[1]); PrintWriter out=new PrintWriter( new OutputStreamWriter(os)); while (true) { String s=in.readLine(); if (s==null) break; out.println(s); } in.close(); out.close(); } catch (Exception e) { System.out.println(e); } } }
Man beachte immer, dass Dateien geschlossen werden müssen, um anderen Programmen zur Verfügung zu stehen.
Will man in Java Bytes ausgeben, so kann man DataOutputStream.write() direkt verwenden.
Eines der Designziele von Java war die Programmierung von Netzanwendungen. Java bringt daher eine Reihe von Klassen zur Kommunikation über TCP/IP mit. Eine davon bringt die Möglichkeit, eine Datei direkt von einem WWW-Server zu lesen. Das folgende Beispiel kopiert eine Datei byteweise vom Internet.
Wir werden die Befehlszeile
java Test http://mathsrv.ku-eichstaett.de/MGF/homes/grothmann/java/kurs/index.html index.html
verwenden, die das Netzdokument an der entsprechenden URL auf die lokale Datei mgf.html kopieren soll. Netzdokumente werden über eine URL (uniform ressource locator) angesprochen. Die erste Zeichenkette steht dabei für das verwendete Protokoll, in diesem Fall HTTP. Dies URLs sind ja schon aus den Adressangaben im Browser bekannt. Allerdings muss hier das Protokoll immer durch http:// spezifiziert werden.
Um die URL in Java zu verwalten, verwendet man einen der möglichen Konstruktoren der Klasse URL. In diesem Fall geben wir die URL vollständig wie in einem Browser ein. Es ist aber auch möglich, relative Adressen zu verwenden, etwa
URL dir=new URL("http://mathsrv.ku-eichstaett.de/MGF/homes/grothmann/java/kurs"); URL url=new URL(dir,"index.html");
Man kann auch die URL nach dem Erzeugen analysieren, etwa mit den Methoden getProtocol(), getHost() oder getFile(). Es folgt der Code für die Übertragung der Datei aus dem Netz.
import java.io.*; import java.net.*; public class Test { public static void main (String args[]) { get(args[0],args[1]); } static void get (String urltag, String filename) { try { URL url=new URL(urltag); InputStream in=url.openStream(); int b; FileOutputStream out=new FileOutputStream(filename); while (true) { b=in.read(); if (b==-1) break; out.write(b); } in.close(); out.close(); } catch (Exception e) { System.out.println(e); } } }
Bei einem Applet muss beachtet werden, dass es nur von dem Server lesen kann, von dem es geladen wurde. Meist muss man die Datei von derselben Stelle aus laden, wo die Klassendatei auf dem Server liegt. Dazu verwendet man folgenden Code
BufferedReader in=new BufferedReader(new DataInputStream( (new URL(getCodeBase(),filename)).openStream()));
getCodeBase ist eine Methode von Applet, die eine URL auf die Adresse zurückgibt, von der das Applet geladen wurde. Der zusätzliche Parameter bei new URL() spezifiziert die Datei, die zu laden ist.
Es ist allerdings bequemer, solche Dateien zusammen mit den Klassen in ein Archiv zu packen und mit dem Applet direkt zu übertragen. Die Dateien werden dabei als Ressource angesprochen.
Sockets dienen zur Kommunikation über das Internet. Gewöhnlich wartet ein Server auf Verbindungen und die sogenannten Clients nehmen Kontakt mit dem Server auf. Jeder Computer hat mehrere Ports, an denen er Verbindungen aufnehmen kann. Ein Port kann mehrere Verbindungen gleichzeitig bedienen. Ein Server wartet aber immer nur auf einem Port. Auf einem Port kann auch nur ein Server warten. Versucht man, einen Port zu besetzen, auf dem schon ein anderer Server wartet, so wird eine Exception ausgelöst.
Ein Beispiel für einen Port ist 23, der Telnet-Port. Der Server, der dort wartet, ist gewöhnlich ein Programm names telnetd (für Telnet Dämon). Andere Ports sind für FTP (file transfer protocol), SMTP (simple mail transfer protocol), POP (post office protocol) etc. reserviert. Für jeden dieser Port wartet ein Dämon-Prozess im Hintergrund darauf, Verbindungen aufzunehmen. Typischerweise heißen diese Prozesse telnetd, ftpd, smtpd usw.
Wir wollen als erstes einen solchen Dämon schreiben. Dazu werden wir auf einem Port auf eine Verbindung warten, und bei Kontaktaufnahme einfach alle Eingaben zurückgeben (außer QUIT, was die Kommunikation beenden soll). Dies ist sozusagen ein Echo-Dämon.
Das Programm wird zunächst einen Socket erzeugen, der auf einem Port wartet. Die Ports bis 1000 sind für definierte Zwecke reserviert. Wir verwenden einfach den Port 6000. Sollte dieser Port schon von einem anderen Dämon besetzt sein, wird es eine BindException geben.
Falls ein Programm mit dem Socket Kontakt aufnimmt, so starten wir einen Thread, der die Verbindung aufnimmt. Dazu dient eine Instanz der Klasse echod, die ein Runnable implementiert. Diese Klasse besorgt sich Ein- und Ausgabeströme zu dem Client, der die Verbindung aufgenommen hatte. Dies geschieht mit den Methoden getInputStream und getOutputStream. Wir machen daraus wieder einen BufferedReader und einen PrintWriter. Letzter bekommt aber im Konstruktor noch einen zusätzlichen Paramter true, der angibt, dass alle Ausgaben ungepuffert sofort übertragen werden sollen. Sonst müßten wir nach jeder Zeile die Methode flush von PrintWriter aufrufen, um den Puffer zu leeren.
Nebenbei verwenden wir als weitere Neuerung die Ausgabe auf System.err, die für Fehlermeldungen gedacht ist. Normalerweise erscheinen diese Fehlermeldungen ebenfalls auf der Komandozeile.
Man beachte, dass das Hauptprogramm main gleich in der Klasse echod integriert ist.
import java.net.*; import java.io.*; public class echod implements Runnable { PrintWriter Out; BufferedReader In; public static void main (String args[]) { try // generiere wartenden Socket { ServerSocket server=new ServerSocket(6000); while (true) // Läßt sich nur durch CNTRL-C abbrechen { Socket socket=server.accept(); // Socket wartet new echod(socket); // Verbindung gefunden } } catch (Exception e) { System.err.println(e); } } public echod (Socket socket) { try // generiere Ein- und Ausgabestreams und einen Thread { Out=new PrintWriter( new DataOutputStream(socket.getOutputStream()),true); In=new BufferedReader(new InputStreamReader( new DataInputStream(socket.getInputStream()))); new Thread(this).start(); // starte eine Thread (und damit run()) } catch (Exception e) { System.err.println(e); } } public void run () // der Thread, der eine Verbindung verwaltet. { try { while (true) // Echo-Schleife { String s=In.readLine(); if (s.equals("QUIT")) break; Out.println("Echo: "+s); } // Streams immer schließen: In.close(); Out.close(); } catch (Exception e) { System.out.println(e); } } }
Die Kommunikation erfolgt hier synchron. Der Server liest immer eine Zeile und schreibt seine Antwort zurück. Viele Protokolle beruhen auf solchen synchronen Antworten. Im Prinzip könnte das Protokoll auch asynchron laufen. Der Server würde dann an einer beliebigen Stelle senden können.
Sie können das obige Programm mit einem Telnet-Client testen. Dazu geben Sie einfach
telnet localhost 6000
ein, nachdem in einer anderen Kommandozeile der Echo-Server mit
java echod
gestartet wurde. Nun können Sie blind eine Zeile schreiben und der Server wird die Zeile zurückgeben. Man kann aber auch das lokale Echo in Telnet einstellen und ist dann in der Lage zu lesen, was man eingibt.
Wir wollen jedoch ein Javaprogramm schreiben, das mit dem Server Kontakt aufnehmen kann. Ein solches Programm kann im Prinzip synchron arbeiten. Es würde dann immer eine Zeile einlesen und eine Zeile empfangen.
Wir wollen aber ein asynchrones Programm schreiben. Dazu generieren wir einen separaten Thread, der die Eingaben entgegen nimmt. Alternativ kann man nach jeder Eingabe auf eine Antwort des Servers warten. Dies würde eine synchrone Kommunikation voraussetzen. Eine solche Methode ist geeignet, wenn man genau weiß, was der Server senden muss.
import java.net.*; import java.io.*; public class client implements Runnable { PrintWriter Out; BufferedReader In; boolean Closed; public static void main (String args[]) { try { Socket socket=new Socket("localhost",6000); new client(socket); // Starte Kommunikation } catch (Exception e) { System.err.println(e); } } public client (Socket socket) // Öffnet die Ströme und einen Lesethread { try { Out=new PrintWriter( new DataOutputStream(socket.getOutputStream()),true); In=new BufferedReader(new InputStreamReader( new DataInputStream(socket.getInputStream()))); Thread t=new Thread(this); // Der Lesethread Closed=false; t.start(); // Mache aus System.in einen Eingabestrom: BufferedReader in=new BufferedReader( new InputStreamReader(System.in)); while (!Closed) { String s=in.readLine(); // Lies Zeile vom Terminal if (s==null || s.equals("quit")) break; Out.println(s); // Gib sie wieder aus } t.stop(); // Stoppe den Lesethread Out.close(); } catch (Exception e) { System.err.println(e); } } public void run () // Der Lesethread { try { while (true) { String s=In.readLine(); // Lies vom Server if (s==null) break; System.out.println("Server :"+s); // Schreibe ins Terminal } In.close(); Closed=true; // Benachrichtigt den Schreibthread } catch (Exception e) { System.err.println(e); } } }
Dabei tritt ein Synchronisationsproblem auf bezüglich des Endes der Kommunikation. In unserem Fall wird durch die Eingabe von QUIT die Kommunikation vom Server getrennt (siehe Porgramm oben) und durch Eingabe von quit vom Client.
Sie können das Programm auch mit ihrem SMTP-Server testen. Dazu tragen Sie den Servernamen und den Port 25 in main ein und starten den Client. Dann führt man mit dem Server folgenden Dialog. (Die Ausgaben des Servers sind mit Server: gekennzeichnet).
D:\java\rene\kurs>java client Server :220 eo-dec-mathsrv.ku-eichstaett.de ESMTP Sendmail ... HELO test Server :250 eo-dec-mathsrv.ku-eichstaett.de Hello ... pleased to meet you MAIL FROM: grothm@ku-eichstaett.de Server :250 grothm@ku-eichstaett.de... Sender ok RCPT TO: grothm@ku-eichstaett.de Server :250 grothm@ku-eichstaett.de... Recipient ok DATA Server :354 Enter mail, end with "." on a line by itself test . Server :250 LAA28080 Message accepted for delivery QUIT Server :221 eo-dec-mathsrv.ku-eichstaett.de closing connection
Der Server wird die Mail an die angegebene Adresse schicken. Dies ist ein typisches Beispiel für ein synchrones Protokoll. FTP, POP etc. sind ähnlich aufgebaut.