PageRenderTime 51ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/marauroa/src/marauroa/server/net/nio/NioServer.java

http://wastelanders.googlecode.com/
Java | 395 lines | 214 code | 63 blank | 118 comment | 29 complexity | c3670bb33ff882171a6ded96ed27f34b MD5 | raw file
Possible License(s): Apache-2.0, GPL-2.0
  1. /* $Id: NioServer.java,v 1.29 2010/06/12 15:09:47 nhnb Exp $ */
  2. /***************************************************************************
  3. * (C) Copyright 2003 - Marauroa *
  4. ***************************************************************************
  5. ***************************************************************************
  6. * *
  7. * This program is free software; you can redistribute it and/or modify *
  8. * it under the terms of the GNU General Public License as published by *
  9. * the Free Software Foundation; either version 2 of the License, or *
  10. * (at your option) any later version. *
  11. * *
  12. ***************************************************************************/
  13. package marauroa.server.net.nio;
  14. import marauroa.common.Log4J;
  15. import marauroa.server.net.IDisconnectedListener;
  16. import java.io.IOException;
  17. import java.net.InetAddress;
  18. import java.net.InetSocketAddress;
  19. import java.nio.ByteBuffer;
  20. import java.nio.channels.SelectionKey;
  21. import java.nio.channels.Selector;
  22. import java.nio.channels.ServerSocketChannel;
  23. import java.nio.channels.SocketChannel;
  24. import java.nio.channels.spi.SelectorProvider;
  25. import java.util.*;
  26. /**
  27. * This class is the basic schema for a nio server. It works in a pattern of
  28. * master/slave.
  29. *
  30. * @author miguel
  31. */
  32. class NioServer extends Thread {
  33. private static final int BACKLOG_WARNING_SIZE = 10;
  34. /**
  35. * the logger instance.
  36. */
  37. private static final marauroa.common.Logger logger = Log4J.getLogger(NioServer.class);
  38. /**
  39. * The host:port combination to listen on
  40. */
  41. private InetAddress hostAddress;
  42. private int port;
  43. /**
  44. * While keepRunning is true, we keep receiving messages
  45. */
  46. private boolean keepRunning;
  47. /**
  48. * isFinished is true when the thread has really exited.
  49. */
  50. private boolean isFinished;
  51. /**
  52. * The channel on which we'll accept connections
  53. */
  54. private ServerSocketChannel serverChannel;
  55. /**
  56. * The selector we'll be monitoring
  57. */
  58. private Selector selector;
  59. /**
  60. * The buffer into which we'll read data when it's available
  61. */
  62. private ByteBuffer readBuffer = ByteBuffer.allocate(8192);
  63. /**
  64. * This is the slave associated with this master. As it is a simple thread,
  65. * we only need one slave.
  66. */
  67. private IWorker worker;
  68. /**
  69. * A list of PendingChange instances
  70. */
  71. private List<ChangeRequest> pendingChanges = new LinkedList<ChangeRequest>();
  72. private List<ChangeRequest> pendingClosed;
  73. /**
  74. * Maps a SocketChannel to a list of ByteBuffer instances
  75. */
  76. private Map<SocketChannel, List<ByteBuffer>> pendingData = new HashMap<SocketChannel, List<ByteBuffer>>();
  77. /**
  78. * A list of the listeners to the onDisconnect event.
  79. */
  80. private List<IDisconnectedListener> listeners;
  81. public NioServer(InetAddress hostAddress, int port, IWorker worker) throws IOException {
  82. super("NioServer");
  83. keepRunning = true;
  84. isFinished = false;
  85. this.hostAddress = hostAddress;
  86. this.port = port;
  87. this.selector = this.initSelector();
  88. this.worker = worker;
  89. this.worker.setServer(this);
  90. pendingClosed = new LinkedList<ChangeRequest>();
  91. listeners = new LinkedList<IDisconnectedListener>();
  92. }
  93. /**
  94. * This method closes a channel. It also notify any listener about the
  95. * event.
  96. *
  97. * @param channel the channel to close.
  98. */
  99. public void close(SocketChannel channel) {
  100. for (IDisconnectedListener listener : listeners) {
  101. listener.onDisconnect(channel);
  102. }
  103. /*
  104. * We ask the server to close the channel
  105. */
  106. synchronized (this.pendingClosed) {
  107. pendingClosed.add(new ChangeRequest(channel, ChangeRequest.CLOSE, 0));
  108. }
  109. /*
  110. * Wake up to make the closure effective.
  111. */
  112. selector.wakeup();
  113. }
  114. /**
  115. * This method is used to send data on a socket.
  116. *
  117. * @param socket the socketchannel to use.
  118. * @param data a byte array of data to send
  119. */
  120. public void send(SocketChannel socket, byte[] data) {
  121. synchronized (this.pendingChanges) {
  122. // Indicate we want the interest ops set changed
  123. this.pendingChanges.add(new ChangeRequest(socket, ChangeRequest.CHANGEOPS,
  124. SelectionKey.OP_WRITE));
  125. // And queue the data we want written
  126. synchronized (this.pendingData) {
  127. List<ByteBuffer> queue = this.pendingData.get(socket);
  128. if (queue == null) {
  129. queue = new ArrayList<ByteBuffer>();
  130. this.pendingData.put(socket, queue);
  131. }
  132. queue.add(ByteBuffer.wrap(data));
  133. if (queue.size() > BACKLOG_WARNING_SIZE) {
  134. logger.debug(socket + ": " + queue.size());
  135. }
  136. }
  137. }
  138. // Finally, wake up our selecting thread so it can make the required
  139. // changes
  140. this.selector.wakeup();
  141. }
  142. /**
  143. * Finish this thread in a correct way.
  144. */
  145. public void finish() {
  146. keepRunning = false;
  147. selector.wakeup();
  148. while (isFinished == false) {
  149. Thread.yield();
  150. }
  151. try {
  152. selector.close();
  153. } catch (IOException e) {
  154. // We really don't care about the exception.
  155. }
  156. }
  157. @Override
  158. public void run() {
  159. while (keepRunning) {
  160. try {
  161. // Process any pending changes
  162. synchronized (this.pendingChanges) {
  163. Iterator<?> changes = this.pendingChanges.iterator();
  164. while (changes.hasNext()) {
  165. ChangeRequest change = (ChangeRequest) changes.next();
  166. if (change.socket.isConnected()) {
  167. switch (change.type) {
  168. case ChangeRequest.CHANGEOPS:
  169. SelectionKey key = change.socket.keyFor(this.selector);
  170. if (key.isValid()) {
  171. key.interestOps(change.ops);
  172. }
  173. }
  174. }
  175. }
  176. this.pendingChanges.clear();
  177. }
  178. synchronized (this.pendingClosed) {
  179. Iterator<?> it = pendingClosed.iterator();
  180. while (it.hasNext()) {
  181. ChangeRequest change = (ChangeRequest) it.next();
  182. if (change.socket.isConnected()) {
  183. switch (change.type) {
  184. case ChangeRequest.CLOSE:
  185. try {
  186. /*
  187. * Force data to be sent if there is data
  188. * waiting.
  189. */
  190. if (pendingData.containsKey(change.socket)) {
  191. SelectionKey key = change.socket.keyFor(selector);
  192. if (key.isValid()) {
  193. write(key);
  194. }
  195. }
  196. /*
  197. * Close the socket
  198. */
  199. change.socket.close();
  200. } catch (Exception e) {
  201. logger.info("Exception happened when closing socket", e);
  202. }
  203. break;
  204. }
  205. } else {
  206. logger.info("Closing a not connected socket");
  207. }
  208. }
  209. pendingClosed.clear();
  210. }
  211. // Wait for an event one of the registered channels
  212. this.selector.select();
  213. // Iterate over the set of keys for which events are available
  214. Iterator<?> selectedKeys = this.selector.selectedKeys().iterator();
  215. while (selectedKeys.hasNext()) {
  216. SelectionKey key = (SelectionKey) selectedKeys.next();
  217. selectedKeys.remove();
  218. if (!key.isValid()) {
  219. continue;
  220. }
  221. // Check what event is available and deal with it
  222. if (key.isAcceptable()) {
  223. this.accept(key);
  224. } else if (key.isReadable()) {
  225. this.read(key);
  226. } else if (key.isWritable()) {
  227. this.write(key);
  228. }
  229. }
  230. } catch (IOException e) {
  231. logger.error("Error on NIOServer", e);
  232. } catch (RuntimeException e) {
  233. logger.error("Error on NIOServer", e);
  234. }
  235. }
  236. isFinished = true;
  237. }
  238. private void accept(SelectionKey key) throws IOException {
  239. // For an accept to be pending the channel must be a server socket
  240. // channel.
  241. ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
  242. // Accept the connection and make it non-blocking
  243. SocketChannel socketChannel = serverSocketChannel.accept();
  244. socketChannel.configureBlocking(false);
  245. // Register the new SocketChannel with our Selector, indicating
  246. // we'd like to be notified when there's data waiting to be read
  247. socketChannel.register(this.selector, SelectionKey.OP_READ);
  248. worker.onConnect(socketChannel);
  249. }
  250. private void read(SelectionKey key) {
  251. SocketChannel socketChannel = (SocketChannel) key.channel();
  252. // Clear out our read buffer so it's ready for new data
  253. this.readBuffer.clear();
  254. // Attempt to read off the channel
  255. int numRead;
  256. try {
  257. numRead = socketChannel.read(this.readBuffer);
  258. } catch (IOException e) {
  259. // The remote forcibly closed the connection, cancel
  260. // the selection key and close the channel.
  261. logger.debug("Remote closed connnection", e);
  262. key.cancel();
  263. close(socketChannel);
  264. return;
  265. }
  266. if (numRead == -1) {
  267. // Remote entity shut the socket down cleanly. Do the
  268. // same from our end and cancel the channel.
  269. logger.debug("Remote closed connnection cleanly");
  270. close((SocketChannel) key.channel());
  271. key.cancel();
  272. return;
  273. }
  274. // Hand the data off to our worker thread
  275. this.worker.onData(this, socketChannel, this.readBuffer.array(), numRead);
  276. }
  277. private void write(SelectionKey key) {
  278. SocketChannel socketChannel = (SocketChannel) key.channel();
  279. synchronized (this.pendingData) {
  280. List<ByteBuffer> queue = this.pendingData.get(socketChannel);
  281. try {
  282. // Write until there's not more data ...
  283. while (!queue.isEmpty()) {
  284. ByteBuffer buf = queue.get(0);
  285. socketChannel.write(buf);
  286. if (buf.remaining() > 0) {
  287. // ... or the socket's buffer fills up
  288. break;
  289. }
  290. queue.remove(0);
  291. }
  292. if (queue.isEmpty()) {
  293. // We wrote away all data, so we're no longer interested
  294. // in writing on this socket. Switch back to waiting for
  295. // data.
  296. key.interestOps(SelectionKey.OP_READ);
  297. }
  298. } catch (IOException e) {
  299. // The remote forcibly closed the connection, cancel
  300. // the selection key and close the channel.
  301. logger.debug("Remote closed connnection", e);
  302. queue.clear();
  303. key.cancel();
  304. close(socketChannel);
  305. return;
  306. }
  307. }
  308. }
  309. private Selector initSelector() throws IOException {
  310. // Create a new selector
  311. Selector socketSelector = SelectorProvider.provider().openSelector();
  312. // Create a new non-blocking server socket channel
  313. this.serverChannel = ServerSocketChannel.open();
  314. serverChannel.configureBlocking(false);
  315. // Bind the server socket to the specified address and port
  316. InetSocketAddress isa = new InetSocketAddress(this.hostAddress, this.port);
  317. serverChannel.socket().bind(isa);
  318. serverChannel.socket().setPerformancePreferences(0, 2, 1);
  319. // Register the server socket channel, indicating an interest in
  320. // accepting new connections
  321. serverChannel.register(socketSelector, SelectionKey.OP_ACCEPT);
  322. return socketSelector;
  323. }
  324. /**
  325. * Register a listener to notify about disconnected events
  326. *
  327. * @param listener listener to add
  328. */
  329. public void registerDisconnectedListener(IDisconnectedListener listener) {
  330. this.listeners.add(listener);
  331. }
  332. }