I’ve been working on a system involving connecting custom game servers to a larger, message based and multi-tenant infrastructure. The custom game server I was writing requires a persistent connection to the larger system, since messages originate from all directions (from/to client, from other servers, from the infrastructure, from thin air…). And while the code I’m working with is all in Java, it’s expected that other game servers will be written in C#, or python, or ruby, or whatever they feel like, within reason.
Rather than build a custom protocol on top of TCP to go between the custom game server and the rest of the system, we decided to use Web Sockets for the wire protocol and objects serialized via JSON. There are plenty of client and server implementations for all of the major languages we’re intending to support – making the job of supporting the other dev teams much easier. Plus, it’s trivial to create various test harness Web Socket powered clients and servers – useful for developing your custom game without having to be tied to the larger system’s environment. Particularly since that larger system was still actively under development..
As I was writing the game server, I found many examples of writing a Java server which would listen for incoming clients. I found plenty of examples for writing Web Socket clients in JavaScript, or clients in Java that did everything in “public static void main()”. But I didn’t find much in the way of writing a Java Server that kept a persistent Web Socket client around for the life of the server. So, attached is an example Web Socket “client” server (a server application) which can send and receive messages using netty.
https://github.com/trasa/WebSocketClientServer
This particular example connects to a public “echo” server — which is great for testing out such ideas.
Netty
Netty is a client server framework for building network applications. Netty’s power comes from being able to construct a high performance Channel Pipeline which describes the steps that network traffic goes through on its way to and from the application. For this example, we define a pipeline that is built on HTTP and includes a Serializer/Deserializer for translating objects into json (and back), and also the WebSocketClientHandler itself for doing something with those WebSocket messages.
When sending a Message, we send the Message through the pipeline we’ve established. The Serializer/Deserializer (SerDe) translates the message into a byte array, wraps the bytes into a TextWebSocketFrame, and sends the WebSocketFrame on it’s way.
Coming back the other way, the SerDe recognizes an incoming stream of bytes as a Message and reconstitutes the java object. The Message object is sent up the pipeline, handled by WebSocketClientHandler, which then executes that message, finding the correct handler to call, and otherwise “doing stuff” with the message received.
The advantage of this pipeline approach is that the actual game logic is easy to separate from the underlying communication, making the game code easier to write and much easier to test in isolation. For example, a message handler might be defined as:
@MessageHandler
public void handleConstructArmyRequest(ConstructArmyRequest request) {
// do whatever you're supposed to do here
sendResponse(new ConstructArmyResponse(SUCCESS));
}
Netty met the needs of this particular game server, performance-wise and through it’s flexibility. Since we’re using web sockets, such a system would be easy to put together using a library or different language completely.
The next step for this system is to look at replacing Netty with Jetty – which provides a servlet container and web server with web sockets support. Typically a custom game server will need some http-based parts for monitoring (connecting to nagios, viewing and graphing statistics) and for allowing some administrative access to the inner workings of the game server. Jetty doesn’t provide the same sort of pipeline for serializing and deserializing but the process is pretty much the same.