• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2011 Oracle and/or its affiliates. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  *   - Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  *
11  *   - Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  *
15  *   - Neither the name of Oracle nor the names of its
16  *     contributors may be used to endorse or promote products derived
17  *     from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 /*
33  * This source code is provided to illustrate the usage of a given feature
34  * or technique and has been deliberately simplified. Additional steps
35  * required for a production-quality application, such as security checks,
36  * input validation and proper error handling, might not be present in
37  * this sample code.
38  */
39 
40 
41 import java.io.IOException;
42 import java.net.InetSocketAddress;
43 import java.net.SocketAddress;
44 import java.net.StandardSocketOptions;
45 import java.nio.channels.*;
46 import java.util.*;
47 import java.util.concurrent.Executors;
48 import java.util.concurrent.TimeUnit;
49 
50 /**
51  * Implements a chat server, this class holds the list of {@code clients} connected to the server.
52  * It sets up a server socket using AsynchronousServerSocketChannel listening to a specified port.
53  */
54 public class ChatServer implements Runnable {
55     private final List<Client> connections = Collections.synchronizedList(new ArrayList<Client>());
56     private int port;
57     private final AsynchronousServerSocketChannel listener;
58     private final AsynchronousChannelGroup channelGroup;
59 
60     /**
61      *
62      * @param port to listen to
63      * @throws java.io.IOException when failing to start the server
64      */
ChatServer(int port)65     public ChatServer(int port) throws IOException {
66         channelGroup = AsynchronousChannelGroup.withFixedThreadPool(Runtime.getRuntime().availableProcessors(),
67                 Executors.defaultThreadFactory());
68         this.port = port;
69         listener = createListener(channelGroup);
70     }
71 
72     /**
73      *
74      * @return The socket address that the server is bound to
75      * @throws java.io.IOException if an I/O error occurs
76      */
getSocketAddress()77     public SocketAddress getSocketAddress() throws IOException {
78         return listener.getLocalAddress();
79     }
80 
81     /**
82      * Start accepting connections
83      */
run()84     public void run() {
85 
86         // call accept to wait for connections, tell it to call our CompletionHandler when there
87         // is a new incoming connection
88         listener.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
89             @Override
90             public void completed(AsynchronousSocketChannel result, Void attachment) {
91                 // request a new accept and handle the incoming connection
92                 listener.accept(null, this);
93                 handleNewConnection(result);
94             }
95 
96             @Override
97             public void failed(Throwable exc, Void attachment) {
98             }
99         });
100     }
101 
102     /**
103      * Shuts down the server
104      * @throws InterruptedException if terminated while waiting for shutdown
105      * @throws IOException if failing to shutdown the channel group
106      */
shutdown()107     public void shutdown() throws InterruptedException, IOException {
108         channelGroup.shutdownNow();
109         channelGroup.awaitTermination(1, TimeUnit.SECONDS);
110     }
111 
112     /*
113     * Creates a listener and starts accepting connections
114     */
createListener(AsynchronousChannelGroup channelGroup)115     private AsynchronousServerSocketChannel createListener(AsynchronousChannelGroup channelGroup) throws IOException {
116         final AsynchronousServerSocketChannel listener = openChannel(channelGroup);
117         listener.setOption(StandardSocketOptions.SO_REUSEADDR, true);
118         listener.bind(new InetSocketAddress(port));
119         return listener;
120     }
121 
openChannel(AsynchronousChannelGroup channelGroup)122     private AsynchronousServerSocketChannel openChannel(AsynchronousChannelGroup channelGroup) throws IOException {
123         return AsynchronousServerSocketChannel.open(channelGroup);
124     }
125 
126     /**
127      * Creates a new client and adds it to the list of connections.
128      * Sets the clients handler to the initial state of NameReader
129      *
130      * @param channel the newly accepted channel
131      */
handleNewConnection(AsynchronousSocketChannel channel)132     private void handleNewConnection(AsynchronousSocketChannel channel) {
133         Client client = new Client(channel, new ClientReader(this, new NameReader(this)));
134         try {
135             channel.setOption(StandardSocketOptions.TCP_NODELAY, true);
136         } catch (IOException e) {
137             // ignore
138         }
139         connections.add(client);
140         client.run();
141     }
142 
143     /**
144      * Sends a message to all clients except the source.
145      * The method is synchronized as it is desired that messages are sent to
146      * all clients in the same order as received.
147      *
148      * @param client the message source
149      * @param message the message to be sent
150      */
writeMessageToClients(Client client, String message)151     public void writeMessageToClients(Client client, String message) {
152         synchronized (connections) {
153             for (Client clientConnection : connections) {
154                 if (clientConnection != client) {
155                     clientConnection.writeMessageFrom(client, message);
156                 }
157             }
158         }
159     }
160 
removeClient(Client client)161     public void removeClient(Client client) {
162         connections.remove(client);
163     }
164 
usage()165     private static void usage() {
166         System.err.println("ChatServer [-port <port number>]");
167         System.exit(1);
168     }
169 
main(String[] args)170     public static void main(String[] args) throws IOException {
171         int port = 5000;
172         if (args.length != 0 && args.length != 2) {
173             usage();
174         } else if (args.length == 2) {
175             try {
176                 if (args[0].equals("-port")) {
177                     port = Integer.parseInt(args[1]);
178                 } else {
179                     usage();
180                 }
181             } catch (NumberFormatException e) {
182                 usage();
183             }
184         }
185         System.out.println("Running on port " + port);
186         new ChatServer(port).run();
187     }
188 }
189