diff --git a/src/main/java/org/java_websocket/AbstractWebSocket.java b/src/main/java/org/java_websocket/AbstractWebSocket.java new file mode 100644 index 00000000..d07b328c --- /dev/null +++ b/src/main/java/org/java_websocket/AbstractWebSocket.java @@ -0,0 +1,144 @@ +package org.java_websocket; + +import org.java_websocket.framing.CloseFrame; + +import java.util.Collection; +import java.util.Timer; +import java.util.TimerTask; + + +/** + * Base class for additional implementations for the server as well as the client + */ +public abstract class AbstractWebSocket extends WebSocketAdapter { + + /** + * Attribute which allows you to deactivate the Nagle's algorithm + */ + private boolean tcpNoDelay; + + /** + * Attribute for a timer allowing to check for lost connections + */ + private Timer connectionLostTimer; + /** + * Attribute for a timertask allowing to check for lost connections + */ + private TimerTask connectionLostTimerTask; + + /** + * Attribute for the lost connection check interval + */ + private int connectionLostTimeout = 60; + + /** + * Get the interval checking for lost connections + * Default is 60 seconds + * @return the interval + */ + public int getConnectionLostTimeout() { + return connectionLostTimeout; + } + + /** + * Setter for the interval checking for lost connections + * A value >= 0 results in the check to be deactivated + * + * @param connectionLostTimeout the interval in seconds + */ + public void setConnectionLostTimeout( int connectionLostTimeout ) { + this.connectionLostTimeout = connectionLostTimeout; + if (this.connectionLostTimeout <= 0) { + stopConnectionLostTimer(); + } else { + startConnectionLostTimer(); + } + } + + /** + * Stop the connection lost timer + */ + protected void stopConnectionLostTimer() { + if (connectionLostTimer != null ||connectionLostTimerTask != null) { + if( WebSocketImpl.DEBUG ) + System.out.println( "Connection lost timer stoped" ); + cancelConnectionLostTimer(); + } + } + /** + * Start the connection lost timer + */ + protected void startConnectionLostTimer() { + if (this.connectionLostTimeout <= 0) { + if (WebSocketImpl.DEBUG) + System.out.println("Connection lost timer deactivated"); + return; + } + if (WebSocketImpl.DEBUG) + System.out.println("Connection lost timer started"); + cancelConnectionLostTimer(); + connectionLostTimer = new Timer(); + connectionLostTimerTask = new TimerTask() { + @Override + public void run() { + Collection con = connections(); + synchronized ( con ) { + long current = (System.currentTimeMillis()-(connectionLostTimeout * 1500)); + for( WebSocket conn : con ) { + if (conn instanceof WebSocketImpl) { + if( ((WebSocketImpl)conn).getLastPong() < current ) { + if (WebSocketImpl.DEBUG) + System.out.println("Closing connection due to no pong received: " + conn.toString()); + conn.close( CloseFrame.ABNORMAL_CLOSE ); + } else { + conn.sendPing(); + } + } + } + } + } + }; + connectionLostTimer.scheduleAtFixedRate( connectionLostTimerTask,connectionLostTimeout * 1000, connectionLostTimeout * 1000 ); + } + + /** + * Getter to get all the currently available connections + * @return the currently available connections + */ + protected abstract Collection connections(); + + /** + * Cancel any running timer for the connection lost detection + */ + private void cancelConnectionLostTimer() { + if( connectionLostTimer != null ) { + connectionLostTimer.cancel(); + connectionLostTimer = null; + } + if( connectionLostTimerTask != null ) { + connectionLostTimerTask.cancel(); + connectionLostTimerTask = null; + } + } + + /** + * Tests if TCP_NODELAY is enabled. + * + * @return a boolean indicating whether or not TCP_NODELAY is enabled for new connections. + */ + public boolean isTcpNoDelay() { + return tcpNoDelay; + } + + /** + * Setter for tcpNoDelay + *

+ * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm) for new connections + * + * @param tcpNoDelay true to enable TCP_NODELAY, false to disable. + */ + public void setTcpNoDelay( boolean tcpNoDelay ) { + this.tcpNoDelay = tcpNoDelay; + } + +} diff --git a/src/main/java/org/java_websocket/WebSocket.java b/src/main/java/org/java_websocket/WebSocket.java index 2fc8ef0f..b2bfe4fd 100644 --- a/src/main/java/org/java_websocket/WebSocket.java +++ b/src/main/java/org/java_websocket/WebSocket.java @@ -7,6 +7,7 @@ import org.java_websocket.drafts.Draft; import org.java_websocket.framing.Framedata; import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.framing.FramedataImpl1; public interface WebSocket { /** @@ -30,6 +31,11 @@ public enum READYSTATE { */ public static final int DEFAULT_PORT = 80; + /** + * The default wss port of WebSockets, as defined in the spec. If the nullary + * constructor is used, DEFAULT_WSS_PORT will be the port the WebSocketServer + * is binded to. Note that ports under 1024 usually require root permissions. + */ public static final int DEFAULT_WSS_PORT = 443; /** @@ -90,6 +96,11 @@ public enum READYSTATE { */ public abstract void sendFrame( Framedata framedata ); + /** + * Send a ping to the other end + * @throws NotYetConnectedException websocket is not yet connected + */ + public void sendPing() throws NotYetConnectedException; /** * Allows to send continuous/fragmented frames conveniently.
* For more into on this frame type see http://tools.ietf.org/html/rfc6455#section-5.4
diff --git a/src/main/java/org/java_websocket/WebSocketAdapter.java b/src/main/java/org/java_websocket/WebSocketAdapter.java index c7d3c2b8..de4ba4ac 100644 --- a/src/main/java/org/java_websocket/WebSocketAdapter.java +++ b/src/main/java/org/java_websocket/WebSocketAdapter.java @@ -1,10 +1,9 @@ package org.java_websocket; -import java.net.InetSocketAddress; - import org.java_websocket.drafts.Draft; import org.java_websocket.exceptions.InvalidDataException; import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.framing.CloseFrame; import org.java_websocket.framing.Framedata; import org.java_websocket.framing.Framedata.Opcode; import org.java_websocket.framing.FramedataImpl1; @@ -13,6 +12,11 @@ import org.java_websocket.handshake.ServerHandshake; import org.java_websocket.handshake.ServerHandshakeBuilder; +import java.net.InetSocketAddress; +import java.util.Collection; +import java.util.Timer; +import java.util.TimerTask; + /** * This class default implements all methods of the WebSocketListener that can be overridden optionally when advances functionalities is needed.
**/ @@ -75,25 +79,25 @@ public void onWebsocketPong( WebSocket conn, Framedata f ) { /** * Gets the XML string that should be returned if a client requests a Flash * security policy. - * + *

* The default implementation allows access from all remote domains, but * only on the port that this WebSocketServer is listening on. - * + *

* This is specifically implemented for gitime's WebSocket client for Flash: * http://github.com/gimite/web-socket-js - * + * * @return An XML String that comforts to Flash's security policy. You MUST - * not include the null char at the end, it is appended automatically. + * not include the null char at the end, it is appended automatically. * @throws InvalidDataException thrown when some data that is required to generate the flash-policy like the websocket local port could not be obtained e.g because the websocket is not connected. */ @Override public String getFlashPolicy( WebSocket conn ) throws InvalidDataException { InetSocketAddress adr = conn.getLocalSocketAddress(); - if(null == adr){ + if( null == adr ) { throw new InvalidHandshakeException( "socket not bound" ); } - return "\0"; + return "\0"; } } diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index 157bdfb7..cbb1b960 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -11,6 +11,7 @@ import org.java_websocket.framing.CloseFrameBuilder; import org.java_websocket.framing.Framedata; import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.framing.FramedataImpl1; import org.java_websocket.handshake.*; import org.java_websocket.server.WebSocketServer.WebSocketWorker; import org.java_websocket.util.Charsetfunctions; @@ -93,6 +94,11 @@ public class WebSocketImpl implements WebSocket { private String resourceDescriptor = null; + /** + * Attribute, when the last pong was recieved + */ + private long lastPong = System.currentTimeMillis(); + /** * Creates a websocket with server role * @@ -347,6 +353,7 @@ private void decodeFrames( ByteBuffer socketBuffer ) { wsl.onWebsocketPing( this, f ); continue; } else if( curop == Opcode.PONG ) { + lastPong = System.currentTimeMillis(); wsl.onWebsocketPong( this, f ); continue; } else if( !fin || curop == Opcode.CONTINUOUS ) { @@ -612,6 +619,12 @@ public void sendFrame( Framedata framedata ) { write( draft.createBinaryFrame( framedata ) ); } + public void sendPing() throws NotYetConnectedException { + FramedataImpl1 frame = new FramedataImpl1(Opcode.PING); + frame.setFin(true); + sendFrame(frame); + } + @Override public boolean hasBufferedData() { return !this.outQueue.isEmpty(); @@ -757,4 +770,12 @@ public void close() { public String getResourceDescriptor() { return resourceDescriptor; } + + /** + * Getter for the last pong recieved + * @return the timestamp for the last recieved pong + */ + long getLastPong() { + return lastPong; + } } diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index f578c532..faa6d582 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -9,9 +9,12 @@ import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.NotYetConnectedException; +import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.concurrent.CountDownLatch; +import org.java_websocket.AbstractWebSocket; import org.java_websocket.WebSocket; import org.java_websocket.WebSocketAdapter; import org.java_websocket.WebSocketImpl; @@ -21,6 +24,7 @@ import org.java_websocket.framing.CloseFrame; import org.java_websocket.framing.Framedata; import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.framing.FramedataImpl1; import org.java_websocket.handshake.HandshakeImpl1Client; import org.java_websocket.handshake.Handshakedata; import org.java_websocket.handshake.ServerHandshake; @@ -29,7 +33,7 @@ * A subclass must implement at least onOpen, onClose, and onMessage to be * useful. At runtime the user is expected to establish a connection via {@link #connect()}, then receive events like {@link #onMessage(String)} via the overloaded methods and to {@link #send(String)} data to the server. */ -public abstract class WebSocketClient extends WebSocketAdapter implements Runnable, WebSocket { +public abstract class WebSocketClient extends AbstractWebSocket implements Runnable, WebSocket { /** * The URI this channel is supposed to connect to. @@ -58,11 +62,6 @@ public abstract class WebSocketClient extends WebSocketAdapter implements Runnab private int connectTimeout = 0; - /** - * Attribute which allows you to deactivate the Nagle's algorithm - */ - private boolean tcpNoDelay; - /** * Constructs a WebSocketClient instance and sets it to the connect to the * specified URI. The channel does not attampt to connect automatically. The connection @@ -104,7 +103,7 @@ public WebSocketClient( URI serverUri , Draft protocolDraft , Map this.draft = protocolDraft; this.headers = httpHeaders; this.connectTimeout = connectTimeout; - this.tcpNoDelay = false; + setTcpNoDelay( false ); this.engine = new WebSocketImpl( this, protocolDraft ); } @@ -133,24 +132,6 @@ public Socket getSocket() { return socket; } - /** - * Tests if TCP_NODELAY is enabled. - * @return a boolean indicating whether or not TCP_NODELAY is enabled for new connections. - */ - public boolean isTcpNoDelay() { - return tcpNoDelay; - } - - /** - * Setter for tcpNoDelay - * - * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm) for new connections - * @param tcpNoDelay true to enable TCP_NODELAY, false to disable. - */ - public void setTcpNoDelay( boolean tcpNoDelay ) { - this.tcpNoDelay = tcpNoDelay; - } - /** * Initiates the websocket connection. This method does not block. */ @@ -210,6 +191,15 @@ public void send( byte[] data ) throws NotYetConnectedException { engine.send( data ); } + protected Collection connections() { + return Collections.singletonList((WebSocket ) engine ); + } + + + public void sendPing() throws NotYetConnectedException { + engine.sendPing( ); + } + public void run() { try { if( socket == null ) { @@ -217,7 +207,7 @@ public void run() { } else if( socket.isClosed() ) { throw new IOException(); } - socket.setTcpNoDelay( tcpNoDelay ); + socket.setTcpNoDelay( isTcpNoDelay() ); if( !socket.isBound() ) socket.connect( new InetSocketAddress( uri.getHost(), getPort() ), connectTimeout ); istream = socket.getInputStream(); @@ -319,6 +309,7 @@ public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { */ @Override public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) { + startConnectionLostTimer(); onOpen( (ServerHandshake) handshake ); connectLatch.countDown(); } @@ -328,6 +319,7 @@ public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) { */ @Override public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) { + stopConnectionLostTimer(); if( writeThread != null ) writeThread.interrupt(); try { diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index dac79ac9..ee430d0e 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -4,7 +4,6 @@ import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; -import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.channels.CancelledKeyException; @@ -28,12 +27,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import org.java_websocket.SocketChannelIOHelper; -import org.java_websocket.WebSocket; -import org.java_websocket.WebSocketAdapter; -import org.java_websocket.WebSocketFactory; -import org.java_websocket.WebSocketImpl; -import org.java_websocket.WrappedByteChannel; +import org.java_websocket.*; import org.java_websocket.drafts.Draft; import org.java_websocket.exceptions.InvalidDataException; import org.java_websocket.framing.CloseFrame; @@ -48,7 +42,7 @@ * functionality/purpose to the server. * */ -public abstract class WebSocketServer extends WebSocketAdapter implements Runnable { +public abstract class WebSocketServer extends AbstractWebSocket implements Runnable { public static int DECODERS = Runtime.getRuntime().availableProcessors(); @@ -88,11 +82,6 @@ public abstract class WebSocketServer extends WebSocketAdapter implements Runnab private WebSocketServerFactory wsf = new DefaultWebSocketServerFactory(); - /** - * Attribute which allows you to deactivate the Nagle's algorithm - */ - private boolean tcpNoDelay; - /** * Creates a WebSocketServer that will attempt to * listen on port WebSocket.DEFAULT_PORT. @@ -186,7 +175,7 @@ public WebSocketServer( InetSocketAddress address , int decodercount , List(); decoders = new ArrayList( decodercount ); @@ -198,24 +187,6 @@ public WebSocketServer( InetSocketAddress address , int decodercount , List