-
Notifications
You must be signed in to change notification settings - Fork 0
Server Manual
Strategy
The Server class provides the facility to receive and answer connection attempts from the Internet, which are directed to a host and a port-address. The server owns a socket which only deals with the reception of new connections; it is called the "server-socket". Upon incoming requests to its address, it creates new connections, which have their own sockets, and renders them to the application for communication. Alternatively, the request can be rejected. A multitude of connections to different remote clients can originate from a single server instance. These connections all show the server-address as their local Internet-address. JennyNet keeps track of all active server-connections, which can be retrieved from the Server instance. For a single peer-to-peer communication setup the Server is used in the same manner.
Due to the generic strategy of Server, only two things have to be established: a definition of its Internet service address and a method to deal with new connection attempts. In short, the Server class functions as a Connection broker. In contrast, all data communication with the clients takes place on the connections rendered thereof.
Preparations A Server instance can be created either bound to a local port and Internet address or unbound. However, a Server must be bound before it can be started and become operational.
Creating a Server
// creates an unbound server instance
Server server = new Server();
// creates a server bound to port 42900 on the wildcard Internet address
Server server = new Server(42900);
Server server = new Server();
server.bind(42900);
// creates a server bound to a random free port address on the
// wildcard Internet address
Server server = new Server();
server.bind(0);
// creates a server bound to an Internet socket address
SocketAddress address = new InetSocketAddress("localhost", 42900);
Server server = new Server(address);
Server server = new Server();
server.bind(address);
// choose one of the methods to get notified about new connections
server.setSignalMethod(ServerSignalMethod.ACCEPT);
server.setSignalMethod(ServerSignalMethod.LISTENER);
// optionally add a listener for server events
ServerListener serverListener = new OurServerListener();
server.addListener(serverListener);
// start the server
server.start();
Services of the Server
The Server class provides autonomous event dispatching within a dedicated thread per instance. Events are distributed to listeners implementing the ServerListener interface.
Accepting Connections
The ServerListener interface defines some frugal information events for the application, such as when a connection is added to or removed from the server's registry of alive connections. Its most prominent feature, however, is the signalling of new connections that can be taken away from the interface for further application use (via connectionAvailable(ServerConnection)). ServerConnection is more or less a tagging class for Connection to make it discriminable from [Client](Client Manual). It has an additional function start(), that is to start activity of the connection. This is useful, first to allow some space to set up individual parameters for the connection before it starts execution, second to also allow for rejecting a connection, instead of starting it, by the reject() method. Connections that are signalled by the Server class are layer-verified, that means they have identified as originating from a matching JennyNet software package.
A typical code for dealing with incoming connections then would look like this:
server.addListener( new DefaultServerListener() {
@Override
public void connectionAvailable(ServerConnection connection) {
// we think about whether we can handle this connection
boolean isAcceptable = true;
// then we accept or reject it
try {
if (isAcceptable) {
// we can set some parameter, like e.g. the file-root-path
connection.getParameters().setFileRootDir(rootDirectory);
// and add our listener to connection events
connection.addListener(ourConnectionListener);
connection.start();
} else {
connection.reject();
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
However, there is a second way to getting to the incoming connections, and that is by the server's ServerConnection accept() method. This method works much like a server socket's accept method and returns a new connection when it has arrived and validated, while blocking the calling thread until such an event occurs. This Accept conception follows a different philosophy, compared to the above described event driven Listener method. While in "listening" network events determine time and frequency of application's reactions to incoming connections, in "accepting" them application retains full control of when it comes around to collect more connections. A server can run only in one of the two rendering methods.
A code snippet for dealing connections within a dedicated server thread and with the Accept method could look like this:
try {
// create the server and set ACCEPT method
Server server = new Server(portNumber);
server.setSignalMethod(SignalMethod.Accept);
server.setAcceptQueueCapacity(150);
server.start();
// loop over reception queue of incoming connections (blocking)
while (operating) {
ServerConnection connnection = server.accept(0);
connection.getParameters().setFileRootDir(rootDirectory);
connnection.addListener(ourConnectionListener);
// integrate connection into application
....
connnection.start();
connnection.sendPing();
System.out.println("--- CONNECTION ACCEPTED from : "
+ connnection.getRemoteAddress() );
}
} catch (InterruptedException e) {
} catch (IOException e) {
e.printStackTrace();
System.err.println("Could not listen on port " + portNumber);
System.exit(-1);
}
Keeping the Memory (Connection Register)
After starting a Connection rendered by a Server instance, they function logically independent of the server and from each other. For a server application it is not even required to keep references to open connections, and it could possibly just react to connection events reported at the ConnectionListener interface. Where a more active role is played by the server, it would suffice to keep the UUID identifiers of the connections involved. This is made possible as the Server class of JennyNet keeps a register of open connections, and the application can at any time retrieve connections, single by their UUID value, or collective as an array of registered connections. When a connection closes, no matter what the cause, it is automatically removed from the registry. To give the user control of this membership, connections may be removed from or added to this registry arbitrarily with methods removeConnection(Connection) and addConnection(Connection).
/** Send a priority text to given connection (UUID). */
boolean sendStringImmediate(UUID connection_UUID, String text) {
Connection c = server.getConnection(connection_UUID);
if (c != null) {
c.sendObject(text, true);
return true;
}
return false;
}
Multiplex Anyone ..?
Taking advantage of its registry, the Server class offers methods to send objects or actions to the entire set of registered connections in a single call. The following send actions can be performed on the set of connections: PING, TEMPO, OBJECT, OBJECT-PRIORITY, FILE.