org.javagamesfactory.nioservers
Class StringBasedServer

java.lang.Object
  extended by org.javagamesfactory.nioservers.StringBasedServer
All Implemented Interfaces:
java.lang.Runnable
Direct Known Subclasses:
SimpleServerDemo

public abstract class StringBasedServer
extends java.lang.Object
implements java.lang.Runnable

Base class that provides the core of a non-blocking NIO-driven server which can send and receive Strings seamlessly. It correctly handles partial/fragmented incoming and outgoing packets, etc.

There are two methods in this class you need to override when subclassing. The first is keyCancelled which you MUST override, or else you will get data-errors / crashes because your data structures will become corrupted whenever a user disconnects.

The second is processStringMessage, which is where all the incoming messages get sent to, and is the primary way for you to respond to incoming messages / requests / etc.

There is a second Logger instance in this class, mapped to [classname].verbose, which gives VERY verbose information when you put it into info or debug Level.

See Also:
processStringMessage(String, SelectionKey), keyCancelled(SelectionKey)

Field Summary
protected  java.util.LinkedList<java.nio.channels.SocketChannel> connectedChannels
           
static int defaultByteBufferSize
          Default size to create ALL incoming and outgoing buffers; this equates to the maximum size of message that can be sent or received.
protected  org.apache.log4j.Logger logger
           
protected  java.util.HashMap<java.nio.channels.SelectionKey,java.lang.Integer> messageLengths
           
protected  iServerInfo myInfo
           
protected  java.util.HashMap<java.nio.channels.SelectionKey,java.util.LinkedList<java.nio.ByteBuffer>> pendingOutgoingEncodedMessages
           
protected  java.util.HashMap<java.nio.channels.SelectionKey,java.util.LinkedList<java.lang.String>> pendingOutgoingMessages
           
protected  int port
           
protected  java.util.HashMap<java.nio.channels.SelectionKey,java.lang.Boolean> readBufferIsEmpty
           
protected  java.util.HashMap<java.nio.channels.SelectionKey,java.nio.ByteBuffer> readByteBuffers
           
protected  java.util.HashMap<java.nio.channels.SelectionKey,java.nio.CharBuffer> readCharBuffers
           
protected  java.nio.channels.Selector selector
           
protected  ServerState status
           
protected  int targetPort
           
protected  org.apache.log4j.Logger verboseLogger
           
protected  java.util.HashMap<java.nio.channels.SelectionKey,java.nio.ByteBuffer> writeByteBuffers
           
 
Constructor Summary
StringBasedServer(int p)
          Creates a server using the default byte buffer size
StringBasedServer(int p, int newBufferSize)
          Creates a server with a custom byte-buffer size, and ignores the defaultByteBufferSize
 
Method Summary
protected  void addErrorToKey(java.nio.channels.SelectionKey key, java.lang.String originalMessage, java.lang.String errorDescription)
          Convenience method for sending error messages to a client using simple XML format to easily extract what went wrong and what caused the error
protected  void addMessageToKey(java.nio.channels.SelectionKey key, java.lang.String message)
          Primary means of sending a message to a particular client; subclasses should use this method for ALL outgoing messages
protected  void debug(java.lang.String s)
          Works out the thread and port and prepends them to the log message before logging it to the standard logger
protected  void debugVerbose(java.lang.String s)
          Works out the thread and port and prepends them to the log message before logging it to the standard logger
protected  void error(java.lang.String s)
          Works out the thread and port and prepends them to the log message before logging it to the standard logger
protected  void error(java.lang.String s, java.lang.Throwable t)
          Works out the thread and port and prepends them to the log message before logging it to the standard logger
protected  int getNumberOfConnectedChannels()
          This class keeps track of how many channels are currently connected, adding them every time a key is accepted, and removing them as soon as there is an I/O error or READ or WRITE
 int getPort()
          The port that this server is bound to
 ServerState getStatus()
          The status variable tells you exactly what the internal state-machine of the server is currently doing (or trying to do)
protected  void info(java.lang.String s)
          Works out the thread and port and prepends them to the log message before logging it to the standard logger
protected  void info(java.lang.String s, java.lang.Throwable t)
          Works out the thread and port and prepends them to the log message before logging it to the standard logger
protected  void infoVerbose(java.lang.String s)
          Works out the thread and port and prepends them to the log message before logging it to the standard logger
protected abstract  void keyCancelled(java.nio.channels.SelectionKey key)
          Subclasses MUST override this method to remove data from local data structures whenever connections are dropped / closed
protected  java.lang.String peekOutgoingMessageQueueForKey(java.nio.channels.SelectionKey key)
          Allows you to have a look at what is due to be sent to any given client, but hasn't yet been sent - automatically keeps track of the message in non-bytes.
protected abstract  void postSelect(long millisecondsSinceLastStarted)
          Invoked every time a select completes, IRRESPECTIVE of whether there was any network data.
protected abstract  void processStringMessage(java.lang.String message, java.nio.channels.SelectionKey key)
          Subclasses SHOULD override this method to process all incoming messages
protected  java.lang.String readIncomingMessageFromKey(java.nio.channels.SelectionKey key)
          An intelligent read-from-bytebuffer-into-string method that seamlessly copes with partial reads.
 void run()
          Core of the server; this method runs continuously once the server has started, and continues until a successful call is made to the stop method
 void start()
          Starts the server; must be called or else the server won't do anything.
 void stop()
          Stops the server - not immediately (attempts immediate stop, but the server will have to finish some processing and close down some native OS resources, which takes time)
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Field Detail

defaultByteBufferSize

public static int defaultByteBufferSize
Default size to create ALL incoming and outgoing buffers; this equates to the maximum size of message that can be sent or received.

NB: this variable is ONLY checked at construction time; you must alter it BEFORE constructing an instance of a server if you want it to have effect (or use the alternative constructor that allows you to specify a custom value for that instance only).


logger

protected org.apache.log4j.Logger logger

verboseLogger

protected org.apache.log4j.Logger verboseLogger

status

protected ServerState status

selector

protected java.nio.channels.Selector selector

readByteBuffers

protected java.util.HashMap<java.nio.channels.SelectionKey,java.nio.ByteBuffer> readByteBuffers

readCharBuffers

protected java.util.HashMap<java.nio.channels.SelectionKey,java.nio.CharBuffer> readCharBuffers

messageLengths

protected java.util.HashMap<java.nio.channels.SelectionKey,java.lang.Integer> messageLengths

writeByteBuffers

protected java.util.HashMap<java.nio.channels.SelectionKey,java.nio.ByteBuffer> writeByteBuffers

pendingOutgoingMessages

protected java.util.HashMap<java.nio.channels.SelectionKey,java.util.LinkedList<java.lang.String>> pendingOutgoingMessages

pendingOutgoingEncodedMessages

protected java.util.HashMap<java.nio.channels.SelectionKey,java.util.LinkedList<java.nio.ByteBuffer>> pendingOutgoingEncodedMessages

readBufferIsEmpty

protected java.util.HashMap<java.nio.channels.SelectionKey,java.lang.Boolean> readBufferIsEmpty

port

protected int port

targetPort

protected int targetPort

myInfo

protected iServerInfo myInfo

connectedChannels

protected java.util.LinkedList<java.nio.channels.SocketChannel> connectedChannels
Constructor Detail

StringBasedServer

public StringBasedServer(int p)
                  throws java.net.UnknownHostException
Creates a server using the default byte buffer size

Parameters:
p - port which this server should bind to
Throws:
java.net.UnknownHostException
See Also:
defaultByteBufferSize

StringBasedServer

public StringBasedServer(int p,
                         int newBufferSize)
                  throws java.net.UnknownHostException
Creates a server with a custom byte-buffer size, and ignores the defaultByteBufferSize

Parameters:
p - port which this server should bind to
newBufferSize - sets the size of all receive and send buffers, overriding the defaultByteBufferSize
Throws:
java.net.UnknownHostException
See Also:
defaultByteBufferSize
Method Detail

processStringMessage

protected abstract void processStringMessage(java.lang.String message,
                                             java.nio.channels.SelectionKey key)
                                      throws java.nio.channels.ClosedChannelException
Subclasses SHOULD override this method to process all incoming messages

Throws:
java.nio.channels.ClosedChannelException

keyCancelled

protected abstract void keyCancelled(java.nio.channels.SelectionKey key)
Subclasses MUST override this method to remove data from local data structures whenever connections are dropped / closed


run

public void run()
Core of the server; this method runs continuously once the server has started, and continues until a successful call is made to the stop method

You can check if this method is running by inspecting the status variable - if it is RUNNING, then this method is happily chugging away

This server is single-threaded, using a single NIO Selector to do all the work of accepting, reading from and writing to connected TCP channels.

Specified by:
run in interface java.lang.Runnable
See Also:
start(), stop(), getStatus()

postSelect

protected abstract void postSelect(long millisecondsSinceLastStarted)
Invoked every time a select completes, IRRESPECTIVE of whether there was any network data.

This is primarily useful for servers that wish to be purely singlethreaded and do all their local processing in the gap between performing successive selects (lets you avoid using synchronized blocks anywhere)

Parameters:
millisecondsSinceLastStarted - the number of milliseconds since this method was last called; i.e. the time at which it was INVOKED, not the time at which it RETURNED; this is perfect for maintaining fixed-rate game loops

getNumberOfConnectedChannels

protected int getNumberOfConnectedChannels()
This class keeps track of how many channels are currently connected, adding them every time a key is accepted, and removing them as soon as there is an I/O error or READ or WRITE

Returns:
number of currently connected channels - may be slightly incorrect (gets updated only after each select operation takes place)

addMessageToKey

protected void addMessageToKey(java.nio.channels.SelectionKey key,
                               java.lang.String message)
                        throws java.nio.channels.ClosedChannelException
Primary means of sending a message to a particular client; subclasses should use this method for ALL outgoing messages

NB: this method is NOT as efficient as it could be - it neither recycles buffers, nor does it intelligently create them of variable size. All buffers are created at a single fixed size (default 2000 bytes), configurable by changing the byteBufferSize variable.

Parameters:
key - the key representing the client to send to
message - the message to send
Throws:
java.nio.channels.ClosedChannelException - if the client is no longer connected

peekOutgoingMessageQueueForKey

protected java.lang.String peekOutgoingMessageQueueForKey(java.nio.channels.SelectionKey key)
Allows you to have a look at what is due to be sent to any given client, but hasn't yet been sent - automatically keeps track of the message in non-bytes.

Parameters:
key - client to peek at
Returns:

addErrorToKey

protected void addErrorToKey(java.nio.channels.SelectionKey key,
                             java.lang.String originalMessage,
                             java.lang.String errorDescription)
                      throws java.nio.channels.ClosedChannelException
Convenience method for sending error messages to a client using simple XML format to easily extract what went wrong and what caused the error

Parameters:
key - client to send to
originalMessage - the message that you received from the client that caused the error
errorDescription - human-readable description of the error
Throws:
java.nio.channels.ClosedChannelException

start

public void start()
Starts the server; must be called or else the server won't do anything.


stop

public void stop()
Stops the server - not immediately (attempts immediate stop, but the server will have to finish some processing and close down some native OS resources, which takes time)

NB: there are MANY stages to stopping the server (this method is over 50 lines of code already) to handle all the edge cases of parts of the server crashing while trying to stop. Be patient! Worst-case scenario, an inner loop pauses for 10 milliseconds and re-checks to see if it can terminate the server yet - but if this waiting happens, it will output INFO messages to warn you that it hasn't hung, it's just trying to shutdown and failing.

NOTE: uses custom logger calls because the notion of which thread it the server is running in is less obvious inside this method!


getStatus

public ServerState getStatus()
The status variable tells you exactly what the internal state-machine of the server is currently doing (or trying to do)

Returns:
current status of the server
See Also:
ServerState

readIncomingMessageFromKey

protected java.lang.String readIncomingMessageFromKey(java.nio.channels.SelectionKey key)
                                               throws java.io.IOException
An intelligent read-from-bytebuffer-into-string method that seamlessly copes with partial reads. NB: this server REQUIRES that all incoming messages to be preceded by a single int saying how many bytes are in the message, and assumes that you are using a 1-byte wide charset (i.e. it doesn't track number of bytes separately from number of chars - it will NOT WORK if you use multi-byte charsets!)

Parameters:
key -
Returns:
null if the message is incomplete, the complete message otherwise
Throws:
java.io.IOException

getPort

public int getPort()
The port that this server is bound to

Returns:
the port that this server is bound to, or -1 if not yet bound successfully

debug

protected void debug(java.lang.String s)
Works out the thread and port and prepends them to the log message before logging it to the standard logger

Parameters:
s -

debugVerbose

protected void debugVerbose(java.lang.String s)
Works out the thread and port and prepends them to the log message before logging it to the standard logger

Parameters:
s -

info

protected void info(java.lang.String s)
Works out the thread and port and prepends them to the log message before logging it to the standard logger

Parameters:
s -

infoVerbose

protected void infoVerbose(java.lang.String s)
Works out the thread and port and prepends them to the log message before logging it to the standard logger

Parameters:
s -

info

protected void info(java.lang.String s,
                    java.lang.Throwable t)
Works out the thread and port and prepends them to the log message before logging it to the standard logger

Parameters:
s -

error

protected void error(java.lang.String s)
Works out the thread and port and prepends them to the log message before logging it to the standard logger

Parameters:
s -

error

protected void error(java.lang.String s,
                     java.lang.Throwable t)
Works out the thread and port and prepends them to the log message before logging it to the standard logger

Parameters:
s -