*
* [header 1-4 bytes] => content size
*
*
* [content] => 0-255/0-65535/0-16777215/0-2147483646
*
* Note that the maximum size for 4 bytes is a signed 32 bit int, not unsigned. *
* The packet writer will not validate outgoing packets, so make sure that * the packet content size will fit in the header. I.e. make sure that if you have * a 1 byte header, you do not send packets larger than 255 bytes, if two bytes, larger than 65535 and * so on. * * @author Christoffer Lerno */ public class RegularPacketWriter implements PacketWriter { private ByteBuffer m_header; private ByteBuffer m_content; private final boolean m_bigEndian; private final int m_headerSize; /** * Creates a regular packet writer with the given header size. * * @param headerSize the header size, 1 - 4 bytes. * @param bigEndian big endian (largest byte first) or little endian (smallest byte first) */ public RegularPacketWriter(int headerSize, boolean bigEndian) { if (headerSize < 1 || headerSize > 4) throw new IllegalArgumentException("Header must be between 1 and 4 bytes long."); m_bigEndian = bigEndian; m_headerSize = headerSize; m_header = ByteBuffer.allocate(0); m_content = ByteBuffer.allocate(0); } public ByteBuffer getBuffer() { return m_header.hasRemaining() ? m_header : m_content; } public boolean isEmpty() { return !m_header.hasRemaining() && !m_content.hasRemaining(); } public void setPacket(byte[] bytes) { if (!isEmpty()) throw new IllegalStateException("Attempted to add new packet before the previous was sent."); m_header = NIOUtils.getByteBufferFromPacketSize(m_headerSize, bytes.length, m_bigEndian); m_content = ByteBuffer.wrap(bytes); } } naga-2.1/src/main/naga/packetwriter/RawPacketWriter.java 0000664 0001750 0001750 00000001266 11653321253 022610 0 ustar mbanck mbanck package naga.packetwriter; import naga.PacketWriter; import java.nio.ByteBuffer; /** * Writes a byte packet to the stream without doing any changes to it. *
* This is the commonly case when one wants to output text or similarly * delimited data. * * @author Christoffer Lerno */ public class RawPacketWriter implements PacketWriter { private ByteBuffer m_buffer; /** * Creates a new writer. */ public RawPacketWriter() { m_buffer = null; } public void setPacket(byte[] bytes) { m_buffer = ByteBuffer.wrap(bytes); } public ByteBuffer getBuffer() { return m_buffer; } public boolean isEmpty() { return m_buffer == null || !m_buffer.hasRemaining(); } } naga-2.1/src/main/naga/packetwriter/package-info.java 0000664 0001750 0001750 00000000163 11653321234 022050 0 ustar mbanck mbanck /** * Package containing various ready-to-use {@code PacketWriter} implementations. */ package naga.packetwriter; naga-2.1/src/main/naga/PacketWriter.java 0000664 0001750 0001750 00000003541 11653321253 017430 0 ustar mbanck mbanck package naga; import java.nio.ByteBuffer; /** * Interface for classes implementing packet writing strategies. *
* The method {@link naga.PacketWriter#setPacket(byte[])} initializes for writer for output of a new * packet. Implementing classes can assume that setPacket is never called unless * {@link #isEmpty()} * returns true. The {@link #getBuffer()} * method should return a {@link java.nio.ByteBuffer} containing * the next chunk of data to output on the socket. *
* The implementation is similar to: *
*
* while (!packetWriter.isEmpty()) channel.write(packetWriter.getBuffer());
*
*
* In other words, it is ok to split a single packet into several byte buffers each are handed in * turn for every call to {@link #getBuffer()}. (See {@link naga.packetwriter.RegularPacketWriter} source code for an example.) * * @author Christoffer Lerno */ public interface PacketWriter { /** * Set the next packet to write. * * @param bytes an array of bytes representing the next packet. */ void setPacket(byte[] bytes); /** * Determines if the packet writer has more data to write. *
* Classes will never invoke {@link #setPacket(byte[])} unless
* isEmpty
returns true.
*
* @return true if everything buffered in the writer is writen, false otherwise.
*/
boolean isEmpty();
/**
* The current byte buffer to write to the socket.
*
* Note that the socket does no rewinding or similar of the buffer, * the only way it interacts with the buffer is by calling * {@link java.nio.channels.SocketChannel#write(ByteBuffer)}, so the implementing * class needs to make sure that the buffer is in the right state. *
* This code will not be called unless {@link #isEmpty()} returns false.
*
* @return the byte buffer to send data from to the socket.
*/
ByteBuffer getBuffer();
}
naga-2.1/src/main/naga/exception/ 0000775 0001750 0001750 00000000000 11653321234 016153 5 ustar mbanck mbanck naga-2.1/src/main/naga/exception/package-info.java 0000664 0001750 0001750 00000000073 11653321234 021342 0 ustar mbanck mbanck /**
* Exceptions used by Naga.
*/
package naga.exception; naga-2.1/src/main/naga/exception/ProtocolViolationException.java 0000664 0001750 0001750 00000000702 11653321234 024362 0 ustar mbanck mbanck package naga.exception;
import java.io.IOException;
/**
* Throw an exception due to unexpected data when reading packets.
*
* @author Christoffer Lerno
*/
public class ProtocolViolationException extends IOException
{
private static final long serialVersionUID = 6869467292395980590L;
/**
* Create a new exception.
*
* @param message exception message.
*/
public ProtocolViolationException(String message)
{
super(message);
}
}
naga-2.1/src/main/naga/SocketChannelResponder.java 0000664 0001750 0001750 00000016710 11653321253 021431 0 ustar mbanck mbanck package naga;
import naga.packetreader.RawPacketReader;
import naga.packetwriter.RawPacketWriter;
import java.io.EOFException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author Christoffer Lerno
*/
class SocketChannelResponder extends ChannelResponder implements NIOSocket
{
private final static byte[] CLOSE_PACKET = new byte[0];
private final static IOException CLOSE_EXCEPTION = new IOException("CLOSE");
private int m_bytesRead;
private int m_bytesWritten;
private int m_maxQueueSize;
private long m_timeOpened;
private final AtomicLong m_bytesInQueue;
private ConcurrentLinkedQueue
* Since packets read with delimiters may potentially grow unbounded, you can also supply
* a maximum buffer size to prevent an attacker from causing an out of memory
* by continously sending data without the delimiter.
*
* The delimiter will never appear in the packet itself.
*
* @author Christoffer Lerno
*/
public class DelimiterPacketReader implements PacketReader
{
public final static int DEFAULT_READ_BUFFER_SIZE = 256;
private ByteBuffer m_currentBuffer;
private volatile int m_maxPacketSize;
private byte[] m_buffer;
private byte m_delimiter;
/**
* Create a new reader with the default min buffer size and unlimited max buffer size.
*
* @param delimiter the byte delimiter to use.
*/
public DelimiterPacketReader(byte delimiter)
{
this(delimiter, DEFAULT_READ_BUFFER_SIZE, -1);
}
/**
* Create a new reader with the given min and max buffer size
* delimited by the given byte.
*
* @param delimiter the byte value of the delimiter.
* @param readBufferSize the size of the read buffer (i.e. how many
* bytes are read in a single pass) - this only has effect on read
* efficiency and memory requirements.
* @param maxPacketSize the maximum number of bytes read before throwing a
* ProtocolException. -1 means the packet has no size limit.
* @throws IllegalArgumentException if maxPacketSize < readBufferSize or if
* readBufferSize < 1.
*/
public DelimiterPacketReader(byte delimiter, int readBufferSize, int maxPacketSize)
{
if (readBufferSize < 1) throw new IllegalArgumentException("Min buffer must at least be 1 byte.");
if (maxPacketSize > -1 && readBufferSize > maxPacketSize)
{
throw new IllegalArgumentException("Read buffer cannot be be larger than the max packet size.");
}
m_currentBuffer = ByteBuffer.allocate(readBufferSize);
m_buffer = null;
m_delimiter = delimiter;
m_maxPacketSize = maxPacketSize;
}
/**
* Get the current maximum buffer size.
*
* @return the current maximum size.
*/
public int getMaxPacketSize()
{
return m_maxPacketSize;
}
/**
* Set the new maximum packet size.
*
* This method is thread-safe, but will not
* affect reads in progress.
*
* @param maxPacketSize the new maximum packet size.
*/
public void setMaxPacketSize(int maxPacketSize)
{
m_maxPacketSize = maxPacketSize;
}
/**
* Return the currently used byte buffer.
*
* @return the byte buffer to use.
* @throws ProtocolViolationException if the internal buffer already exceeds the maximum size.
*/
public ByteBuffer getBuffer() throws ProtocolViolationException
{
if (m_maxPacketSize > -1 && m_buffer != null && m_buffer.length > m_maxPacketSize)
{
throw new ProtocolViolationException("Packet size exceeds " + m_maxPacketSize);
}
return m_currentBuffer;
}
public byte[] getNextPacket() throws ProtocolViolationException
{
if (m_currentBuffer.position() > 0)
{
m_currentBuffer.flip();
int oldBufferLength = m_buffer == null ? 0 : m_buffer.length;
byte[] newBuffer = new byte[oldBufferLength + m_currentBuffer.remaining()];
if (m_buffer != null)
{
System.arraycopy(m_buffer, 0, newBuffer, 0, m_buffer.length);
}
m_currentBuffer.get(newBuffer, oldBufferLength, m_currentBuffer.remaining());
m_currentBuffer.clear();
m_buffer = newBuffer;
}
if (m_buffer == null) return null;
for (int i = 0; i < m_buffer.length; i++)
{
if (m_buffer[i] == m_delimiter)
{
byte[] packet = new byte[i];
System.arraycopy(m_buffer, 0, packet, 0, i);
if (i > m_buffer.length - 2)
{
m_buffer = null;
}
else
{
byte[] newBuffer = new byte[m_buffer.length - i - 1];
System.arraycopy(m_buffer, i + 1, newBuffer, 0, newBuffer.length);
m_buffer = newBuffer;
}
return packet;
}
}
return null;
}
} naga-2.1/src/main/naga/packetreader/AsciiLinePacketReader.java 0000664 0001750 0001750 00000002441 11653321253 023567 0 ustar mbanck mbanck package naga.packetreader;
/**
* Reads a bytestream delimited by '\n'.
*
* This can be used for reading lines of ASCII characters.
*
* @author Christoffer Lerno
*/
public class AsciiLinePacketReader extends DelimiterPacketReader
{
/**
* Creates a '\n' delimited reader with default min buffer size
* and unlimited max buffer size.
*/
public AsciiLinePacketReader()
{
super((byte) '\n');
}
/**
* Creates a '\n' delimited reader with the given max line length
* and read buffer size.
*
* Exceeding the line length will throw a ProtocolViolationException.
*
* @param readBufferSize the size of the read buffer (i.e. how many
* bytes are read in a single pass) - this only has effect on read
* efficiency and memory requirements.
* @param maxLineLength maximum line length.
*/
public AsciiLinePacketReader(int readBufferSize, int maxLineLength)
{
super((byte) '\n', readBufferSize, maxLineLength);
}
/**
* Creates a '\n' delimited reader with the given max line length
* and default read buffer size.
*
* Exceeding the line length will throw a ProtocolViolationException.
*
* @param maxLineLength maximum line length.
*/
public AsciiLinePacketReader(int maxLineLength)
{
super((byte) '\n', DEFAULT_READ_BUFFER_SIZE, maxLineLength);
}
}
naga-2.1/src/main/naga/packetreader/ZeroDelimitedPacketReader.java 0000664 0001750 0001750 00000002374 11653321253 024474 0 ustar mbanck mbanck package naga.packetreader;
/**
* Reads a bytestream delimited by 0.
*
* @author Christoffer Lerno
*/
public class ZeroDelimitedPacketReader extends DelimiterPacketReader
{
/**
* Creates zero delimited reader with a default read buffer
* and unlimited max packet size.
*/
public ZeroDelimitedPacketReader()
{
super((byte) 0);
}
/**
* Creates a zero delimited reader with the given max packet size
* and read buffer size.
*
* Exceeding the packet size will throw a ProtocolViolationException.
*
* @param readBufferSize the size of the read buffer (i.e. how many
* bytes are read in a single pass) - this only has effect on read
* efficiency and memory requirements.
* @param maxPacketSize the maximum packet size to accept.
*/
public ZeroDelimitedPacketReader(int readBufferSize, int maxPacketSize)
{
super((byte)0, readBufferSize, maxPacketSize);
}
/**
* Creates a zero delimited reader with the given max packet size
* and the default read buffer size.
*
* Exceeding the packet size will throw a ProtocolViolationException.
*
* @param maxPacketSize the maximum packet size to accept.
*/
public ZeroDelimitedPacketReader(int maxPacketSize)
{
super((byte) 0, DEFAULT_READ_BUFFER_SIZE, maxPacketSize);
}
}
naga-2.1/src/main/naga/packetreader/RegularPacketReader.java 0000664 0001750 0001750 00000005443 11653321253 023335 0 ustar mbanck mbanck package naga.packetreader;
import naga.NIOUtils;
import naga.PacketReader;
import naga.exception.ProtocolViolationException;
import java.nio.ByteBuffer;
/**
* Reads packet of the format
*
*
* Note that the maximum size for 4 bytes is a signed 32 bit int, not unsigned.
*
* @author Christoffer Lerno
*/
public class RegularPacketReader implements PacketReader
{
private final boolean m_bigEndian;
private ByteBuffer m_header;
private ByteBuffer m_content;
private int m_contentSize = -1;
/**
* Creates a regular packet reader with the given header size.
*
* @param headerSize the header size, 1 - 4 bytes.
* @param bigEndian big endian (largest byte first) or little endian (smallest byte first)
*/
public RegularPacketReader(int headerSize, boolean bigEndian)
{
if (headerSize < 1 || headerSize > 4) throw new IllegalArgumentException("Header must be between 1 and 4 bytes long.");
m_bigEndian = bigEndian;
m_header = ByteBuffer.allocate(headerSize);
m_contentSize = -1;
m_content = null;
}
/**
* Return the next buffer to use.
*
* @return the next buffer to use.
* @throws ProtocolViolationException if the header was read and the size of the content is
* larger or equal to Integer.MAX_VALUE.
*/
public ByteBuffer getBuffer() throws ProtocolViolationException
{
if (m_header.hasRemaining()) return m_header;
prepareContentBuffer();
return m_content;
}
/**
* Tries to read and parse the header if possible.
*
* Makes sure that the content buffer is initialized if the header has finished reading.
*
* @throws ProtocolViolationException if the header indicates that the size of the
* content is equal to or larger than Integer.MAX_VALUE.
*/
private void prepareContentBuffer() throws ProtocolViolationException
{
if (m_contentSize < 0 && !m_header.hasRemaining())
{
m_contentSize = NIOUtils.getPacketSizeFromByteBuffer(m_header, m_bigEndian);
if (m_contentSize < 0 || m_contentSize >= Integer.MAX_VALUE)
{
throw new ProtocolViolationException("Content size out of range, was: " + m_contentSize);
}
m_content = ByteBuffer.allocate(m_contentSize);
}
}
/**
* Return the next packet or null if no complete packet can be constructed.
*
* @return the next packet available or null if none is available.
* @throws ProtocolViolationException if the size of the packet is larger or equal to Integer.MAX_VALUE.
*/
public byte[] getNextPacket() throws ProtocolViolationException
{
prepareContentBuffer();
if (m_contentSize < 0 || m_content.hasRemaining())
{
return null;
}
byte[] content = m_content.array();
m_content = null;
m_header.rewind();
m_contentSize = -1;
return content;
}
}
naga-2.1/src/main/naga/packetreader/package-info.java 0000664 0001750 0001750 00000000163 11653321234 021776 0 ustar mbanck mbanck /**
* Package containing various ready-to-use {@code PacketReader} implementations.
*/
package naga.packetreader; naga-2.1/src/main/naga/eventmachine/ 0000775 0001750 0001750 00000000000 11653321253 016624 5 ustar mbanck mbanck naga-2.1/src/main/naga/eventmachine/DelayedAction.java 0000664 0001750 0001750 00000003736 11653321234 022204 0 ustar mbanck mbanck package naga.eventmachine;
import java.util.Date;
import java.util.concurrent.atomic.AtomicLong;
/**
* Holds a delayed runnable for the event service.
*
* This class implements the DelayedEvent interface and offers
* offering the ability to cancel an event before it is executed.
*
* @author Christoffer Lerno
*/
class DelayedAction implements Comparable
* Comparison is first done by execution time, then on id (i.e. creation order).
*
* @param o the other delayed action.
* @return -1, 0, 1 depending on where this action should be compared to the other action.
*/
@SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"})
public int compareTo(DelayedAction o)
{
if (m_time < o.m_time) return -1;
if (m_time > o.m_time) return 1;
if (m_id < o.m_id) return -1;
return m_id > o.m_id ? 1 : 0;
}
public Runnable getCall()
{
return m_call;
}
public long getTime()
{
return m_time;
}
@Override
public String toString()
{
return "DelayedAction @ " + new Date(m_time) + " [" + (m_call == null ? "Cancelled" : m_call) + "]";
}
}
naga-2.1/src/main/naga/eventmachine/EventMachine.java 0000664 0001750 0001750 00000015407 11653321253 022044 0 ustar mbanck mbanck package naga.eventmachine;
import naga.NIOService;
import java.io.IOException;
import java.util.Date;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.PriorityBlockingQueue;
/**
* EventMachine is a simple event service for driving asynchronous and delayed tasks
* together with the a Naga NIOService.
*
* Creating and starting an event machine:
*
* This method is thread-safe.
*
* @param runnable the runnable to execute on the server thread as soon as possible,
*/
public void asyncExecute(Runnable runnable)
{
executeLater(runnable, 0);
}
/**
* Execute a runnable on the Event/NIO thread after a delay.
*
* This is the primary way to execute delayed events, typically time-outs and similar
* behaviour.
*
* This method is thread-safe.
*
* @param runnable the runnable to execute after the given delay.
* @param msDelay the delay until executing this runnable.
* @return the delayed event created to execute later. This can be used
* to cancel the event.
*/
public DelayedEvent executeLater(Runnable runnable, long msDelay)
{
return queueAction(runnable, msDelay + System.currentTimeMillis());
}
/**
* Creates and queuest a delayed action for execution at a certain time.
*
* @param runnable the runnable to execute at the given time.
* @param time the time date when this runnable should execute.
* @return the delayed action created and queued.
*/
private DelayedAction queueAction(Runnable runnable, long time)
{
DelayedAction action = new DelayedAction(runnable, time);
m_queue.add(action);
m_service.wakeup();
return action;
}
/**
* Execute a runnable on the Event/NIO thread after at a certain time.
*
* The observer will receive all exceptions thrown by the underlying NIOService
* and by queued events.
*
* This method is thread-safe.
*
* @param observer the observer to use, may not be null.
* @throws NullPointerException if the observer is null.
*/
public void setObserver(ExceptionObserver observer)
{
if (observer == null) throw new NullPointerException();
m_observer = observer;
}
/**
* Returns the time when the next scheduled event will execute.
*
* @return a long representing the date of the next event, or Long.MAX_VALUE if
* no event is scheduled.
*/
public long timeOfNextEvent()
{
DelayedAction action = m_queue.peek();
return action == null ? Long.MAX_VALUE : action.getTime();
}
/**
* Causes the event machine to start running on a separate thread together with the
* NIOService.
*
* Will default to ExceptionObserver.DEFAULT if no observer was set.
*
* @return the current ExceptionObserver for this service.
*/
public ExceptionObserver getObserver()
{
return m_observer;
}
/**
* Returns the NIOService used by this event service.
*
* @return the NIOService that this event service uses.
*/
public NIOService getNIOService()
{
return m_service;
}
/**
* Return the current event service queue.
*
* @return a copy of the current queue.
*/
public Queue
* The notifyExceptionThrown will be called synchronously
* by the event machine if executing an event throws
* an exception.
*
* @author Christoffer Lerno
*/
public interface ExceptionObserver
{
ExceptionObserver DEFAULT = new ExceptionObserver()
{
@SuppressWarnings({"CallToPrintStackTrace"})
public void notifyExceptionThrown(Throwable e)
{
e.printStackTrace();
}
};
/**
* Notify the observer that an exception has been thrown.
*
* @param e the exception that was thrown.
*/
void notifyExceptionThrown(Throwable e);
}
naga-2.1/src/main/naga/eventmachine/package-info.java 0000664 0001750 0001750 00000000220 11653321234 022004 0 ustar mbanck mbanck /**
* An optional simple service for driving asynchronous and delayed tasks integrated with the Naga NIOService.
*/
package naga.eventmachine; naga-2.1/src/main/naga/eventmachine/DelayedEvent.java 0000664 0001750 0001750 00000002062 11653321234 022037 0 ustar mbanck mbanck package naga.eventmachine;
/**
* A cancellable, delayed event posted to the event service.
*
* @author Christoffer Lerno
*/
public interface DelayedEvent
{
/**
* Cancels this delayed event.
*
* Note that cancelling a delayed event is *not* guaranteed to
* remove the event from the queue. But it is guaranteed to
* clear the reference to the Runnable associated with the event.
* The method may be called multiple times with no ill effect.
*
* Cancelling an event while it is executing will not prevent it
* from executing.
*
* This metod is thread-safe.
*/
void cancel();
/**
* Returns the actual Runnable to be executed when this event runs.
*
* This will value will be null if this event has been cancelled.
*
* @return the call to execute with this event runs, or null if this
* event is cancelled.
*/
Runnable getCall();
/**
* Returns the time when this event will execute. See Date#getTime().
*
* @return a long representing the time when this event will occur.
*/
long getTime();
}
naga-2.1/src/main/naga/ServerSocketObserverAdapter.java 0000664 0001750 0001750 00000000571 11653321234 022453 0 ustar mbanck mbanck package naga;
import java.io.IOException;
/**
* Class with null-implementations for all callbacks.
*
* @author Christoffer Lerno
*/
public class ServerSocketObserverAdapter implements ServerSocketObserver
{
public void acceptFailed(IOException exception)
{
}
public void serverSocketDied(Exception e)
{
}
public void newConnection(NIOSocket nioSocket)
{
}
}
naga-2.1/src/main/naga/ChannelResponder.java 0000664 0001750 0001750 00000015432 11653321253 020260 0 ustar mbanck mbanck package naga;
import java.net.InetSocketAddress;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
/**
* The base class for socket and server socket responders.
*
* Handles basic functionality such as presenting IP and port,
* handling of key and channel as well as closing the socket.
*
* @author Christoffer Lerno
*/
abstract class ChannelResponder implements NIOAbstractSocket
{
private final NIOService m_service;
private final String m_ip;
private final InetSocketAddress m_address;
private final int m_port;
private final SelectableChannel m_channel;
private volatile boolean m_open;
private volatile SelectionKey m_key;
private volatile int m_interestOps;
private boolean m_observerSet;
/**
* Creates a new channel responder.
*
* @param service the NIOService this responder belongs to.
* @param channel the channel handled by this responder.
* @param address the address this channel is associated with.
*/
protected ChannelResponder(NIOService service, SelectableChannel channel, InetSocketAddress address)
{
m_channel = channel;
m_service = service;
m_open = true;
m_key = null;
m_interestOps = 0;
m_observerSet = false;
m_address = address;
m_ip = address.getAddress().getHostAddress();
m_port = address.getPort();
}
public InetSocketAddress getAddress()
{
return m_address;
}
public String getIp()
{
return m_ip;
}
public int getPort()
{
return m_port;
}
/**
* @return the NIOService this responder is connected to.
*/
protected NIOService getNIOService()
{
return m_service;
}
/**
* Mark the observer for this responder as set.
*
* This does not mean that the observer is really set, but instead that
* a set has been queued on the NIOService thread.
*
* This call prevents the observer from being set more than once.
*/
protected void markObserverSet()
{
synchronized (this)
{
if (m_observerSet) throw new IllegalStateException("Listener already set.");
m_observerSet = true;
}
}
/**
* Called by the NIOService when the key has read interest ready.
*/
void socketReadyForRead()
{
throw new UnsupportedOperationException(getClass() + " does not support read.");
}
/**
* Called by the NIOService when the key has write interest ready.
*/
void socketReadyForWrite()
{
throw new UnsupportedOperationException(getClass() + " does not support write.");
}
/**
* Called by the NIOService when the key has accept interest ready.
*/
void socketReadyForAccept()
{
throw new UnsupportedOperationException(getClass() + " does not support accept.");
}
/**
* Called by the NIOService when the key has connect interest ready.
*/
void socketReadyForConnect()
{
throw new UnsupportedOperationException(getClass() + " does not support connect.");
}
/**
* Get the channel used by this responder.
*
* This method is thread-safe.
*
* @return the channel used by the responder.
*/
protected SelectableChannel getChannel()
{
return m_channel;
}
/**
* Sets the selection key of this responder.
*
* This method is called on the NIOService-thread.
*
* @param key the selection key for this responder.
* @throws IllegalStateException if the selection key already is set.
*/
void setKey(SelectionKey key)
{
if (m_key != null) throw new IllegalStateException("Tried to set selection key twice");
m_key = key;
if (!isOpen())
{
// If we closed this before receiving the key, we should cancel the key.
NIOUtils.cancelKeySilently(m_key);
return;
}
keyInitialized();
synchronizeKeyInterestOps();
}
/**
* Returns the selection key or null if the key is not set.
*
* This method is thread-safe.
*
* @return the selection key or nul if the responder is not yet registered.
*/
protected SelectionKey getKey()
{
return m_key;
}
/**
* This method is called after the SelectionKey is set.
*
* This method is called on the NIOService-thread.
*/
abstract void keyInitialized();
public boolean isOpen()
{
return m_open;
}
public void close()
{
close(null);
}
/**
* Asynchronously closes the channel with a given exception as a cause.
*
* @param exception the exception that caused the close, or null if this was
* closed by the user of this responder.
*/
protected void close(Exception exception)
{
if (isOpen())
{
getNIOService().queue(new CloseEvent(this, exception));
}
}
/**
* Synchronizes the desired interest ops with the key interests ops,
* if the key is initialized.
*
* This method is thread-safe.
*/
private void synchronizeKeyInterestOps()
{
if (m_key != null)
{
try
{
int oldOps = m_key.interestOps();
if ((m_interestOps & SelectionKey.OP_CONNECT) != 0)
{
m_key.interestOps(SelectionKey.OP_CONNECT);
}
else
{
m_key.interestOps(m_interestOps);
}
if (m_key.interestOps() != oldOps)
{
m_service.wakeup();
}
}
catch (CancelledKeyException e)
{
// Ignore these.
}
}
}
/**
* Deleted an interest on a key.
*
* This method is thread-safe.
*
* @param interest the interest to delete.
*/
protected void deleteInterest(int interest)
{
m_interestOps = m_interestOps & ~interest;
synchronizeKeyInterestOps();
}
/**
* Add an interest to the key, or change the currently pending interest.
*
* This method is thread-safe.
*
* @param interest the interest to add.
*/
protected void addInterest(int interest)
{
m_interestOps |= interest;
synchronizeKeyInterestOps();
}
/**
* Returns a string on the format [ip]:[port]
* This method is thread-safe.
*
* @return a string on the format IP:port.
*/
@Override
public String toString()
{
return m_ip + ":" + m_port;
}
/**
* Called once when the responder goes from state open to closed.
*
* This method is called on the NIOService thread.
*
* @param e the exception that caused the shutdown, may be null.
*/
protected abstract void shutdown(Exception e);
/**
* A close event sent when close() is called.
*
* When run this event sets m_open to false and silently cancels
* the key and closes the channel.
*
* Finally the responder's shutdown method is called.
*/
private static class CloseEvent implements Runnable
{
private final ChannelResponder m_responder;
private final Exception m_exception;
private CloseEvent(ChannelResponder responder, Exception e)
{
m_responder = responder;
m_exception = e;
}
public void run()
{
if (m_responder.isOpen())
{
m_responder.m_open = false;
NIOUtils.closeKeyAndChannelSilently(m_responder.getKey(), m_responder.getChannel());
m_responder.shutdown(m_exception);
}
}
}
}
naga-2.1/src/main/naga/SocketObserver.java 0000664 0001750 0001750 00000004453 11653321253 017767 0 ustar mbanck mbanck package naga;
/**
* This interface contains the callbacks used by a NIOSocket
* to inform its observer of events.
*
* All callbacks will be run on the NIOService-thread,
* so callbacks should try to return as quickly as possible
* since the callback blocks communication on all sockets
* of the service.
*
* @author Christoffer Lerno
*/
public interface SocketObserver
{
/** A null object used as the default observer */
SocketObserver NULL = new SocketObserverAdapter();
/**
* Called by the NIOService on the NIO thread when a connection completes on a socket.
*
* Note: Since this is a direct callback on the NIO thread, this method will suspend IO on
* all other connections until the method returns. It is therefore strongly recommended
* that the implementation of this method returns as quickly as possible to avoid blocking IO.
*
* @param nioSocket the socket that completed its connect.
*/
void connectionOpened(NIOSocket nioSocket);
/**
* Called by the NIOService on the NIO thread when a connection is disconnected.
*
* This may be sent even if a
* Note: Since this is a direct callback on the NIO thread, this method will suspend IO on
* all other connections until the method returns. It is therefore strongly recommended
* that the implementation of this method returns as quickly as possible to avoid blocking IO.
*
* @param nioSocket the socket that was disconnected.
* @param exception the exception that caused the connection to break, may be null.
*/
void connectionBroken(NIOSocket nioSocket, Exception exception);
/**
* Called by the NIOService on the NIO thread when a packet is finished reading.
* The byte array contains the packet as parsed by the current PacketReader.
*
* Note: Since this is a direct callback on the NIO thread, this method will suspend IO on
* all other connections until the method returns. It is therefore strongly recommended
* that the implementation of this method returns as quickly as possible to avoid blocking IO.
*
* @param socket the socket we received a packet on.
* @param packet the packet we received.
*/
void packetReceived(NIOSocket socket, byte[] packet);
}
naga-2.1/src/main/naga/SocketObserverAdapter.java 0000664 0001750 0001750 00000000612 11653321253 021261 0 ustar mbanck mbanck package naga;
/**
* Class with null-implementation of all SocketObserver callbacks.
*
* @author Christoffer Lerno
*/
public class SocketObserverAdapter implements SocketObserver
{
public void connectionBroken(NIOSocket nioSocket, Exception exception)
{
}
public void packetReceived(NIOSocket socket, byte[] packet)
{
}
public void connectionOpened(NIOSocket nioSocket)
{
}
}
naga-2.1/src/main/naga/ServerSocketObserver.java 0000664 0001750 0001750 00000004430 11653321234 021150 0 ustar mbanck mbanck package naga;
import java.io.IOException;
/**
* Implemented by an observer to a server socket.
*
* A server socket observer is responsible for handling
* incoming connections by implementing a callback
* to properly assign a SocketObserver to the new connections.
*
* All callbacks will be run on the NIOService-thread,
* so callbacks should try to return as quickly as possible
* since the callback blocks communication on all sockets
* of the service.
*
* @author Christoffer Lerno
*/
public interface ServerSocketObserver
{
/**
* Called by the NIOService on the NIO thread when an accept fails on the socket.
*
* Note: Since this is a direct callback on the NIO thread, this method will suspend IO on
* all other connections until the method returns. It is therefore strongly recommended
* that the implementation of this method returns as quickly as possible to avoid blocking IO.
*
* @param exception the reason for the failure, never null.
*/
void acceptFailed(IOException exception);
/**
* Called by the NIOService on the NIO thread when the server socket is closed.
*
* Note: Since this is a direct callback on the NIO thread, this method will suspend IO on
* all other connections until the method returns. It is therefore strongly recommended
* that the implementation of this method returns as quickly as possible to avoid blocking IO.
*
* @param exception the exception that caused the close, or null if this was
* caused by an explicit
* The normal behaviour would be for the observer to assign a reader and a writer to the socket,
* and then finally invoke
* Note: Since this is a direct callback on the NIO thread, this method will suspend IO on
* all other connections until the method returns. It is therefore strongly recommended
* that the implementation of this method returns as quickly as possible to avoid blocking IO.
*
* @param nioSocket the socket that was accepted.
*/
void newConnection(NIOSocket nioSocket);
}
naga-2.1/src/main/naga/NIOSocket.java 0000664 0001750 0001750 00000007676 11653321253 016637 0 ustar mbanck mbanck package naga;
import java.net.Socket;
/**
* Interface for the NIOSocket, which is
* an asynchronous facade to an underlying Socket.
*
* The NIOSocket executes callbacks to a Socket observer
* to react to incoming packets and other events.
*
* @author Christoffer Lerno
*/
public interface NIOSocket extends NIOAbstractSocket
{
/**
* Write a packet of bytes asynchronously on this socket.
*
* The bytes will be sent to the PacketWriter belonging to this
* socket for dispatch. However, if the queue is full (i.e. the new
* queue size would exceed
* This method is thread-safe.
*
* @param packet the packet to send.
* @return true if the packet was queued, false if the queue limit
* was reached and the packet was thrown away.
*/
boolean write(byte[] packet);
/**
* Return the total number of bytes read on this socket since
* it was opened.
*
* This method is thread-safe.
*
* @return the total number of bytes read on this socket.
*/
long getBytesRead();
/**
* Return the total number of bytes written on this socket
* since it was opened.
*
* This method is thread-safe.
*
* @return the total number of bytes written on this socket.
*/
long getBytesWritten();
/**
* Return the time this socket has been open.
*
* This method is thread-safe.
*
* @return the time this socket has been open in ms.
*/
long getTimeOpen();
/**
* This method returns the number of bytes queued for dispatch.
* This size is compared against the maximum queue size to determine if additional packets
* will be refused or not.
*
* However, this number does not include the packet currently waiting to be written.
*
* This method is thread-safe.
*
* @return the total size of the packets waiting to be dispatched, exluding the currently
* dispatching packet.
*/
long getWriteQueueSize();
/**
* The current maximum queue size in bytes.
*
* This method is thread-safe.
*
* @return the current maximum queue size.
*/
int getMaxQueueSize();
/**
* Sets the maximum number of bytes allowed in the queue for this socket. If this
* number is less than 1, the queue is unbounded.
*
* This method is thread-safe.
*
* @param maxQueueSize the new max queue size. A value less than 1 is an unbounded queue.
*/
void setMaxQueueSize(int maxQueueSize);
/**
* Sets the packet reader for this socket.
*
* @param packetReader the packet reader to interpret the incoming byte stream.
*/
void setPacketReader(PacketReader packetReader);
/**
* Sets the packet writer for this socket.
*
* @param packetWriter the packet writer to interpret the incoming byte stream.
*/
void setPacketWriter(PacketWriter packetWriter);
/**
* Opens the socket for reads.
*
* The socket observer will receive connects, disconnects and packets.
* If the socket was opened or disconnected before the observer was attached,
* the socket observer will still receive those callbacks.
*
* This method is thread-safe, but may only be called once.
*
* @param socketObserver the observer to receive packets and be notified of connects/disconnects.
* @throws IllegalStateException if the method already has been called.
*/
void listen(SocketObserver socketObserver);
/**
* Causes the socket to close after writing the current entries in the queue
* (consequent entries will be thrown away).
*
* Also see
* This method is thread-safe.
*/
void closeAfterWrite();
/**
* Allows access to the underlying socket.
*
* Note that accessing streams or closing the socket will
* put this NIOSocket in an undefined state
*
* @return return the underlying socket.
*/
Socket socket();
}
naga-2.1/src/main/naga/examples/ 0000775 0001750 0001750 00000000000 11653321253 015774 5 ustar mbanck mbanck naga-2.1/src/main/naga/examples/Rot13Server.java 0000664 0001750 0001750 00000005324 11653321253 020742 0 ustar mbanck mbanck package naga.examples;
import naga.*;
import naga.packetreader.AsciiLinePacketReader;
import java.io.IOException;
/**
* Creates a Rot13Server that takes a line of text and returns the Rot13 version of the
* text.
*
* Run using {@code java naga.examples.EchoServer [port]}
*
* @author Christoffer Lerno
*/
public class EchoServer
{
EchoServer()
{}
/**
* Runs the echo server.
*
* @param args command line arguments, assumed to be a 1 length string containing a port.
*/
public static void main(String... args)
{
int port = Integer.parseInt(args[0]);
try
{
NIOService service = new NIOService();
NIOServerSocket socket = service.openServerSocket(port);
socket.listen(new ServerSocketObserverAdapter()
{
public void newConnection(NIOSocket nioSocket)
{
System.out.println("Client " + nioSocket.getIp() + " connected.");
nioSocket.listen(new SocketObserverAdapter()
{
public void packetReceived(NIOSocket socket, byte[] packet)
{
socket.write(packet);
}
public void connectionBroken(NIOSocket nioSocket, Exception exception)
{
System.out.println("Client " + nioSocket.getIp() + " disconnected.");
}
});
}
});
socket.setConnectionAcceptor(ConnectionAcceptor.ALLOW);
while (true)
{
service.selectBlocking();
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
naga-2.1/src/main/naga/examples/ValidationServer.java 0000664 0001750 0001750 00000006003 11653321234 022116 0 ustar mbanck mbanck package naga.examples;
import naga.*;
import naga.packetreader.RegularPacketReader;
import naga.packetwriter.RegularPacketWriter;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
/**
* An example validation server to validate logins.
*
* @author Christoffer Lerno
*/
public class ValidationServer
{
ValidationServer() {
}
public static void main(String... args)
{
int port = Integer.parseInt(args[0]);
// Create a map with users and passwords.
final Map
* Use with {@code java naga.examples.ValidationClient [host] [port] [account] [password]}.
* @author Christoffer Lerno
*/
public class ValidationClient
{
ValidationClient()
{}
/**
* Make a login request to the server.
*
* @param args assumed to be 4 strings representing host, port, account and password.
*/
public static void main(String... args)
{
try
{
// Parse arguments.
String host = args[0];
int port = Integer.parseInt(args[1]);
String account = args[2];
String password = args[3];
// Prepare the login packet, packing two UTF strings together
// using a data output stream.
ByteArrayOutputStream stream = new ByteArrayOutputStream();
DataOutputStream dataStream = new DataOutputStream(stream);
dataStream.writeUTF(account);
dataStream.writeUTF(password);
dataStream.flush();
final byte[] content = stream.toByteArray();
dataStream.close();
// Start up the service.
NIOService service = new NIOService();
// Open our socket.
NIOSocket socket = service.openSocket(host, port);
// Use regular 1 byte header reader/writer
socket.setPacketReader(new RegularPacketReader(1, true));
socket.setPacketWriter(new RegularPacketWriter(1, true));
// Start listening to the socket.
socket.listen(new SocketObserver()
{
public void connectionOpened(NIOSocket nioSocket)
{
System.out.println("Sending login...");
nioSocket.write(content);
}
public void packetReceived(NIOSocket socket, byte[] packet)
{
try
{
// Read the UTF-reply and print it.
String reply = new DataInputStream(new ByteArrayInputStream(packet)).readUTF();
System.out.println("Reply was: " + reply);
// Exit the program.
System.exit(0);
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void connectionBroken(NIOSocket nioSocket, Exception exception)
{
System.out.println("Connection failed.");
// Exit the program.
System.exit(-1);
}
});
// Read IO until process exits.
while (true)
{
service.selectBlocking();
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
naga-2.1/src/main/naga/examples/package-info.java 0000664 0001750 0001750 00000000251 11653321234 021160 0 ustar mbanck mbanck /**
* Various examples on how to use Naga.
*
* This method is thread-safe.
*/
void close();
/**
* Returns the InetSocketAddress for this socket.
*
* This method is thread-safe.
*
* @return the InetSocketAddress this socket connects to.
*/
InetSocketAddress getAddress();
/**
* Returns the current state of this socket.
*
* This method is thread-safe.
*
* @return true if the connection is socket is open, false if closed.
*/
boolean isOpen();
/**
* Reports the IP used by this socket.
*
* This method is thread-safe.
*
* @return the IP of this socket.
*/
String getIp();
/**
* Returns the port in use by this socket.
*
* This method is thread-safe.
*
* @return the port of this socket.
*/
int getPort();
}
naga-2.1/src/main/naga/NIOServerSocket.java 0000664 0001750 0001750 00000005121 11653321234 020004 0 ustar mbanck mbanck package naga;
import java.net.ServerSocket;
/**
* Interface for the NIOServerSocket, which is
* an asynchronous facade to an underlying ServerSocket.
*
* The NIOServerSocket executes callbacks to a ServerSocket observer
* to react to new connections and other events.
*
* @author Christoffer Lerno
*/
public interface NIOServerSocket extends NIOAbstractSocket
{
/**
* Returns the total number of connections made on this socket since
* it opened.
*
* This method is thread-safe.
*
* @return the total number of connections made on this server socket.
*/
long getTotalConnections();
/**
* Returns the total number of refused connections on this socket since
* it opened.
*
* This method is thread-safe.
*
* @return the total number of refused connections on this server socket.
*/
long getTotalRefusedConnections();
/**
* Returns the total number of accepted connections on this socket since
* it opened.
*
* This method is thread-safe.
*
* @return the total number of accepted connections on this server socket.
*/
long getTotalAcceptedConnections();
/**
* Returns the total number of failed connections on this socket since
* it opened.
*
* This method is thread-safe.
*
* @return the total number of failed connections on this server socket.
*/
long getTotalFailedConnections();
/**
* Associates a server socket observer with this server socket and
* starts accepting connections.
*
* This method is thread-safe, but may only be called once.
*
* @param observer the observer to receive callbacks from this socket.
* @throws NullPointerException if the observer given is null.
* @throws IllegalStateException if an observer has already been set.
*/
void listen(ServerSocketObserver observer);
/**
* Sets the connection acceptor for this server socket.
*
* A connection acceptor determines if a connection should be
* disconnected or not after the initial accept is done.
*
* For more information, see the documentation for
* This method is thread-safe.
*
* @param acceptor the acceptor to use, or null to default to
* ConnectorAcceptor.DENY.
*/
void setConnectionAcceptor(ConnectionAcceptor acceptor);
/**
* Allows access to the underlying server socket.
*
* Note calling close and similar functions on this socket
* will put the NIOServerSocket in an undefined state
*
* @return return the underlying server socket.
*/
ServerSocket socket();
}
naga-2.1/src/main/naga/package-info.java 0000664 0001750 0001750 00000000550 11653321234 017344 0 ustar mbanck mbanck /**
* The main Naga classes.
*
* This can be used to implement black-listing of certain IP-ranges
* or to limit the number of simultaneous connection. However,
* in most cases it is enough to use the ConnectorAcceptor.ALLOW which
* accepts all incoming connections.
*
* Note that a NIOServerSocket defaults to the ConnectorAcceptor.ALLOW
* acceptor when it is created.
*
*
* @author Christoffer Lerno
*/
public interface ConnectionAcceptor
{
/**
* A connection acceptor that refuses all connections.
*/
ConnectionAcceptor DENY = new ConnectionAcceptor()
{
public boolean acceptConnection(InetSocketAddress address)
{
return false;
}
};
/**
* A connection acceptor that accepts all connections.
*/
ConnectionAcceptor ALLOW = new ConnectionAcceptor()
{
public boolean acceptConnection(InetSocketAddress address)
{
return true;
}
};
/**
* Return true if the connection should be accepted, false otherwise.
*
* Note: This callback is run on the NIOService thread. This means it will block
* all other reads, writes and accepts on the service while it executes.
* For this reason it is recommended that this method should return fairly quickly
* (i.e. don't make reverse ip lookups or similar - potentially very slow - calls).
*
*
* @param inetSocketAddress the adress the connection came from.
* @return true to accept, false to refuse.
*/
boolean acceptConnection(InetSocketAddress inetSocketAddress);
}
naga-2.1/src/main/naga/ServerSocketChannelResponder.java 0000664 0001750 0001750 00000010034 11653321253 022611 0 ustar mbanck mbanck package naga;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* @author Christoffer Lerno
*/
class ServerSocketChannelResponder extends ChannelResponder implements NIOServerSocket
{
private long m_totalRefusedConnections;
private long m_totalAcceptedConnections;
private long m_totalFailedConnections;
private long m_totalConnections;
private volatile ConnectionAcceptor m_connectionAcceptor;
private ServerSocketObserver m_observer;
@SuppressWarnings({"ObjectToString"})
public ServerSocketChannelResponder(NIOService service,
ServerSocketChannel channel,
InetSocketAddress address) throws IOException
{
super(service, channel, address);
m_observer = null;
setConnectionAcceptor(ConnectionAcceptor.ALLOW);
m_totalRefusedConnections = 0;
m_totalAcceptedConnections = 0;
m_totalFailedConnections = 0;
m_totalConnections = 0;
}
public void keyInitialized()
{
// Do nothing, since the accept automatically will be set.
}
public ServerSocketChannel getChannel()
{
return (ServerSocketChannel) super.getChannel();
}
/**
* Callback to tell the object that there is at least one accept that can be done on the server socket.
*/
public void socketReadyForAccept()
{
m_totalConnections++;
SocketChannel socketChannel = null;
try
{
socketChannel = getChannel().accept();
if (socketChannel == null)
{
// This means there actually wasn't any connection waiting,
// so tick down the number of actual total connections.
m_totalConnections--;
return;
}
InetSocketAddress address = (InetSocketAddress) socketChannel.socket().getRemoteSocketAddress();
// Is this connection acceptable?
if (!m_connectionAcceptor.acceptConnection(address))
{
// Connection was refused by the socket owner, so update stats and close connection
m_totalRefusedConnections++;
NIOUtils.closeChannelSilently(socketChannel);
return;
}
m_observer.newConnection(getNIOService().registerSocketChannel(socketChannel, address));
m_totalAcceptedConnections++;
}
catch (IOException e)
{
// Close channel in case it opened.
NIOUtils.closeChannelSilently(socketChannel);
m_totalFailedConnections++;
m_observer.acceptFailed(e);
}
}
public void notifyWasCancelled()
{
close();
}
public long getTotalRefusedConnections()
{
return m_totalRefusedConnections;
}
public long getTotalConnections()
{
return m_totalConnections;
}
public long getTotalFailedConnections()
{
return m_totalFailedConnections;
}
public long getTotalAcceptedConnections()
{
return m_totalAcceptedConnections;
}
public void setConnectionAcceptor(ConnectionAcceptor connectionAcceptor)
{
m_connectionAcceptor = connectionAcceptor == null ? ConnectionAcceptor.DENY : connectionAcceptor;
}
@SuppressWarnings({"CallToPrintStackTrace"})
private void notifyObserverSocketDied(Exception exception)
{
if (m_observer == null) return;
try
{
m_observer.serverSocketDied(exception);
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void listen(ServerSocketObserver observer)
{
if (observer == null) throw new NullPointerException();
markObserverSet();
getNIOService().queue(new BeginListenEvent(observer));
}
private class BeginListenEvent implements Runnable
{
private final ServerSocketObserver m_newObserver;
private BeginListenEvent(ServerSocketObserver socketObserver)
{
m_newObserver = socketObserver;
}
public void run()
{
m_observer = m_newObserver;
if (!isOpen())
{
notifyObserverSocketDied(null);
return;
}
addInterest(SelectionKey.OP_ACCEPT);
}
@Override
public String toString()
{
return "BeginListen[" + m_newObserver + "]";
}
}
protected void shutdown(Exception e)
{
notifyObserverSocketDied(e);
}
public ServerSocket socket()
{
return getChannel().socket();
}
}
naga-2.1/src/main/naga/NIOService.java 0000664 0001750 0001750 00000027107 11653321253 016776 0 ustar mbanck mbanck package naga;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* This class forms the basis of the NIO handling in Naga.
*
* Common usage is to create a single instance of this service and
* then run one other the select methods in a loop.
*
* Use {@link naga.NIOService#openSocket(String, int)} to open a socket to a remote server,
* and {@link naga.NIOService#openServerSocket(int)} to open a server socket locally.
*
* Note that the NIOServerSocket by default opens in a "refuse connections" state. To
* start accepting players, the socket's acceptor must first be set to accept connections.
* See documentation for openServerSocket for more details.
*
* Example use:
*
* Using the server socket:
*
* This roughly corresponds to creating a regular socket using new Socket(host, port).
*
* This method is thread-safe.
*
* @param host the host we want to connect to.
* @param port the port to use for the connection.
* @return a NIOSocket object for asynchronous communication.
* @throws IOException if registering the new socket failed.
*/
public NIOSocket openSocket(String host, int port) throws IOException
{
return openSocket(InetAddress.getByName(host), port);
}
/**
* Open a normal socket to the host on the given port returning
* a NIOSocket.
*
* This roughly corresponds to creating a regular socket using new Socket(inetAddress, port).
*
* This method is thread-safe.
*
* @param inetAddress the address we want to connect to.
* @param port the port to use for the connection.
* @return a NIOSocket object for asynchronous communication.
* @throws IOException if registering the new socket failed.
*/
public NIOSocket openSocket(InetAddress inetAddress, int port) throws IOException
{
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
InetSocketAddress address = new InetSocketAddress(inetAddress, port);
channel.connect(address);
return registerSocketChannel(channel, address);
}
/**
* Open a server socket on the given port.
*
* This roughly corresponds to using new ServerSocket(port, backlog);
*
* This method is thread-safe.
*
* @param port the port to open.
* @param backlog the maximum connection backlog (i.e. connections pending accept)
* @return a NIOServerSocket for asynchronous connection to the server socket.
* @throws IOException if registering the socket fails.
*/
public NIOServerSocket openServerSocket(int port, int backlog) throws IOException
{
return openServerSocket(new InetSocketAddress(port), backlog);
}
/**
* Open a server socket on the given port with the default connection backlog.
*
* This roughly corresponds to using new ServerSocket(port);
*
* This method is thread-safe.
*
* @param port the port to open.
* @return a NIOServerSocket for asynchronous connection to the server socket.
* @throws IOException if registering the socket fails.
*/
public NIOServerSocket openServerSocket(int port) throws IOException
{
return openServerSocket(port, -1);
}
/**
* Open a server socket on the address.
*
* This method is thread-safe.
*
* @param address the address to open.
* @param backlog the maximum connection backlog (i.e. connections pending accept)
* @return a NIOServerSocket for asynchronous connection to the server socket.
* @throws IOException if registering the socket fails.
*/
public NIOServerSocket openServerSocket(InetSocketAddress address, int backlog) throws IOException
{
ServerSocketChannel channel = ServerSocketChannel.open();
channel.socket().setReuseAddress(true);
channel.socket().bind(address, backlog);
channel.configureBlocking(false);
ServerSocketChannelResponder channelResponder = new ServerSocketChannelResponder(this, channel, address);
queue(new RegisterChannelEvent(channelResponder));
return channelResponder;
}
/**
* Internal method to mark a socket channel for pending registration
* and create a NIOSocket wrapper around it.
*
* This method is thread-safe.
*
* @param socketChannel the socket channel to wrap.
* @param address the address for this socket.
* @return the NIOSocket wrapper.
* @throws IOException if configuring the channel fails, or the underlying selector is closed.
*/
NIOSocket registerSocketChannel(SocketChannel socketChannel, InetSocketAddress address) throws IOException
{
socketChannel.configureBlocking(false);
SocketChannelResponder channelResponder = new SocketChannelResponder(this, socketChannel, address);
queue(new RegisterChannelEvent(channelResponder));
return channelResponder;
}
/**
* Internal method to execute events on the internal event queue.
*
* This method should only ever be called from the NIOService thread.
*/
private void executeQueue()
{
Runnable event;
while ((event = m_internalEventQueue.poll()) != null)
{
event.run();
}
}
/**
* Internal method to handle the key set generated by the internal Selector.
*
* Will simply remove each entry and handle the key.
*
* Called on the NIOService thread.
*/
private void handleSelectedKeys()
{
// Loop through all selected keys and handle each key at a time.
for (Iterator
* Will delegate actual actions to the associated ChannelResponder.
*
* Called on the NIOService thread.
* @param key the key to handle.
*/
private void handleKey(SelectionKey key)
{
ChannelResponder responder = (ChannelResponder) key.attachment();
try
{
if (key.isReadable())
{
responder.socketReadyForRead();
}
if (key.isWritable())
{
responder.socketReadyForWrite();
}
if (key.isAcceptable())
{
responder.socketReadyForAccept();
}
if (key.isConnectable())
{
responder.socketReadyForConnect();
}
}
catch (CancelledKeyException e)
{
responder.close(e);
// The key was cancelled and will automatically be removed next select.
}
}
/**
* Close the entire service.
*
* This will disconnect all sockets associated with this service.
*
* It is not possible to restart the service once closed.
*
* This method is thread-safe.
*/
public void close()
{
if (!isOpen()) return;
queue(new ShutdownEvent());
}
/**
* Determine if this service is open.
*
* This method is thread-safe.
*
* @return true if the service is open, false otherwise.
*/
public boolean isOpen()
{
return m_selector.isOpen();
}
/**
* Queues an event on the NIOService queue.
*
* This method is thread-safe, but should in general not be used by
* other applications.
*
* @param event the event to run on the NIOService-thread.
*/
public void queue(Runnable event)
{
m_internalEventQueue.add(event);
wakeup();
}
/**
* Returns a copy of the internal event queue.
*
* @return a copy of the internal event queue.
*/
public Queue
* To implement a packet reader, the reader has to offer the currently
* used byte buffer whenever the NIO service calls
*
* Note that getNextPacket() will be called repeatedly until it returns null.
*
* @author Christoffer Lerno
*/
public interface PacketReader
{
/**
* Return the currently used byte buffer. The NIOSocket will use this byte buffer and perform
* a SocketChannel.read(ByteBuffer) on it.
*
* The reader is guaranteed not to have this method called more than once before a
* call to
* This call may or may not have been proceeded by a call to getBuffer().
*
* The calling thread will call this method repeatedly until it returns null.
*
* @return a byte array containing the data of a packet, or null if not packet can be created
* yet from the data read.
* @throws ProtocolViolationException if a protocol violation was detected when
* parsing the next packet.
*/
byte[] getNextPacket() throws ProtocolViolationException;
}
naga-2.1/src/main/naga/NIOUtils.java 0000664 0001750 0001750 00000006613 11653321253 016475 0 ustar mbanck mbanck package naga;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.SelectionKey;
/**
* A collection of utilites used by various classes.
*
* @author Christoffer Lerno
*/
public class NIOUtils
{
NIOUtils() {}
/**
* Silently close both a key and a channel.
*
* @param key the key to cancel, may be null.
* @param channel the channel to close, may be null.
*/
public static void closeKeyAndChannelSilently(SelectionKey key, Channel channel)
{
closeChannelSilently(channel);
cancelKeySilently(key);
}
/**
* Creates a byte buffer with a given length with an encoded value,
* in either big or little endian encoding (i.e. biggest or smallest byte first).
*
* @param headerSize the header size in bytes. 1-4.
* @param valueToEncode the value to encode, 0 <= value < 2^(headerSize * 8)
* @param bigEndian if the encoding is big endian or not.
* @return a byte buffer with the number encoded.
* @throws IllegalArgumentException if the value is out of range for the given header size.
*/
public static ByteBuffer getByteBufferFromPacketSize(int headerSize, int valueToEncode, boolean bigEndian)
{
if (valueToEncode < 0) throw new IllegalArgumentException("Payload size is less than 0.");
// If header size is 4, we get valueToEncode >> 32, which is defined as valueToEncode >> 0 for int.
// Therefore, we handle the that case separately, as any int will fit in 4 bytes.
if (headerSize != 4 && valueToEncode >> (headerSize * 8) > 0)
{
throw new IllegalArgumentException("Payload size cannot be encoded into " + headerSize + " byte(s).");
}
ByteBuffer header = ByteBuffer.allocate(headerSize);
for (int i = 0; i < headerSize; i++)
{
int index = bigEndian ? (headerSize - 1 - i) : i;
// We do not need to extend valueToEncode here, since the maximum is valueToEncode >> 24
header.put((byte) (valueToEncode >> (8 * index) & 0xFF));
}
header.rewind();
return header;
}
/**
* Converts a value in a header buffer encoded in either big or little endian
* encoding.
*
* Note that trying to decode a value larger than 2^31 - 2 is not supported.
*
* @param header the header to encode from.
* @param bigEndian if the encoding is big endian or not.
* @return the decoded number.
*/
public static int getPacketSizeFromByteBuffer(ByteBuffer header, boolean bigEndian)
{
long packetSize = 0;
if (bigEndian)
{
header.rewind();
while (header.hasRemaining())
{
packetSize <<= 8;
packetSize += header.get() & 0xFF;
}
}
else
{
header.rewind();
int shift = 0;
while (header.hasRemaining())
{
// We do not need to extend valueToEncode here, since the maximum is valueToEncode >> 24
packetSize += (header.get() & 0xFF) << shift;
shift += 8;
}
}
return (int) packetSize;
}
/**
* Silently close a channel.
*
* @param channel the channel to close, may be null.
*/
public static void closeChannelSilently(Channel channel)
{
try
{
if (channel != null)
{
channel.close();
}
}
catch (IOException e)
{
// Do nothing
}
}
/**
* Silently cancel a key.
*
* @param key the key to cancel, may be null.
*/
public static void cancelKeySilently(SelectionKey key)
{
try
{
if (key != null) key.cancel();
}
catch (Exception e)
{
// Do nothing
}
}
} naga-2.1/version.properties 0000664 0001750 0001750 00000000163 11653321253 015340 0 ustar mbanck mbanck #Build Number for ANT. Do not edit!
#Sun Apr 25 09:31:26 CEST 2010
build.number=43
major.version=2
minor.version=1
naga-2.1/build.properties 0000664 0001750 0001750 00000000170 11653321234 014747 0 ustar mbanck mbanck build.dir=_BUILD
build.classes.dir=${build.dir}/classes
main.dir=src/main
docs.dir=${build.dir}/docs/api
dist.dir=_DIST
* [header 1-4 bytes] => content size
*
*
* [content] => 0-255/0-65535/0-16777215/0-2147483646
*
* EventMachine em = new EventMachine();
* // Start our event machine thread:
* em.start();
*
* Delayed execution:
*
* em.executeLater(new Runnable() {
* public void run()
* {
* // Code here will execute after 1 second on the nio thread.
* }
* }, 1000);
*
* Asynchronous execution, i.e. putting a task from another thread
* to be executed on the EventMachine thread.
*
* em.asyncExecute(new Runnable() {
* public void run()
* {
* // Code here will be executed on the nio thread.
* }
* });
*
* It is possible to cancel scheduled tasks:
*
* // Schedule an event
* DelayedEvent event = em.executeLater(new Runnable()
* public void run()
* {
* // Code to run in 1 minute.
* }
* }, 60000);
* // Cancel the event before it is executed.
* event.cancel();
*
*
* @author Christoffer Lerno
*/
public class EventMachine
{
private final NIOService m_service;
private final QueueconnectionOpened(NIOSocket)
* wasn't ever called, since the connect itself may
* fail.
* close()
on the NIOServerSocket.
*/
void serverSocketDied(Exception exception);
/**
* Called by the NIOService on the NIO thread when a new connection has been accepted by the socket.
* NIOSocket#listen(SocketObserver)
on the socket.
* getMaxQueueSize()
),
* the packet is discarded and the method returns false.
* close()
if you want to immediately close the socket.
* naga.ConnectionAcceptor
.
*
* Using regular sockets:
*
* NIOService service = new NIOService;
* NIOServerSocket serverSocket = service.openServerSocket(1234);
* serverSocket.setConnectionAcceptor(myAcceptor);
* serverSocket.listen(myObserver);
*
*
* @author Christoffer Lerno
*/
public class NIOService
{
/** The selector used by this service */
private final Selector m_selector;
private final Queue
* NIOService service = new NIOService;
* NIOSocket socket = service.openSocket("www.google.com", 1234);
* socket.listen(myObserver);
* // Asynchronous write by default:
* socket.write("Some message".getBytes());
*
PacketReader#getBuffer()
* PacketReader#getNextPacket()
should return a byte-array if it is possible to create
* one from the data loaded into the buffer(s).
* PacketReader#getNextPacket()
is made.
*
* @return the byte buffer to use.
* @throws ProtocolViolationException if a protocol violation was detected when
* reading preparing the buffer.
*/
ByteBuffer getBuffer() throws ProtocolViolationException;
/**
* Return the next packet constructed from the data read in the buffers.
*