/** * Closes all connected clients sockets, then closes the underlying ServerSocketChannel, * effectively killing the server socket selectorthread, freeing the port the server was bound to * and stops all internal workerthreads. * * <p>If this method is called before the server is started it will never start. * * @param timeout Specifies how many milliseconds shall pass between initiating the close * handshakes with the connected clients and closing the servers socket channel. * @throws IOException When {@link ServerSocketChannel}.close throws an IOException * @throws InterruptedException */ public void stop(int timeout) throws IOException, InterruptedException { if (!isclosed.compareAndSet(false, true)) { return; } synchronized (connections) { for (WebSocket ws : connections) { ws.close(CloseFrame.GOING_AWAY); } } synchronized (this) { if (selectorthread != null) { if (Thread.currentThread() != selectorthread) {} if (selectorthread != Thread.currentThread()) { selectorthread.interrupt(); selectorthread.join(); } } if (decoders != null) { for (WebSocketWorker w : decoders) { w.interrupt(); } } if (server != null) { server.close(); } } }
/** * Creates a WebSocketServer that will attempt to bind/listen on the given <var>address</var>, and * comply with <tt>Draft</tt> version <var>draft</var>. * * @param address The address (host:port) this server should listen on. * @param decodercount The number of {@link WebSocketWorker}s that will be used to process the * incoming network data. By default this will be <code> * Runtime.getRuntime().availableProcessors()</code> * @param drafts The versions of the WebSocket protocol that this server instance should comply * to. Clients that use an other protocol version will be rejected. * @param connectionscontainer Allows to specify a collection that will be used to store the * websockets in. <br> * If you plan to often iterate through the currently connected websockets you may want to use * a collection that does not require synchronization like a {@link CopyOnWriteArraySet}. In * that case make sure that you overload {@link #removeConnection(WebSocket)} and {@link * #addConnection(WebSocket)}.<br> * By default a {@link HashSet} will be used. * @see #removeConnection(WebSocket) for more control over syncronized operation * @see <a href="https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts" > more about drafts */ public WebSocketServer( InetSocketAddress address, int decodercount, List<Draft> drafts, Collection<WebSocket> connectionscontainer) { if (address == null || decodercount < 1 || connectionscontainer == null) { throw new IllegalArgumentException( "address and connectionscontainer must not be null and you need at least 1 decoder"); } if (drafts == null) this.drafts = Collections.emptyList(); else this.drafts = drafts; this.address = address; this.connections = connectionscontainer; oqueue = new LinkedBlockingQueue<WebSocketImpl>(); iqueue = new LinkedList<WebSocketImpl>(); decoders = new ArrayList<WebSocketWorker>(decodercount); buffers = new LinkedBlockingQueue<ByteBuffer>(); for (int i = 0; i < decodercount; i++) { WebSocketWorker ex = new WebSocketWorker(); decoders.add(ex); ex.start(); } }
private void handleFatal(WebSocket conn, RuntimeException e) { onError(conn, e); try { selector.close(); } catch (IOException e1) { onError(null, e1); } for (WebSocketWorker w : decoders) { w.interrupt(); } }
public WebSocketServer(InetSocketAddress address, int decodercount, List<Draft> drafts) { if (drafts == null) this.drafts = Collections.emptyList(); else this.drafts = drafts; setAddress(address); oqueue = new LinkedBlockingQueue<WebSocketImpl>(); decoders = new ArrayList<WebSocketWorker>(decodercount); buffers = new LinkedBlockingQueue<ByteBuffer>(); for (int i = 0; i < decodercount; i++) { WebSocketWorker ex = new WebSocketWorker(); decoders.add(ex); ex.start(); } }
// Runnable IMPLEMENTATION ///////////////////////////////////////////////// public void run() { synchronized (this) { if (selectorthread != null) throw new IllegalStateException(getClass().getName() + " can only be started once."); selectorthread = Thread.currentThread(); if (isclosed.get()) { return; } } selectorthread.setName("WebsocketSelector" + selectorthread.getId()); try { server = ServerSocketChannel.open(); server.configureBlocking(false); ServerSocket socket = server.socket(); socket.setReceiveBufferSize(WebSocketImpl.RCVBUF); socket.bind(address); selector = Selector.open(); server.register(selector, server.validOps()); } catch (IOException ex) { handleFatal(null, ex); return; } try { while (!selectorthread.isInterrupted()) { SelectionKey key = null; WebSocketImpl conn = null; try { selector.select(); Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> i = keys.iterator(); while (i.hasNext()) { key = i.next(); if (!key.isValid()) { // Object o = key.attachment(); continue; } if (key.isAcceptable()) { if (!onConnect(key)) { key.cancel(); continue; } SocketChannel channel = server.accept(); channel.configureBlocking(false); WebSocketImpl w = wsf.createWebSocket(this, drafts, channel.socket()); w.key = channel.register(selector, SelectionKey.OP_READ, w); w.channel = wsf.wrapChannel(channel, w.key); i.remove(); allocateBuffers(w); continue; } if (key.isReadable()) { conn = (WebSocketImpl) key.attachment(); ByteBuffer buf = takeBuffer(); try { if (SocketChannelIOHelper.read(buf, conn, conn.channel)) { if (buf.hasRemaining()) { conn.inQueue.put(buf); queue(conn); i.remove(); if (conn.channel instanceof WrappedByteChannel) { if (((WrappedByteChannel) conn.channel).isNeedRead()) { iqueue.add(conn); } } } else pushBuffer(buf); } else { pushBuffer(buf); } } catch (IOException e) { pushBuffer(buf); throw e; } } if (key.isWritable()) { conn = (WebSocketImpl) key.attachment(); if (SocketChannelIOHelper.batch(conn, conn.channel)) { if (key.isValid()) key.interestOps(SelectionKey.OP_READ); } } } while (!iqueue.isEmpty()) { conn = iqueue.remove(0); WrappedByteChannel c = ((WrappedByteChannel) conn.channel); ByteBuffer buf = takeBuffer(); try { if (SocketChannelIOHelper.readMore(buf, conn, c)) iqueue.add(conn); if (buf.hasRemaining()) { conn.inQueue.put(buf); queue(conn); } else { pushBuffer(buf); } } catch (IOException e) { pushBuffer(buf); throw e; } } } catch (CancelledKeyException e) { // an other thread may cancel the key } catch (ClosedByInterruptException e) { return; // do the same stuff as when InterruptedException is thrown } catch (IOException ex) { if (key != null) key.cancel(); handleIOException(key, conn, ex); } catch (InterruptedException e) { return; // FIXME controlled shutdown (e.g. take care of buffermanagement) } } } catch (RuntimeException e) { // should hopefully never occur handleFatal(null, e); } finally { if (decoders != null) { for (WebSocketWorker w : decoders) { w.interrupt(); } } if (server != null) { try { server.close(); } catch (IOException e) { onError(null, e); } } } }