3. Netzwerk-Programmierung


Mehrprozessorsysteme ohne gemeinsamen Hauptspeicher benötigen spezielle Leitungen zur Kommunikation.

Das Schema des Nachrichtenaustauschs ist in Abbildung 2 dargestellt.

Abbildung 2: Schema des Nachrichtenaustauschs


3.1 Sockets

Spezifikation von ServerSocket:

   public ServerSocket(int port) throws IOException 
   public Socket accept() throws IOException

Spezifikationen von Socket

   public Socket(String host, int port)
          throws UnknownHostException, IOException
   public InputStream getInputStream() throws IOException
   public OutputStream getOutputStream() throws IOException

Abbildung E: UML Socket
UML Socket

Zuordnung von passenden Datenströmen:

Spezifikation der benötigten Konstruktoren und Methoden

   public ObjectOutputStream(OutputStream out) throws IOException 
   public void flush() throws IOException
   public ObjectInputStream(InputStream in)
          throws IOException, StreamCorruptedException

Abbildung F: UML Serialization
UML Serialization

Datenübertragung mit Send-Operation ( send) und Empfangs-Operation ( recieve).

Spezifikation aus ObjectOutputStream

   public final void writeObject(Object obj)
          throws IOException

Spezifikation aus ObjectInputStream

   public final Object readObject()
          throws OptionalDataException, 
                 ClassNotFoundException, IOException

3.2 Beispiel SocketChannel

Der Konstruktor nimmt einen Socket als Eingabe und setzt die Objekt-Ströme aus den entsprechenden Strömen auf.

Die Methode close() dient zum schließen des Kanals.

send()- und receive()-Methoden dienen der Datenübertragung

   import java.io.*;
   import java.net.*;
   
   public class SocketChannel {

     private ObjectInputStream in;
     private ObjectOutputStream out;
   
     private Socket soc;
   
     public SocketChannel(Socket s) throws IOException {
       soc = s;
       if (checkOrder(s)) {
         in = new ObjectInputStream(s.getInputStream());
         out = new ObjectOutputStream(
                   s.getOutputStream());
       }
       else {
         out = new ObjectOutputStream(
                   s.getOutputStream());
         in = new ObjectInputStream(s.getInputStream());
       }
     }

     public void close() {
       if (in != null) {
          try { in.close(); } catch (IOException e) { }
       }
       if (out != null) {
          try { out.close(); } catch (IOException e) { }
       }
       if (soc != null) {
          try { soc.close(); } catch (IOException e) { }
       }
     }
   
     private boolean checkOrder(Socket s) 
                     throws IOException {
       int p1 = s.getLocalPort();
       int p2 = s.getPort();
       if (p1 < p2) return true;
       else if (p1 > p2) return false;
   
       int a1 = s.getLocalAddress().hashCode();
       int a2 = s.getInetAddress().hashCode();
       if (a1 < a2) return true;
       else if (a1 > a2) return false;
   
       throw new IOException(); // this shouldn't happen
     }
   }

Die Sende- und Empfangs-Methoden sind wie folgt.

     public void send(Object v) throws IOException {
         out.writeObject(v);
     }
Im Fehlerfall wird eine IOException ausgelöst, die dann vom Aufrufer behandelt werden muß.

     public Object receive() 
                   throws IOException, 
                          ClassNotFoundException {
       return in.readObject();
     }
Neben einer IOException kann auch noch eine ClassNotFoundException ausgelöst werden, falls das Objekt zu einer dem Empfänger unbekannten Klasse gehört.

3.2a Beispiel HelloWorld mit SocketChannel

 

ExCon

Abbildung A1:HelloWorld

Implementierung: HelloWorldClient.java HelloWorldServ.java SocketChannel.java

3.3 Beispiel Kanalfabrik

Zwei Grundideen zur Lösung

Abbildung G: UML ChannelFactory
UML ChannelFactory

Der Konstruktor der Klasse ChannelFactory erzeugt einen Server-Socket und startet sich dann selbst als Thread.

   import java.io.*;
   import java.net.*;
   
   public class ChannelFactory extends Thread {
     
     public final static int DEFAULT_PORT = 4711;
     private int port;
   
     private BoundedBuffer buf = new BoundedBuffer(10);
   
     private ServerSocket srv = null;
   
     public ChannelFactory(int p) {
       if (p<=0) { port = DEFAULT_PORT; } 
          else { port = p; }
       try {
           srv = new ServerSocket(port);
           this.start();
           System.out.println(
                  "server started on port "+port);
       } catch (IOException e) 
               { System.out.println(e); }
     }
   
     public ChannelFactory() {
       this(DEFAULT_PORT); 
     }
     public void run() {
       while (true) {
         try {
             System.out.println(
                 "waiting for connection");
             Socket s = srv.accept();
             System.out.println("connection accepted");
             SocketChannel c = new SocketChannel(s);
             buf.put( (Object) c );
         } catch (IOException e) {
           System.out.println(e);
         }
       }
     }
   
     public SocketChannel getChannel() 
            throws IOException {
       return (SocketChannel)buf.get();
     }

     public SocketChannel getChannel(String h, int p) 
            throws IOException {
       if (p<=0) { p = port; } 
       SocketChannel c = null;
       System.out.println("connecting to "+h);
       while (c == null) {
           try { c = new SocketChannel( 
                     new Socket(h, p) );
           } catch (IOException e) {
             System.out.println(e);
             // wait server ready
             System.out.println("Server on "+h+
                    " not ready");
             try {
                 Thread.currentThread().sleep(
                        (int)(5000));
             } catch (InterruptedException e1) { } 
           }
       }
       System.out.println("connected");
       return c;
     }
   }

ChannelFactory löst das Reihenfolge-Problem und das Deadlock-Problem.

Das Reihenfolge-Problem wird durch Warten und Neuversuch in der Methode getChannel(String h, int p) gelöst.

Die Lösung des Deadlock-Problems wie folgt.

Wir setzen voraus, daß für jeden Prozeß genau einmal der Konstruktor ChannelFactory() und für jeden Kanal genau einmal die Methode getChannel() und die Methode getChannel(String h, int p) aufgerufen wird.

Betrachten wir die drei Programmzustände:
  1. Aufruf der Konstruktoren ChannelFactory(). Damit wird ein Thread gestartet, der Verbindungsanfragen vom Server-Socket entgegennimmt und speichert.

  2. Aufruf von getChannel(String h, int p) auf allen Client-Seiten der Kanäle. Dabei wird die Verbindung zu Sockets aus Punkt 1 hergestellt. Falls diese noch nicht bereit sind, wird gewartet.

  3. Aufruf von allen getChannel() auf den Server-Seiten der Kanäle. Dabei werden nur noch die gespeicherten Verbindungen zurückgegeben. Falls noch keine gespeichert sind, wird gewartet.

Kompliziertere Verbindungsstrukturen mit virtuellen Kanälen oder PVM oder MPI.

3.3a Beispiel HelloWorld mit ChannelFactory

 

ExCon

Abbildung A1:HelloWorld

Implementierung: HelloWorldClientCF.java HelloWorldServCF.java ChannelFactory.java


© Universität Mannheim, Rechenzentrum, 2000-2002.

Last modified: Wed Oct 5 12:04:35 CEST 2005