View Javadoc
1   /*
2    * junixsocket
3    *
4    * Copyright 2009-2024 Christian Kohlschütter
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.newsclub.net.unix.demo;
19  
20  import java.io.DataInputStream;
21  import java.io.DataOutputStream;
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.nio.charset.StandardCharsets;
27  
28  import org.newsclub.net.unix.AFUNIXServerSocket;
29  import org.newsclub.net.unix.AFUNIXSocket;
30  import org.newsclub.net.unix.AFUNIXSocketAddress;
31  import org.newsclub.net.unix.SocketClosedException;
32  
33  /**
34   * A simple demo server.
35   *
36   * Sends a hello message (as a string), then reads back a response string.
37   *
38   * Finally, sends integers (via {@link DataOutputStream}) from 1 to 5, expects an integer response
39   * of twice the sent value each, then sends a "-123" magic number to indicate the end of the
40   * conversation.
41   *
42   * @author Christian Kohlschütter
43   * @see SimpleTestClient
44   */
45  @SuppressWarnings({"CatchAndPrintStackTrace" /* errorprone */, "PMD.CognitiveComplexity"})
46  public final class SimpleTestServer {
47    private static final int MAX_NUMBER = 5;
48  
49    private SimpleTestServer() {
50      throw new UnsupportedOperationException("No instances");
51    }
52  
53    public static void main(String[] args) throws IOException {
54      final File socketFile = new File(new File(System.getProperty("java.io.tmpdir")),
55          "junixsocket-test.sock");
56      System.out.println(socketFile);
57  
58      try (AFUNIXServerSocket server = AFUNIXServerSocket.newInstance()) {
59        /*
60         * Uncomment the code below to change the bind behavior:
61         *
62         * By default ("reuseAddress" is true), attempting to bind while another server is running on
63         * the same address will cause the first server to terminate, and the new server will take
64         * over the address. Depending on the operating system, this may involve connecting to the
65         * first server in order to "wake up" the accept call.
66         *
67         * In this demo code, we use AFSocket.getConnectionStatus to see if the accepted connection is
68         * alive by sending
69         *
70         * When "reuseAddress" is false, attempting to bind while another server is running won't
71         * disrupt the first connection. The second bind will throw a SocketException instead.
72         *
73         * NOTE: "reuseAddress=true" may not yet be supported on certain operating systems, such as
74         * IBM i and z/OS. On these platforms, the behavior is as if "reuseAddress=false". Please
75         * reach out by filing an issue on https://github.com/kohlschutter/junixsocket/issues if this
76         * is a problem for you.
77         */
78        // server.setReuseAddress(false);
79  
80        server.bind(AFUNIXSocketAddress.of(socketFile));
81        System.out.println("server: " + server);
82  
83        while (!Thread.interrupted() && !server.isClosed()) {
84          System.out.println("Waiting for connection...");
85  
86          boolean remoteReady = false;
87          try (AFUNIXSocket sock = server.accept();
88              InputStream is = sock.getInputStream();
89              OutputStream os = sock.getOutputStream();
90              DataOutputStream dout = new DataOutputStream(os);
91              DataInputStream din = new DataInputStream(is);) {
92            remoteReady = true;
93            System.out.println("Connected: " + sock);
94  
95            // This check is optional. Without it, the below write may throw a "Broken pipe" exception
96            // if the remote connection was closed right after connect.
97            //
98            // The check involves sending a zero-byte message to the peer, and catching exceptions
99            // as we go. Note that this is deliberately not automated to allow code perform and detect
100           // "(port) knocking".
101           if (sock.checkConnectionClosed()) {
102             System.out.println("Peer closed socket right after connecting");
103             continue;
104           }
105 
106           System.out.println("Saying hello to client " + sock);
107           os.write("Hello, dear Client".getBytes(StandardCharsets.UTF_8));
108           os.flush();
109 
110           byte[] buf = new byte[128];
111           int read = is.read(buf);
112           System.out.println("Client's response: " + new String(buf, 0, read, "UTF-8"));
113 
114           System.out.println("Now counting to " + MAX_NUMBER + "...");
115           int number = 0;
116           while (!Thread.interrupted()) {
117             number++;
118             System.out.println("write " + number);
119             dout.writeInt(number);
120             try {
121               Thread.sleep(1000);
122             } catch (InterruptedException e) {
123               e.printStackTrace();
124               break;
125             }
126             if (number >= MAX_NUMBER) {
127               System.out.println("write -123 (end of numbers)");
128               dout.writeInt(-123); // in this demo, -123 is our magic number to indicate the end
129               break;
130             }
131 
132             // verify the number from the client
133             // in the demo, the client just sends 2 * our number
134             int theirNumber = din.readInt();
135             System.out.println("received " + theirNumber);
136             if (theirNumber != (number * 2)) {
137               throw new IllegalStateException("Received the wrong number: " + theirNumber);
138             }
139           }
140         } catch (SocketClosedException e) {
141           if (!remoteReady) {
142             // ignore -- the remote connection terminated during accept or when trying to get the
143             // input/output streams
144           } else {
145             // unexpected
146             e.printStackTrace();
147           }
148         } catch (IOException e) {
149           // unexpected
150           e.printStackTrace();
151         }
152       }
153     } finally {
154       System.out.println("Server terminated");
155     }
156   }
157 }