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