/n-utils/src/main/java/com/noahsloan/nutils/net/SocketListener.java

http://n-utils.googlecode.com/ · Java · 213 lines · 89 code · 19 blank · 105 comment · 6 complexity · ef4bdd55a089d843ff5553ffcc2c21a8 MD5 · raw file

  1. package com.noahsloan.nutils.net;
  2. import java.io.IOException;
  3. import java.net.InetSocketAddress;
  4. import java.net.ServerSocket;
  5. import java.net.Socket;
  6. import java.net.SocketAddress;
  7. import com.noahsloan.nutils.log.Logger;
  8. /**
  9. * Base class for classes that listen on a socket. A new instance can function
  10. * as a server by calling run(), although this class is designed to be part of a
  11. * larger server.
  12. *
  13. * All responsibility for handling a new connection is handled by
  14. * handleConnection(Socket).
  15. *
  16. * Implements Runnable so that the listener may be given to a thread pool.
  17. *
  18. * In addition, several methods other than handleConnection(Socket) may be
  19. * overridden by sub-classes, although they do not have to be.
  20. *
  21. * @see SocketListener#bindFailed()
  22. * @see SocketListener#handleSocketError(Socket, IOException)
  23. * @see SocketListener#handleSSError(ServerSocket, IOException)
  24. *
  25. * These methods may not throw any checked exceptions, and should not throw
  26. * RuntimeExceptions. A general strategy for subclasses might be to utilize
  27. * these methods as listeners, to notify another thread of this server's
  28. * failure.
  29. *
  30. * @author Noah Sloan
  31. * @see SocketListener#handleConnection(Socket)
  32. */
  33. public abstract class SocketListener implements Runnable {
  34. private static final Logger LOG = Logger.getLogger(SocketListener.class);
  35. /**
  36. * the number of times in a row to try to restart the server socket
  37. */
  38. public static final int MAX_ATTEMPTS = Integer.getInteger(
  39. "n-utils.net.SocketListener.max-attempts", 15);
  40. private volatile boolean shutdown; // is this listener shutdown?
  41. private volatile ServerSocket ss;
  42. protected final SocketAddress address; // the socket address to bind to
  43. /**
  44. * Create a listener for the wildcard address and given port.
  45. *
  46. * @param port
  47. */
  48. public SocketListener(int port) {
  49. this(new InetSocketAddress(port));
  50. }
  51. /**
  52. * Create a listener for address:port
  53. *
  54. * @param address
  55. * @param port
  56. */
  57. public SocketListener(String address, int port) {
  58. this(new InetSocketAddress(address, port));
  59. }
  60. /**
  61. * Create a listener on the given socket address.
  62. *
  63. * @param address
  64. */
  65. public SocketListener(SocketAddress address) {
  66. this.address = address;
  67. }
  68. /**
  69. * Creates a server socket that listens on the port provided in the
  70. * constructor. Will attempt to re-bind to the given socket address up to
  71. * MAX_ATTEMPTS times.
  72. *
  73. * @see SocketListener#MAX_ATTEMPTS
  74. */
  75. public void run() {
  76. boolean canBind = true;
  77. int bindAttempts = 0;
  78. while (!shutdown && canBind) {
  79. LOG.info("Starting Server Socket. Binding to " + address);
  80. // Assume that we cannot bind until we successfully get a
  81. // connection from a client
  82. canBind = false;
  83. try {
  84. // create a new socket and bind to it
  85. ss = new ServerSocket();
  86. ss.setReuseAddress(true);
  87. ss.bind(address);
  88. // as long as we are bound, and not shutdown, handle
  89. // connections
  90. while (!shutdown && ss.isBound()) {
  91. Socket socket = ss.accept();
  92. canBind = true;// we have successfully made a connection
  93. try {
  94. handleConnection(socket);
  95. } catch (IOException e) {
  96. handleSocketError(socket, e);
  97. }
  98. }
  99. } catch (IOException e) {
  100. handleSSError(ss, e);
  101. }
  102. // reset bind attempts if we successfully bound, otherwise
  103. // count the
  104. // attempts
  105. bindAttempts = canBind ? 0 : bindAttempts + 1;
  106. // shutdown if we fail to bind too many times
  107. shutdown = shutdown || bindAttempts > MAX_ATTEMPTS;
  108. }
  109. // print an error message if this shutdown was because of a
  110. // failure to bind
  111. if (bindAttempts > MAX_ATTEMPTS) {
  112. bindFailed();
  113. }
  114. }
  115. /**
  116. * Prints an error message and attempts to close the ServerSocket. May be
  117. * overridden by subclasses but may not throw any new checked exceptions.
  118. *
  119. * @param ss
  120. * the ServerSocket that threw the exception.
  121. * @param e
  122. * the exception thrown.
  123. */
  124. protected void handleSSError(ServerSocket ss, IOException e) {
  125. LOG.info("Server Socket Error");
  126. try {
  127. ss.close();
  128. } catch (IOException e1) {
  129. // if it wont close, we can't do anything about it
  130. LOG.error("Error closing socket", e1);
  131. }
  132. }
  133. /**
  134. * Prints an error message and attempts to close the socket. May be
  135. * overridden by subclasses but may not throw any new checked exceptions.
  136. *
  137. * @param socket
  138. * the socket that threw the exception.
  139. * @param e
  140. * the exception thrown.
  141. */
  142. protected void handleSocketError(Socket socket, IOException e) {
  143. LOG.warn(String
  144. .format("Error on socket to %s", socket.getInetAddress()), e);
  145. try {
  146. socket.close();
  147. } catch (IOException e1) {
  148. // can't do anything if it wont close...
  149. LOG.warn("Error closing socket", e1);
  150. }
  151. }
  152. /**
  153. * Logs an error message when the server socket fails to bind. May be
  154. * overridden by sub-classes, but may not throw any checked exceptions.
  155. */
  156. protected void bindFailed() {
  157. LOG.error(String.format("%s could not (re)bind to %s after"
  158. + " %d consecutive attempts.\n", this.getClass().getName(),
  159. address, MAX_ATTEMPTS));
  160. }
  161. /**
  162. * Handle is expected to start a new thread and/or quickly return so it can
  163. * handle the next incoming connection.
  164. *
  165. * @param socket
  166. * the socket that just connected.
  167. * @throws IOException
  168. * if there is an error on the socket.
  169. */
  170. protected abstract void handleConnection(Socket socket) throws IOException;
  171. /**
  172. * Shutdown this listener. Note that this will not stop any servant threads
  173. * you may have spawned, it is up to you to ensure that they terminate
  174. * reasonably.
  175. *
  176. * @throws IOException
  177. * if an error occurs closing the socket.
  178. *
  179. */
  180. public void shutdown() throws IOException {
  181. shutdown = true;
  182. ss.close();
  183. }
  184. public static void main(String[] args) {
  185. new SocketListener(Integer.getInteger("socketListenerPort", 1029)) {
  186. @Override
  187. // dumps data to std out
  188. protected void handleConnection(Socket socket) throws IOException {
  189. new com.noahsloan.nutils.InToOut(socket.getInputStream(), System.out,
  190. true, false).connect();
  191. }
  192. }.run();
  193. }
  194. }