Jenjin-io

IO Utilities for Client/Server Connections

Download as .zip Download as .tar.gz View on GitHub

Jenjin-IO

IO Utilities for Client/Server Connections

GitHub license Build Status Coverage Status

Usage

Jenjin-IO provides a library for creating simple Socket-based connections, utilizing a customizable serialization scheme for messages.

ExecutionContext

This interface should be implemented by any application using the Jenjin-IO; it can contain Connection-specific data that persists across incoming and outgoing Messages. Whenever a Connection executes a Message, the ExecutionContext belonging to that Connection is passed as a parameter to the Message#execute method.

Important: In the Message#execute method and in contextual callbacks passed to a Connection, modification of an ExecutionContext is thread safe; synchronization, locking, etc… are not required.
However, if the ExecutionContext is accessed from threads other than these, care should be taken to make sure that any access to it is made safe.

ExecutionContextFactory

This interface should be implemented by any Server-side application using the Jenjin-IO API; it has one method (createInstance) which should return a new, distinct, and mutable ExecutionContext. The Server class must be passed an implementation of this interface (typically through a ServerBuilder) so that when new connections are created, they can be passed their own ExecutionContext instance without affecting those of existing or further new connections.

Message

The Message interface is core to the Jenjin-IO API; it determines what data is received by a Connection and what is done when data is received. It contains a single (default) method called execute which takes a single parameter (the ExecutionContext belonging to the Connection that received the message) and returns an optional Message that is queued up to be sent by the Connection.

Connection

The Connection class is the “glue” that ties the other components of the Jenjin-IO API together. Its main responsibility is to spawn and maintain the threads responsible for reading, executing, and writing Messages.

When a Connection is started (by calling the start method), it automatically begins retrieving any messages sent to it. When it receives a message, the following process takes place:

  1. The raw data of the message is deserialized into a Message object
    • The method of deserialization depends on the MessageReader owned by the Connection; Jenjin-IO provides an implementation using Gson for convenience.
  2. The deserialized Message is placed into the “incoming” queue of the Connection.

  3. During the next message execution cycle, the Connection invokes the execute method of all messages in the “inbound” queue in the order in which they were received, passing in its ExecutionContext as a parameter and storing any non-null return values in the “outgoing” message queue.

  4. During the next message broadcast cycle, any Messages in the “outgoing” queue are serialized into raw data and sent.

Connections can either be built manually (for client-side or peer-to-peer connections) using the SingleConnectionBuilder class, or they can be built automatically by a Server using a MultiConnectionBuilder.

SingleConnectionBuilder

This class is used to build a single Connection; there are a few different configurations that can be done when building a Connection that are of interest:

Important: These methods must both be invoked with non-null values (either explicitly, or by calling withSocket or withInputStream and withOutputStream) before the build method is called, or an IllegalStateException will be thrown.

Note: These methods will be called internally by withSocket, withInputStream

Once you’ve configured your connection, you can build it with the build method:

Important: If the MessageReader, MessageWriter, or ExecutionContext are not set, the build method will throw an IllegalStateException. The MessageReader is set automatically if the withInputStream or getSocket methods are used; similarly, the MessageWriter is set automatically if the withOutputStream or withSocket methods are used.

Connection connection = builder.build();

Note: The SingleConnectionBuilder class is fluent; it can be used like so:

private Connection getConn(Socket sock, ExecutionContext context) {
    return new SingleConnectionBuilder()
        .withMessageIOFactory(new GsonMessageIOFactory())
        .withSocket(sock)
        .withExecutionContext(context)
        .withErrorCallback((connection, throwable) -> connection.stop())
        .build();
}

MultiConnectionBuilder

The MultiConnectionBuilder class is very simliar to the SingleConnectionBuilder class, with a few key differences:

  1. The build method accepts a Socket instead of having no parameters
    • Each time build is called, a new Connection will be created from the given Socket.
  2. There is a new withExecutionContextFactory method, that accepts an ExecutionContextFactory that will be used to generate a new ExecutionContext for each Connection built with this builder.
  3. The withMessageReader, withMessageWriter, withInputStream, withOutputStream and withExecutionContext methods are not present

Important: The callbacks (Consumers, BiConsumers) passed into a MultiConnectionBuilder should be immutable, as each callback will be passed into every connection rather than being copied.

Server

The Server class is a convenience class provided by Jenjin-IO that is capable of accepting multiple client Connections. It is not terribly robust, so it may be prudent to examine the source and create your own implementation that better suits your needs.

A Server requires the following objects (which are supplied from a ServerBuilder:

This class has two broadcast methods, one which takes a single Message parameter, which is broadcast to all existing Connections, and a second which takes both a Message parameter and a Predicate<Connection> parameter, which broadcasts to all Connections which fulfill the Predicate.

ServerBuilder

Much like the SingleConnectionBuilder and MultiConnectionBuilder classes, the ServerBuilder class is responsible for configuring and building a Server.

The ServerBuilder class has several methods that help with configuring a Server:

Important: If the build method is called without the ServerSocket being set, an IllegalStateException will be thrown.

Important: If the build method is called without the MultiConectionBuilder being set, an IllegalStateException will be thrown.

Note: The ServerBuilder class is fluent; it can be used like so:

private Server getServer(ServerSocket sock, MultiConnectionBuilder mcb) {
    return new ServerBuilder()
        .withServerSocket(sock)
        .withMultiConnectionBuilder(mcb)
        .withConnectionAddedCallback(this::doSomething)
        .build();
}

Other important classes

These interfaces need not be explicitly implemented by your application; Jenjin-IO provides a few convenience classes (relying on the Gson library) that implement working versions of them in the com.jenjinstudios.io.serialization package

However, these classes are not optimized for performance or bandwidth, and it may be prudent to implement your own versions that better cater to the particular needs of your application.

MessageIOFactory

This interface exposes methods to create a MessageReader and MessageWriter from an InputStream and OutputStream respectively.

MessageReader

This interface exposes a read method which returns a Message and a close method that should close backing streams and perform any necessary cleanup.

MessageWriter

This interface exposes a wrute method which accepts a Message and a close method that should close backing streams and perform any necessary cleanup.


Building

Note: To build Jenjin-IO from source, you must have Java 8 installed and your JAVA_HOME environment variable pointing at the Java 8 installation.

Jenjin-IO is built using gradle; to build the library and run all tests, simply run this command in the Jenjin-IO directory:

./gradlew build

License

Jenjin-IO is licensed under the MIT license