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 }