distributed-socket.html 8.35 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<p>Eine sehr grundlegende Methode zur bidirektionalen Interprozesskommunikation zwischen verteilten Software-Komponenten ist die Verwendung von <i>Internet-Sockets</i>. Ein Internet-Socket ist ein interner Endpunkt aus Sicht einer Anwendung zum Senden und Empfangen von Daten in einem Netzwerk auf Basis des Internet-Protokolls (IP). Der Begriff <i>Socket</i> selbst geht urspünglich auf den <a href="https://tools.ietf.org/html/rfc147">RFC 147</a> von 1971 zurück, der sich noch auf das ARPA-Netzwerk bezieht. Ein Socket wird einer Anwendung vom Betriebssystem auf Anforderung unter einem bestimmten Port bereitgestellt. Das Betriebssystem verwaltet die aktuell aktiven, lokalen Sockets und die zugehörigen Remote-Sockets (IP-Adressen und Ports). Internet-Sockets lassen sich in <i>Stream Sockets</i> (TCP) und <i>Datagram Sockets</i> (UDP) unterscheiden. Im folgenden soll die <a href="https://docs.oracle.com/javase/tutorial/networking/sockets/readingWriting.html">Java-API für Stream Sockets</a> vorgestellt werden, die im Package <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/package-summary.html"><code>java.net</code></a> implementiert und damit Bestandteil des Moduls <code>java.base</code> ist.</p>

<p>Das Code-Beispiel zeigt eine einfache Chat-Anwendung ähnlich des Beispiels im Kapitel <a href="#unit-websocket" class="navigate">WebSockets</a>.</p>

<ul class="nav nav-tabs" id="socket-tabs" role="tablist">
  <li class="nav-item"><a href="#socket-tabs-server" class="nav-link active" data-toggle="tab" role="tab">ChatServer</a></li>
  <li class="nav-item"><a href="#socket-tabs-serverhandler" class="nav-link" data-toggle="tab" role="tab">BroadcastMessageHandler</a></li>  
  <li class="nav-item"><a href="#socket-tabs-handler" class="nav-link" data-toggle="tab" role="tab">MessageHandler</a></li>
  <li class="nav-item"><a href="#socket-tabs-client" class="nav-link" data-toggle="tab" role="tab">ChatClient</a></li>  
</ul>
<div class="tab-content" id="socket-tabs-content">
  <div class="tab-pane show active" id="socket-tabs-server" role="tabpanel">
	<pre><code class="language-java line-numbers">import java.net.ServerSocket;
import java.net.Socket;
import java.io.BufferedReader;
import java.io.PrintWriter;
// ...

public class ChatServer {

    static Set&lt;PrintWriter> clientWriters = new HashSet<>();

    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(5000);
			
            while (true) {
                // wait for new client connection
                Socket clientSocket = serverSocket.accept();
                System.out.println(">>> Client connected from " + clientSocket.getRemoteSocketAddress());

                // create output writer
                PrintWriter writer = new PrintWriter(clientSocket.getOutputStream());
                clientWriters.add(writer);

                // create input reader
                BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

                // start concurrent message handler
                new Thread(new BroadcastMessageHandler(reader)).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void broadcast(String message) {
        for (PrintWriter writer : clientWriters) {
            writer.println(message);
            writer.flush();
        }
    }
}</code></pre>  
  </div>
  <div class="tab-pane" id="socket-tabs-serverhandler" role="tabpanel">
	<pre><code class="language-java line-numbers">public class BroadcastMessageHandler extends MessageHandler {

    public BroadcastMessageHandler(BufferedReader reader) {
        super(reader);
    }

    @Override
    public void onMessage(String message) {
        super.onMessage(message);
        ChatServer.broadcast(message);
    }
}</code></pre>  
  </div>   
  <div class="tab-pane" id="socket-tabs-handler" role="tabpanel">
	<pre><code class="language-java line-numbers">public class MessageHandler implements Runnable {

    BufferedReader reader;

    public MessageHandler(BufferedReader reader) {
        this.reader = reader;
    }

    @Override
    public void run() {
        String message;
        try {
            while ((message = reader.readLine()) != null) {
                onMessage(message);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void onMessage(String message) {
        System.out.println(">>> Received: '" + message + "'");
    }
}</code></pre>  
  </div> 
  <div class="tab-pane" id="socket-tabs-client" role="tabpanel">
	<pre><code class="language-java line-numbers">public class ChatClient {

    static Socket socket;
    static PrintWriter writer;

    public static void main(String[] args) {
        try {
            // connect to server
            socket = new Socket("127.0.0.1", 5000);
            System.out.println(">>> Client connected using port " + socket.getLocalPort());			

            // create output writer
            writer = new PrintWriter(socket.getOutputStream());

            // create input reader
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            // start concurrent message handler
            new Thread(new MessageHandler(reader)).start();

        } catch (IOException e) {
            e.printStackTrace();
        }

        // Wait for messages to send to the server
        while (true) {
            System.out.println(">>> Write your message:");
            Scanner scanner = new Scanner(System.in);
            writer.println(scanner.next());
            writer.flush();
        }
    }
}</code></pre>  
  </div>
</div> 

<ul>
<li><b>ChatServer</b>: Das serverseitige Socket wird in Zeile 13 unter Port 5000 geöffnet. In der folgenden Endlosschleife wartet der Server auf Clients, die eine Verbindung aufbauen möchten (Zeile 17). Für jeden neuen Client wird ein <code>PrintWriter</code> (Zeile 21) und ein <code>BufferedReader</code> (Zeile 25) erzeugt, um neue Nachrichten über einen <code>OutputStream</code> zu senden bzw. über einen <code>InputStream</code> zu empfangen. Anschließend wird ein <code>BroadcastMessageHandler</code> erzeugt und an einen nebenläufigen Thread übergeben (Zeile 28).</li>
<li><b>BroadcastMessageHandler</b>: Der <code>BroadcastMessageHandler</code> ist ein spezieller <code>MessageHandler</code>, der seine Oberklasse nur dahingehend erweitert, dass er beim Empfang einer Nachricht, die Methode <code>ChatServer.broadcast</code> aufruft. In dieser Methode wird eine eingehende Nachricht an alle Clients über deren jeweiligen <code>PrintWriter</code> verteilt.
<li><b>MessageHandler</b>: Der allgemeinere <code>MessageHandler</code> implementiert das Interface <code>Runnable</code> (s. Kapitel <a href="#unit-threads" class="navigate">Threads in Java</a>). In der Methode <code>run</code> wird in Zeile 13 in einer Endlosschleife auf eingehende Nachrichten gewartet. Diese werden in der Methode <code>onMessage</code> verarbeitet (Zeilen 21-23).</li>
<li><b>ChatClient</b>: Der Client versucht sich in Zeile 9 mit dem serverseitigen Socket zu verbinden. Im Erfolgsfall wird ein clientseitiges Socket zurückgegeben, für das das Betriebssystem einen freien Port auswählt (Zeile 10). Wie beim Server werden anschließend ein <code>PrintWriter</code> und ein <code>BufferedReader</code> erzeugt sowie ein <code>MessageHandler</code> in einem nebenläufigen Thread gestartet. Ab Zeile 26 beginnt eine Endlosschleife, in der auf Eingaben des Anwenders gewartet wird, die als Nachrichten an den Server versendet werden (Zeilen 29-30).</li>
</ul>

<p>Über einen <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/ObjectOutputStream.html"><code>ObjectOutputStream</code></a> können auch Objekte versendet werden, die das <code>Serializable</code>-Interface unterstützen. Sender und Empfänger müssen die entsprechenden Klassen zu den versendeten Objekten untereinander austauschen. Der Empfänger kann über <code>instanceof</code> ermitteln, von welchem Typ das empfangene Objekt ist.
<pre><code class="language-java line-numbers">Object object = inputStream.readObject();
if(object instanceof Message) {
	Message message = (Message) object;
}</code></pre>

<p>Die gezeigten Code-Beispiele zu Sockets finden sich im Verzeichnis <a href="/remote/socket" class="repo-link">/remote/socket</a> des Modul-Repository.</p>