Multi-Socket Server

The Multi-Socket Server or mss package provides an easy way to manage asynchronous input from many different sockets by placing all network events into a queue so that they can be handled one at a time on a single thread.

About

Designing a server properly involves writing a complicated multi-threaded application. These applications are wont to suffer from race conditions and other common synchronization bugs. This package takes the hassle out of that process by managing all those threads behind the scenes and placing all important network events into an EventQueue so that you can handle those events one at a time in a single thread.

The events that fill up automatically in that queue are these:

The package also provides a convenient abstract class called MultiSocketServer that you can extend which requires you to define six methods such as onConnect and onInput that will be called when their corresponding events occur. Since all the multi-threading is handled for you, you are guaranteed that one method will return before the next method is called, which means you don't have to worry about synchronizing the data structures that you use in your server (assuming they aren't used by any other threads).

Example

Here is a simple chat room server that extends MultiSocketServer:

import com.stephengware.java.mss.*;
import java.io.*;
import java.util.HashMap;

public class ChatServer extends MultiSocketServer {

     public static final int DEFAULT_PORT = 5555;
     protected final HashMap<Connection, String> names = new HashMap<>();
     protected final Writer log;
     
     public static void main(String[] args){
          int port = DEFAULT_PORT;
          if(args.length > 0){
               try{
                    port = Integer.parseInt(args[0]);
               }
               catch(NumberFormatException ex){
                    // do nothing
               }
          }
          ChatServer server = new ChatServer(port, new PrintWriter(System.out));
          server.start();
     }
     
     public ChatServer(int port, Writer log){
          super(port);
          this.log = log;
     }
     
     @Override
     protected void onStart(){
          log("Chat server has started.");
     }
     
     @Override
     protected void onConnect(Connection connection){
          log("A new client has connected.");
          connection.write("Welcome. Please enter your name.");
     }
     
     @Override
     protected void onInput(Connection connection, String input){
          // If the person already has a name, this input is a chat message.
          if(names.containsKey(connection)){
               String message = names.get(connection) + ": " + input;
               log(message);
               writeToAll(message);
          }
          // If the person does not yet have a name, this input is their name.
          else{
               log("Identified client \"" + input + "\".");
               writeToAll(input + " has joined.");
               names.put(connection, input);
          }
     }
     
     @Override
     protected void onDisconnect(Connection connection){
          String name = names.get(connection);
          // If they never gave a name, do nothing.
          if(name == null)
               return;
          // If they did give a name, tell the room they have left.
          log("Client \"" + name + "\" has disconnected.");
          names.remove(connection);
          writeToAll(name + " has left.");
     }
     
     @Override
     protected void onException(Exception exception){
          exception.printStackTrace();
     }
     
     @Override
     protected void onStop(){
          log("Chat server has stopped.");
     }
     
     protected void log(String message){
          try{
               log.append(message);
               log.append("\n");
               log.flush();
          }
          catch(IOException ex){
               ex.printStackTrace();
               stop();
          }
     }
     
     protected void writeToAll(String message){
          for(Connection connection : names.keySet())
               connection.write(message);
     }
}

Documentation

More details on how to use this code can be found in the documentation.

Download

The .jar archive can be downloaded here.

The source code is also available as an Eclipse project here.

Bugs

As always, they go to sgware@gmail.com.