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:
- Exactly one SERVER_START_EVENT will be the first event in the queue.
- Each time a new user connects, a ConnectEvent will be added to the queue.
- Each time the server gets input from one of its connections, an InputEvent will be added to the queue.
- Each time a connection is closed or the client disconnects, a DisconnectEvent will be added to the queue.
- Exactly one SERVER_STOP_EVENT will be the last event in the queue.
- If any uncaught or IOExceptions occur, they will be reported via an ExceptionEvent.
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.