Socket

Netzwerkadressen

Wir wollen mit Java Verbindung aufnehmen mit andern Programmen auf andern Computern. Im ersten Schritt befassen wir uns mit den Adressen der Computer im Netz:

  • Erstellen Sie ein neues Projekt z.B. mit dem Namen SocketTest und ich würde vorschlagen vom Typ Basic Java Application - so können Sie die Tests gleich aus einem Grafikfenster heraus durchführen (ist etwas aufwändiger, aber nicht viel).
  • Die Klassen für Netzwerkbelange sind im Package java.net, also ergänzen Sie in SocketTextFrame die Import Statemenst mit

    import java.net.*;
  • Wir brauchen eine Taste um die Verbindungsaufnahme auszulösen und ein Label um die Ausgaben darzustellen. Damit auf beides aus der ganzen Klasse zugegriffen werden kann, deklarieren Sie beides vor dem Konstruktor:

    Label label;
    Button taste;
  • Die Instanz der beiden GUI Elemente erstellen wir im Konstruktor:

    label = new Label("Start");
    add(label);

    taste = new Button("verbinden");
    add(taste, BorderLayout.SOUTH);
  • Und auch der Action Listener für die Taste kommt in den Konstruktor:

    // Add action listener.
    taste.addActionListener
    (
      new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          verbindung();
        }
      }
    );

  • Sie sehen, dass ich vorgesehen habe, dass beim Drücken der Taste eine Methode verbindung() ausgeführt wird. Also deklarieren wir diese Methode (nach dem Ende des Konstruktors!):

    // Verbindung:
    void verbindung() {
      try {
        String s = "Adresse von Localhost: ";
        InetAddress address = InetAddress.getLocalHost();
        s = s + address.getHostName();
        s = s + " = " + address.getHostAddress();
        label.setText(s);
      } catch (UnknownHostException e) {
        label.setText("Hostadresse unbekannt");
      }

    }

    Hier sind nun die Dinge, welche auf das Netzwerk zugreifen. Dazu einige Erklärungen:
  1. Auffällig ist aber zuerst das Wort try: Immer dann, wenn bei der Ausführung von Anweisungen ein Fehler (heisst in Java Exception) auftreten könnte, brauchen wir einen try - catch Block: In der geschweiften Klammer hinter try stehen die Anweisungen, bei denen was schief gehen könnte, hinter catch steht in der runden Klammer was schief gehen könnte, also die Exception und in der geschweiften Klammer dahinter, was zu tun ist, wenn etwas schief geht.
  2. Wir machen einen String namnes s, in welchem wir den Text speichern, welcher im Label erscheinen soll.
  3. Wir machen eine Instanz einer Internet - Adresse (Klasse InetAddress) namens address. Wir machen Sie nicht mit new, sondern mit der Methode getLocalHost von InetAddress, welche die Adresse des Computers liefert, auf welchem unser Programm läuft.
  4. Sie wissen, jeder Computer, der an einem Netz hängt, hat eine numerische Adresse (IP Adresse). Diese liefert uns die Methode getHostAddress().
  5. Viele Computer haben auch einen Namen. Diesen liefert die Methode getHostName().
  6. Name und Adresse werden an den String s gehängt und dann mit label.setText() im Label dargestellt.
  • Testen Sie das Programm. Sie sollten nach dem Drücken der Taste im Label Name und IP Adresse Ihres Computers sehen.

 

 
 
  • Testen Sie auch die Adresse eines andern Computers:

    String h = "www.dgb.ch";
    String s = "Adresse von "+h+": ";
    InetAddress address = InetAddress.getByName(h);
  • Sie sehen, dass jedem Namen eine IP Adresse zugeordnet ist. Für diese Zuordnung ist das DNS (Domain Name System) zuständig. Domain Name Server können zu Domainnamen die entsprechenden IP Adressen liefern. Das ist bei der Ausführung Ihers Programms geschehen (siehe Bild rechts).
  • Eigentlich sollte auch das umgekehrte klappen: Sie geben mit h die IP Adresse an und erhalten den enstprechenden Namen zurück. Bei mir zu Hause hat das für das lokale Gerät funktioniert, für www.dgb.ch aber nicht. Weiss nicht warum! Eventuell versuchen Sie's auch mit unserem Server im LAN, er hat die IP Adresse 10.1.1.253. Die Namensauflösung ist äusserst komplex, Java verwendet dazu Angaben des lokalen Betriebssystems, sucht nach Domaninnameserver, etc. Genaueres finden Sie in der Dokumentation zu InetAddress.
 

Soviel zu den Adressen. Jetzt wollen wir eine Verbindung herstellen. So wie im Computer der Mikroprozessor in einen Sockel gesteckt wird, damit er mit dem Rest des Computers kommunizieren kann (Bild rechts), so "stecken" wir auch unser Programm in einen Sockel (Socket), damit es mit dem Netz kommunizieren kann - und zwar mit irgend einem andern Socket:

  • Wir erzeugen eine Instanz der Klasse Socket:

    void verbindung() {
      try {
        String h = "www.dgb.ch";
        String s = "Verbinden zu " + h;
        InetAddress address = InetAddress.getByName(h);
        Socket sock = new Socket(address,80);
        label.setText(s);
      } catch (UnknownHostException e) {
        label.setText("Hostadresse unbekannt");
      } catch (IOException e) {
        label.setText("IO Exception!");
      }

    }


    Auch hier wieder einige Erklärungen:
  1. Über die Socket Verbindung werden Daten transferiert, ganz ähnlich wie wenn Java in eine Datei schreibt oder von einer solchen liest. Dabei kann es eine IOException geben (IO steht für Input - Output), deshalb muss diese Exception gecatcht werden. Aber Achtung alles zu IO ist im Package java.io, also auch dieses impoertieren!
  2. Im Konstruktor von sock steht als Parameter neben der Adresse die Zahl 80. Sie gibt den Port an, durch welchen wir die Verbindung zum Gerät herstellen. Gemäss Standard antworten Webserver über den Port 80.
  • Testen Sie! Es kann sein, dass Sie zu www.dgb.ch nicht verbinden können, wegen unserem Firewall. Dann verbinden Sie zu 10.1.1.253, auch hier wird ein Webserver betrieben.

Jetzt sollen Daten zu und vom Server geschickt werden. Dies geschieht durch einen Stream: Über einen InputStream holt eine Java Applikation Daten, über einen OutputStream schickt sie Daten. Einen OutputStream können wir an einen PrintStream weiterleiten, welcher Dinge wie Zeilenvorschübe etc. produzieren kann. Ebenso kann ein DataInputstream ganze Zeilen lesen. So können übrigens ganze Ketten von Streams gebildet werden. Im Folgenden schicken wir mit println() dem Webserver den HTTP Befehl GET, welcher ihn anweist, eine Webseite zurückzuschicken. Das ist der häufigste Befehl, den auch ein Browser dem Webserver schickt. Anschliessend lesen wir das, was der Webserver schickt mit readLine(). Im Folgenden wird die erste Zeile der Hauptseite des Webservers 10.1.1.253 im lokalen Netz gelesen. Diese Seite heisst default.htm. Oft heissen Hauptseiten auch index.htm oder index.html.

  • Ergänzen Sie folgendermassen und testen Sie:

    DataInputStream in = new DataInputStream(sock.getInputStream());
    PrintStream out = new PrintStream(sock.getOutputStream());
    out.println("GET /default.htm");
    s = in.readLine();
    label.setText(s);
  • Schaffen Sie es mehrere Zeilen zu lesen und darzustellen? Dazu sollten Sie das Label in eine TextArea abändern, denn das Label ist nur einzeilig. Eine neue Zeile erhalten Sie, indem Sie die Zeilenfolge "\n" schicken.

Jetzt soll eine Java Applikation mit einer andern kommunizieren. Dazu muss die eine ein Server sein (wie ein Webserver) und die andere ein Client (wie bisher). Zum Test lassen Sie beides, Client und Server, auf dem selben PC laufen. Später können Sie dann die beiden auf verschiedenen Maschinen laufen lassen.

  • Erzeugen Sie wieder ein neues Projekt, z.B. namens ServerTest vom Typ Empty Java Project. Erstellen Sie die neue Datei ServerTest.java und fügen Sie sie dem Projekt zu.
  • Die neue Quelldatei sieht folgendermassen aus:

    import java.net.*;
    import java.io.*;
    public class ServerTest {
      public static void main(String args[]) throws IOException {
        System.out.println("Server started ...");
        ServerSocket servSock = new ServerSocket(80);
        for (;;) {
          Socket clntSock = servSock.accept();
          System.out.println("Client accepted!");
          clntSock.close();
        }
      }
    }

Dazu wieder Erklärungen:

  1. Wir importieren wieder .net und .io wie bisher.
  2. Um einen try - catch Block zu vermeiden, können wir auch einfach sagen, dass die Methode main() ev. eine IOException "werfen" wird (throws).
  3. Diesmal machen wir nicht einen normalen, sondern einen ServerSocket, welcher wie ein Webserver über Port 80 kommuniziert.
  4. In einer unendlichen Schlaufe ( for(;;)! ) wartet der ServerSocket auf einen "Anruf" mit der Methode accept(). Die Methode erzeugt eine Instanz von Seocket, über welche dann mit dem Client kommuniziert werden kann.
  5. Hier melden wir auf der Konsole einfach, dass ein Client akzeptiert wurde und brechen die Verbindung mit close() wieder ab.
  • Übersetzen Sie und führen Sie dann das Programm nicht aus der IDE, sondern aus einem DOS Fenster aus ("Start", "Programme", "MS DOS Eingabeaufforderung", ServerTest.class suchen und ausführen mit java ServerTest).
  • Lassen Sie das DOS Fenster offen, gehen Sie zurück in die IDE, machen Sie das Projekt SocketTest aktiv, stellen Sie die Verbindung zu Ihrem Gerät her (10.1.1.x), übersetzen Sie und führen Sie aus. Das Programm wird wohl hängen bleiben, weil es vom Server keine Antwort kriegt. Mit der Tastenkombination <Ctrl>+<c> können Sie das Programm abbrechen.
  • Schauen Sie im DOS Fenster des Servers nach, ob er ihren Anruf akzeptiert hat. Stoppen Sie den Server mit <Ctrl>+<c>.
  • Eventuell testen Sie jetzt ob die Kommunikation auch von einem Gerät zum ander funktioniert. Vielleicht ist es sowieso einfacher, wenn Sie auf einem Gerät den Client und auf dem andern den Server betreiben, dann müssen Sie nicht immer das aktive Projekt wechseln.
  • Sie sollten den Server jetzt noch etwas intelligenter machen. Wir brauchen wieder Input- und OutputStream und machen in diesem Falle einfach nur ein Echo für genau eine Zeile:

    public static void main(String args[]) throws IOException {
      System.out.println("Server started ...");
      ServerSocket servSock = new ServerSocket(80);
      for (;;) {
        Socket clntSock = servSock.accept();
        System.out.println("Client accepted!");
        DataInputStream in = new     DataInputStream(clntSock.getInputStream());
        PrintStream out = new     PrintStream(clntSock.getOutputStream());
        String s = in.readLine();
        out.println(s);
        clntSock.close();
      }
    }
  • Übersetzen und starten Sie den Server wieder. Ändern Sie den Client so, dass er eine Zeile schickt und eine liest.

Jetzt ist der Kommunikation zwischen zwei Computern keine Grenze gesetzt! Es könnten ja auf beiden Server laufen. Das Gerät, welches als erstes die Verbindung aufbaut wird dann zum Client. Es müsste dann wohl an beiden Orten je ein Fenster für den Dialog und eines für die Eingabe geben - und fertig ist das Chatprogramm... Fast, denn ein richtiges Chatprogramm sieht in regelmässigen Abständen nach ob was von der Gegenseite kommt. Ein Ablauf in regelmässigen Abständen wird in Java mit einem Thread realisiert. Siehe dazu im Kapitel Threads.