NBServer - Getting started

main | news | status | download | faq | gettingStarted | javadoc

Getting prerequisites and building the NBServer library.

So you want to implement a server for a specific network protocol built on top of NBServer. You'll need some prerequisite software:

Next, you should tailor the build.xml - edit the paths you see at the top of the file in the "compile.classpath" section to suit the locations of JDK 1.4 runtime JAR and the JUnit JAR on your machine.

Last, open a command prompt in the NBServer directory, and type  

ant jar

to create the NBServer library JAR file. If you want to run the NBServer unit tests, type

ant test

If the tests run fine, you have a correctly working NBServer library on your computer.

Basic concepts

The central concept of the NBServer is a service. A service accepts connections on the specified network port of the machine it runs on, and services the incoming client connections. In NBServer library, services run inside a service container, which is an instance of the NioServer class. (A single service container can run many services, but we'll get to that later). Every service is defined by a socket configuration and a protocol handler factory. A socket configuration is a specification of the socket on which the service will run: the port number, optionally an IP address - if your server has several IP addresses, timeout value, backlog size, etc. Most values have defaults, so you usually only have to specify the port. The protocol handler factory is the first piece of code that you will have to write, and this is still fairly trivial. It's just a factory -  its responsibility is to create a new protocol handler instance for each incoming connection. Finally the protocol handler is the second piece of code you will have to write, and the protocol handler class will actually implement the network protocol.

A minimalist example of setting up a server.

Suppose that you have written a hypothetic HTTP protocol handler. The factory class for that handler is named HttpProtocolHandlerFactory. In order to get your server up and running, you use the following four lines of Java code:

NioServer server = new NioServer();
ServerSocketConfiguration httpSocket = new ServerSocketConfiguration();
httpSocket.setSocketAddress(new InetSocketAddress(InetAddress.getLocalHost(), 80));
Service httpService = server.registerService(new HttpProtocolHandlerFactory(), httpSocket);

And that's it! Your HTTP service is up and running on the port 80 of the default IP address of your local machine. The Service object returned from the registerService() method has only a single meaningful method, stop() which you use to stop the service.

As it was mentioned earlier, a single NioServer instance can host multiple services. You can perfectly legally have a FTP and a HTTP service running in the same NioServer:

NioServer server = new NioServer();

ServerSocketConfiguration httpSocket = new ServerSocketConfiguration();
httpSocket.setSocketAddress(new InetSocketAddress(InetAddress.getLocalHost(), 80));
Service httpService = server.registerService(new HttpProtocolHandlerFactory(), httpSocket);

ServerSocketConfiguration ftpSocket = new ServerSocketConfiguration();
ftpSocket.setSocketAddress(new InetSocketAddress(InetAddress.getLocalHost(), 21));
Service ftpService = server.registerService(new FtpProtocolHandlerFactory(), ftpSocket);

What is important to know is that all services that run in the same NioServer instance share threads. That is, the very same threads will be used to accept, read, and write all connections of all services registered with a single NioServer instance - even for completely different protocols! Normally this is not a problem, and actually helps you better utilize threads.

Writing the protocol handler factory

A protocol handler factory is used to create protocol handlers. One protocol handler is created  for servicing each incoming connection. A typical protocol handler would look like this (for our hypothetical HTTP protocol handler):

public class HttpProtocolHandlerFactory
implements
    ProtocolHandlerFactory
{
    public ProtocolHandler createProtocolHandler(SocketChannel channel)
    {
        return new HttpProtocolHandler(socket);
    }
}

This really can't get any simpler. Depending on your needs, you can eventually write a factory that uses pooled protocol handler instances instead of creating a new one on each invocation; in basic case the default behavior of creating a new instance will do.

Writing the protocol handler

Last, you have to write the protocol handler. Now this is where you will really have to work. The ProtocolHandler interface consists of two methods:

public interface ProtocolHandler
{
    public boolean tryToRead()
    throws
        IOException;

    public boolean tryToWrite()
    throws
        IOException;
}

tryToRead() is called whenever the connection associated with the handler is potentially readable. Note that this means it can happen that it is in fact not readable when the method is called (that is, you'll be able to read only 0 bytes). In a similar fashion, tryToWrite() is called when the connection is potentially writable - it can still happen that you can write only 0 bytes to it. Also, both methods are called when the connection is closed, or when an error occurs. You can implement the ProtocolHandler interface directly, or you can write a subclass of AbstractPipedProtocolHandler. This class gives you the convenience of output piping - you can write all output to an output stream end of a pipe provided by the class, and if your protocol handler produces output faster than your network client can consume it, it will buffer it and pipe it to the socket at its own pace in tryToWrite() calls. Be sure to check the output buffering on FAQ pages for a more extensive information on the way NBServer does the buffering (in short: it will not cost you much RAM).

This entry is incomplete, and I'll add a sample protocol handler implementation to this page later. Until then, you can study org.szegedi.nioserver.protocol.http.HttpProtocolHandler for an example of a fairly complete HTTP protocol handler.

Advanced server configuration

When constructing the NioServer instance, you can use the default constructor as in the examples above, or you can use the constructor that takes two parameters:

public NioServer(int acceptThreads, int transferThreads)

With this constructor, you can determine the initial number of threads the server will use. You can specify separately the number of threads for accepting connections, and the number of threads for data transfer (that is, servicing read and write operations). With the default constructor, both values are 1. If you have a multiprocessor machine, adjust the number of threads to at least the number of CPUs. I.e. on a dual processor machine, you would create a server with "new NioServer(2, 2)". Note that the server can, internally and on its own discretion, create and destroy additional threads on demand. It is guaranteed, however that it will always have at least as many threads as you have specified in the constructor. These threads are called "permanent" whereas the additional threads are called "temporary".

Another configurable option is whether the transfer threads are half-duplex or full-duplex. In the half-duplex scenario (the default). Every transfer thread services both read and write requests for a channel that is registered with it. If a channel is ready for both reading and writing, the thread will first service read operations, then the write operations. In the full-duplex scenario which can be enabled globally by setting the org.szegedi.nio.fullDuplex system property to true. separate threads are used for servicing read and write requests, therefore both read and write operations for every channel can take place concurrently. We have experienced several problems when using the full-duplex mode (i.e. read notifications were being lost when the channel was ready for both reading and writing), so we decided that half-duplex is the default. Since most protocols follow the request-response paradigm, this is no limitation at all. Also note that half-duplex applies only to a single connection; if you have multiple read-write threads, then the connections on separate threads are still serviced concurrently. The term half-duplex applies exclusively to serialization of read and write operations in a context of single connection.


(c) Attila Szegedi, 2002. All rights reserved.

 This project is hosted on SourceForge.SourceForge Logo