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

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