1/*
2 * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25 *
26 *
27 * An "echo" service designed to be used with inetd. It can be configured in
28 * inetd.conf to be used by any of the following types of services :-
29 *
30 *      stream  tcp   nowait
31 *      stream  tcp6  nowait
32 *      stream  tcp   wait
33 *      stream  tcp6  wait
34 *      dgram   udp   wait
35 *      dgram   udp6  wait
36 *
37 * If configured as a "tcp nowait" service then inetd will launch a
38 * VM to run the EchoService each time that a client connects to
39 * the TCP port. The EchoService simply echos any messages it
40 * receives from the client and shuts if the client closes the
41 * connection.
42 *
43 * If configured as a "tcp wait" service then inetd will launch a VM
44 * to run the EchoService when a client connects to the port. When
45 * launched the EchoService takes over the listener socket. It
46 * terminates when all clients have disconnected and the service
47 * is idle for a few seconds.
48 *
49 * If configured as a "udp wait" service then a VM will be launched for
50 * each UDP packet to the configured port. System.inheritedChannel()
51 * will return a DatagramChannel. The echo service here will terminate after
52 * echoing the UDP packet back to the client.
53 *
54 * The service closes the inherited network channel when complete. To
55 * facilate testing that the channel is closed the "tcp nowait" service
56 * can close the connection after a given number of bytes.
57 */
58import java.io.IOException;
59import java.net.SocketAddress;
60import java.nio.ByteBuffer;
61import java.nio.channels.Channel;
62import java.nio.channels.DatagramChannel;
63import java.nio.channels.SelectionKey;
64import java.nio.channels.Selector;
65import java.nio.channels.ServerSocketChannel;
66import java.nio.channels.SocketChannel;
67
68public class EchoService {
69
70    private static void doIt(SocketChannel sc, int closeAfter, int delay) throws IOException {
71        ByteBuffer bb = ByteBuffer.allocate(1024);
72        int total = 0;
73        for (;;) {
74            bb.clear();
75            int n = sc.read(bb);
76            if (n < 0) {
77                break;
78            }
79            total += n;
80
81            // echo
82            bb.flip();
83            sc.write(bb);
84
85            // close after X bytes?
86            if (closeAfter > 0 && total >= closeAfter) {
87                break;
88            }
89        }
90
91        sc.close();
92        if (delay > 0) {
93            try {
94                Thread.currentThread().sleep(delay);
95            } catch (InterruptedException x) { }
96        }
97    }
98
99    private static void doIt(DatagramChannel dc) throws IOException {
100        ByteBuffer bb = ByteBuffer.allocate(1024);
101        SocketAddress sa = dc.receive(bb);
102        bb.flip();
103        dc.send(bb, sa);
104        dc.close();
105    }
106
107
108    // A worker thread to service a single connection
109    // The class maintains a count of the number of worker threads so
110    // can the service can terminate then all clients disconnect.
111
112    static class Worker implements Runnable {
113        private static int count = 0;
114        private static Object lock = new Object();
115
116        public static int count() {
117            synchronized (lock) {
118                return count;
119            }
120        }
121
122        private SocketChannel sc;
123
124        Worker(SocketChannel sc) {
125            this.sc = sc;
126            synchronized (lock) {
127                count++;
128            }
129        }
130
131        public void run() {
132            try {
133                doIt(sc, -1, -1);
134            } catch (IOException x) {
135            } finally {
136                synchronized (lock) {
137                    count--;
138                }
139            }
140
141        }
142    }
143
144    public static void main(String args[]) throws IOException {
145        Channel c = System.inheritedChannel();
146        if (c == null) {
147            return;
148        }
149
150        // tcp nowait
151        if (c instanceof SocketChannel) {
152            int closeAfter = 0;
153            int delay = 0;
154            if (args.length > 0) {
155                closeAfter = Integer.parseInt(args[0]);
156            }
157            if (args.length > 1) {
158                delay = Integer.parseInt(args[1]);
159            }
160            doIt((SocketChannel)c, closeAfter, delay);
161        }
162
163        // tcp wait - in this case we take over the listener socket
164        // In this test case we create a thread to service each connection
165        // and terminate after all clients are gone.
166        //
167        if (c instanceof ServerSocketChannel) {
168            ServerSocketChannel ssc = (ServerSocketChannel)c;
169
170            ssc.configureBlocking(false);
171            Selector sel = ssc.provider().openSelector();
172            SelectionKey sk = ssc.register(sel, SelectionKey.OP_ACCEPT);
173            SocketChannel sc;
174            int count = 0;
175            for (;;) {
176                 sel.select(5000);
177                 if (sk.isAcceptable() && ((sc = ssc.accept()) != null)) {
178                    Worker w = new Worker(sc);
179                    (new Thread(w)).start();
180                 } else {
181                     // if all clients have disconnected then we die as well.
182                     if (Worker.count() == 0) {
183                        break;
184                     }
185                 }
186            }
187            ssc.close();
188        }
189
190        // udp wait
191        if (c instanceof DatagramChannel) {
192            doIt((DatagramChannel)c);
193        }
194
195        // linger?
196        if (args.length > 0) {
197            int delay = Integer.parseInt(args[0]);
198            try {
199                Thread.currentThread().sleep(delay);
200            } catch (InterruptedException x) { }
201        }
202
203    }
204
205}
206