You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DCC.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. /*
  2. * Copyright (c) 2006-2014 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.addons.dcc.io;
  23. import java.io.IOException;
  24. import java.net.ServerSocket;
  25. import java.net.Socket;
  26. import java.util.concurrent.Semaphore;
  27. import java.util.concurrent.atomic.AtomicBoolean;
  28. /**
  29. * This class manages the socket and low-level I/O functionality for all types of DCC. Subclasses
  30. * process the data received by this class.
  31. */
  32. public abstract class DCC implements Runnable {
  33. /** Address. */
  34. protected long address = 0;
  35. /** Port. */
  36. protected int port = 0;
  37. /** Socket used to communicate with. */
  38. protected Socket socket;
  39. /** The Thread in use for this. */
  40. private volatile Thread myThread;
  41. /** Are we already running? */
  42. protected final AtomicBoolean running = new AtomicBoolean();
  43. /** Are we a listen socket? */
  44. protected boolean listen = false;
  45. /**
  46. * The current socket in use if this is a listen socket. This reference may be changed if and
  47. * only if exactly one permit from the {@link #serverSocketSem} and {@link #serverListeningSem}
  48. * semaphores is held by the thread doing the modification.
  49. */
  50. private ServerSocket serverSocket;
  51. /**
  52. * Semaphore to control write access to ServerSocket. If an object acquires a permit from the
  53. * {@link #serverSocketSem}, then {@link #serverSocket} is <em>guaranteed</em> not to be
  54. * externally modified until that permit is released, <em>unless</em> the object also acquires a
  55. * permit from the {@link #serverListeningSem}.
  56. */
  57. private final Semaphore serverSocketSem = new Semaphore(1);
  58. /**
  59. * Semaphore used when we're blocking waiting for connections. If an object acquires a permit
  60. * from the {@link #serverListeningSem}, then it is <em>guaranteed</em> that the {@link #run()}
  61. * method is blocking waiting for incoming connections. In addition, it is <em>guaranteed</em>
  62. * that the {@link #run()} method is holding the {@link #serverSocketSem} permit, and it will
  63. * continue holding that permit until it can reacquire the {@link #serverListeningSem} permit.
  64. */
  65. private final Semaphore serverListeningSem = new Semaphore(0);
  66. /**
  67. * Connect this dcc.
  68. */
  69. public void connect() {
  70. try {
  71. if (listen) {
  72. address = 0;
  73. port = serverSocket.getLocalPort();
  74. } else {
  75. socket = new Socket(longToIP(address), port);
  76. socketOpened();
  77. }
  78. } catch (IOException ioe) {
  79. socketClosed();
  80. return;
  81. }
  82. myThread = new Thread(this, "DCC-Thread-" + getHost());
  83. myThread.start();
  84. }
  85. /**
  86. * Start a listen socket rather than a connect socket.
  87. *
  88. * @throws IOException If the listen socket can't be created
  89. */
  90. public void listen() throws IOException {
  91. serverSocketSem.acquireUninterruptibly();
  92. serverSocket = new ServerSocket(0, 1);
  93. serverSocketSem.release();
  94. listen = true;
  95. connect();
  96. }
  97. /**
  98. * Start a listen socket rather than a connect socket, use a port from the given range.
  99. *
  100. * @param startPort Port to try first
  101. * @param endPort Last port to try.
  102. *
  103. * @throws IOException If no sockets were available in the given range
  104. */
  105. public void listen(final int startPort, final int endPort) throws IOException {
  106. listen = true;
  107. for (int i = startPort; i <= endPort; ++i) {
  108. try {
  109. serverSocketSem.acquireUninterruptibly();
  110. serverSocket = new ServerSocket(i, 1);
  111. // Found a socket we can use!
  112. break;
  113. } catch (IOException | SecurityException ioe) {
  114. // Try next socket.
  115. } finally {
  116. serverSocketSem.release();
  117. }
  118. }
  119. if (serverSocket == null) {
  120. throw new IOException("No available sockets in range " + startPort + ":" + endPort);
  121. } else {
  122. connect();
  123. }
  124. }
  125. /**
  126. * This handles the socket to keep it out of the main thread
  127. */
  128. @Override
  129. public void run() {
  130. if (running.getAndSet(true)) {
  131. return;
  132. }
  133. // handleSocket is implemented by sub classes, and should return false
  134. // when the socket is closed.
  135. final Thread thisThread = Thread.currentThread();
  136. while (myThread == thisThread) {
  137. serverSocketSem.acquireUninterruptibly();
  138. if (serverSocket == null) {
  139. serverSocketSem.release();
  140. if (!handleSocket()) {
  141. close();
  142. break;
  143. }
  144. } else {
  145. try {
  146. serverListeningSem.release();
  147. socket = serverSocket.accept();
  148. serverSocket.close();
  149. socketOpened();
  150. } catch (IOException ioe) {
  151. socketClosed();
  152. break;
  153. } finally {
  154. serverListeningSem.acquireUninterruptibly();
  155. serverSocket = null;
  156. serverSocketSem.release();
  157. }
  158. }
  159. // Yield to reduce CPU usage.
  160. Thread.yield();
  161. }
  162. // Socket closed
  163. running.set(false);
  164. }
  165. /**
  166. * Called to close the socket
  167. */
  168. public void close() {
  169. boolean haveSLS = false;
  170. while (!serverSocketSem.tryAcquire() && !(haveSLS = serverListeningSem.tryAcquire())) {
  171. Thread.yield();
  172. }
  173. if (serverSocket != null) {
  174. try {
  175. if (!serverSocket.isClosed()) {
  176. serverSocket.close();
  177. }
  178. } catch (IOException ioe) {
  179. }
  180. serverSocket = null;
  181. }
  182. if (haveSLS) {
  183. serverListeningSem.release();
  184. } else {
  185. serverSocketSem.release();
  186. }
  187. if (socket != null) {
  188. try {
  189. if (!socket.isClosed()) {
  190. socket.close();
  191. }
  192. } catch (IOException ioe) {
  193. }
  194. socketClosed();
  195. socket = null;
  196. }
  197. }
  198. /**
  199. * Called when the socket is first opened, before any data is handled.
  200. */
  201. protected void socketOpened() {
  202. }
  203. /**
  204. * Called when the socket is closed, before the thread terminates.
  205. */
  206. protected void socketClosed() {
  207. }
  208. /**
  209. * Check if this socket can be written to.
  210. *
  211. * @return True if the socket is writable, false otherwise
  212. */
  213. public boolean isWriteable() {
  214. return false;
  215. }
  216. /**
  217. * Called periodically to read or write data to this DCC's socket. Implementations should
  218. * attempt to send or receive one unit of data (for example one block of binary data, or one
  219. * line of ASCII data) each time this method is called.
  220. * <p>
  221. * The return value of this method is used to determine whether the DCC has been completed. If
  222. * the method returns <code>false</code>, the DCC is assumed to have finished (i.e., the socket
  223. * has closed), and the method will not be called again. A return value of <code>true</code>
  224. * will cause the method to be recalled.
  225. *
  226. * @return false when socket is closed, true will cause the method to be called again.
  227. */
  228. protected abstract boolean handleSocket();
  229. /**
  230. * Set the address to connect to for this DCC
  231. *
  232. * @param address Address as an int (Network Byte Order, as specified in the DCC CTCP)
  233. * @param port Port to connect to
  234. */
  235. public void setAddress(final long address, final int port) {
  236. this.address = address;
  237. this.port = port;
  238. }
  239. /**
  240. * Is this a listening socket
  241. *
  242. * @return True if this is a listening socket
  243. */
  244. public boolean isListenSocket() {
  245. return listen;
  246. }
  247. /**
  248. * Get the host this socket is listening on/connecting to
  249. *
  250. * @return The IP that this socket is listening on/connecting to.
  251. */
  252. public String getHost() {
  253. return longToIP(address);
  254. }
  255. /**
  256. * Get the port this socket is listening on/connecting to
  257. *
  258. * @return The port that this socket is listening on/connecting to.
  259. */
  260. public int getPort() {
  261. return port;
  262. }
  263. /**
  264. * Convert the given IP Address to a long
  265. *
  266. * @param ip Input IP Address
  267. *
  268. * @return ip as a long
  269. */
  270. public static long ipToLong(final String ip) {
  271. final String[] bits = ip.split("\\.");
  272. if (bits.length > 3) {
  273. return (Long.parseLong(bits[0]) << 24) + (Long.parseLong(bits[1]) << 16)
  274. + (Long.parseLong(bits[2]) << 8) + Long.parseLong(bits[3]);
  275. }
  276. return 0;
  277. }
  278. /**
  279. * Convert the given long to an IP Address
  280. *
  281. * @param in Input long
  282. *
  283. * @return long as an IP
  284. */
  285. public static String longToIP(final long in) {
  286. return ((in & 0xff000000) >> 24) + "." + ((in & 0x00ff0000) >> 16) + "."
  287. + ((in & 0x0000ff00) >> 8) + "." + (in & 0x000000ff);
  288. }
  289. }