Skip to content

Jetserver internal details and how it works.

menacher edited this page Dec 26, 2012 · 24 revisions

[work in progress!]

Jet Server - architecture and internal details

This section outlines how data flows through jet server, the important classes and interfaces and also which ones need to be extended by users to write their game logic, receive events and so on. If you want to develop your own game using jetserver then this would be a good starting point to understand the internals.

Login from remote client

Assume that a remote client is attempting to connect to jet server. In this case the data can be considered as flowing towards the server. Netty is used for socket communication. In the default scenario it will pass through the following handlers/decoders in the netty pipeline.

  1. LengthBasedFrameDecoder - This Netty class will check for a 2 byte header in the incoming binary packet, these 2 bytes are the length of the "body" or "payload" of the binary packet. It will wait for the whole payload to become available, strip off the length bytes and send the actual payload bytes to the next handler in the chain.
  2. EventDecoder - The body of the incoming binary message from client will of the format opcode-data. This decoder will read the opcode, create an appropriate event object and set the data part as the "source" of this event instance.
  3. LoginHandler - This handler is responsible for validating a login attempt from a remote client. If successful, it will create create a session for the connecting client, add it to a Game Room and send notification back to client that login was successful. Each game room has a game specific network protocol (attack, move, position etc are commands which then get sent using this protocol between client and server) associated with it, LoginHandler will also apply this protocol to the connection.

How do I configure a different login handler?
Jetserver uses spring to configure all its beans, so swapping out one implementation for another is quite simple. The Server Beans contains a bean named tcpServer with the following configuration.

<bean id="tcpServer" class="org.menacheri.jetserver.server.netty.NettyTCPServer"   
	init-method="createServerBootstrap" destroy-method="stopServer">
	<property> name="pipelineFactory" ref="loginPipelineFactory" </property>
	<property> name="gameAdminService" ref="gameAdminService" </property>
	<property> name="portNumber" value="${tcp.port}" </property>
</bean>

The LoginPipelineFactory bean contains all the handlers mentioned above. This is the bean that can be replaced with your own custom PipelineFactory if you want to modify the bean.
At this point the client has an active session on the server and is connected to a Game and can start sending data to server.

More on Game Protocols

Each game can have a different network protocol even though it may be much easier to use the one provided by default. If you want your own protocol then the interface to implement is Protocol. It may also be beneficial to add this protocol as a spring bean. Example protocol configurations can be found at protocol-beans file. If configured as a spring bean, then injecting this protocol into Game Rooms is very easy using spring.

So how does a protocol get applied?
Each GameRoom can have its own protocol (One Game can have any number of associated GameRooms. Each GameRoom can have its own specific protocol. Since the Game can have multiple game rooms, it opens up the opportunity for allowing varied types of network data to be sent for the same game using different GameRooms). When a successful login attempt is made from a remote client, the game room to which the session gets connected will call the applyProtocol method on its protocol implementation and pass the incoming session to it. It is the responsibility of the protocol implementation to configure the low level decoders and encoders to handle network data. Shown below is such an implementation.

  @Override
	public void applyProtocol(PlayerSession playerSession)
	{
		LOG.trace("Going to apply protocol on session: {}" ,playerSession);

		ChannelPipeline pipeline = NettyUtils
				.getPipeLineOfConnection(playerSession);
		// Upstream handlers or encoders (i.e towards server) are added to
		// pipeline now.
		pipeline.addLast("lengthDecoder", createLengthBasedFrameDecoder());
		pipeline.addLast("messageBufferEventDecoder",messageBufferEventDecoder);
		pipeline.addLast("eventHandler", new DefaultToServerHandler(
				playerSession));

		// Downstream handlers - Filter for data which flows from server to
		// client. Note that the last handler added is actually the first
		// handler for outgoing data.
		pipeline.addLast("lengthFieldPrepender", lengthFieldPrepender);
		pipeline.addLast("messageBufferEventEncoder",messageBufferEventEncoder);

	}

Data Flow from remote client to session on jet server

This section details how a remote client can send data/event to jet server such that it is received by the player session and game logic can then be applied on the incoming event.
Assumption here is that the default game network protocol is used. The following are the network handlers involved.

From Client to Server

  1. LengthFieldBasedFrameDecoder Same functionality as mentioned in above section.
  2. MessageBufferEventDecoder This decoder will read the opcode (first byte) from the incoming binary data, create an appropriate event, say SESSION_MESSAGE and then set the rest of the binary data as the Event's payload. This event is then passed on to the next handler in the chain.
  3. DefaultToServerHandler This handler has the sole responsibility of sending the incoming event instance to the PlayerSession. To send the event the handler invokes the onEvent method on the session and passes the event instance to it as parameter.

Session To SessionEventHandler
Events that come to the session are handled by SessionEventHandlers. The default being DefaultSessionEventHandler which can handle any kind of event. Sessions have an associated EventDispatcher which is used to dispatch incoming events to session event handlers.

So how do I apply game logic to an incoming event?
There are multiple ways in which this can be done.

  1. To extend the DefaultSessionEventHandler
  2. Override the onDataIn method and any other that you require special logic for. Take a look at this handler.
  3. Add this handler to the session using the addHandler method.
    The benefit of using this generic handler is that all default events get handled by this automatically and you need to only worry about overriding onDataIn.

Second possibility is

  1. Create your session event handler class for handling a specific event type say SESSION_MESSAGE type. This can be done by implementing the SessionEventHandler
  2. Now add this event handler to the session using addHandler method on the Session.

Events

Some events, mostly for life cycle are provided by default in jetserver Events class. You can define your own as long as you provide the appropriate session event handler to handle it. This way you are not stuck to one implementation model. Events should be of type byte. Please note that if you use event types other than byte then you need to write your own EventDecoder (only about 3-4 lines!). Pasting code of existing event handler below. This handler should then be configured as part of your network protocol.

   
@Override
protected Object decode(ChannelHandlerContext ctx, Channel channel,
		Object msg) throws Exception
{
	ChannelBuffer buffer = (ChannelBuffer)msg;
	int opCode = buffer.readUnsignedByte();// Read opcode byte.
	return Events.event(buffer, opCode);
}

Some important events are provided below with explanation.

  1. LOG_IN - Sent by client to server to do login.
  2. LOG_IN_SUCCESS - Sent by server to client to signify that login attempt was successful.
  3. SESSION_MESSAGE - Incoming data from another machine/JVM to this JVM (server or client).
  4. NETWORK_MESSAGE - This event is used to send data from the current machine to remote machine/vm using TCP or UDP transport.
  5. START, STOP - Sent by server to client to ask the client to start sending messages or stop sending messages to server.

Session

Player Session
When a remote client successfully logs in to the jetserver, a Default Player Session is created for it on the server. Any data that is sent from the remote client to server or from server to remote client is sent via this session. The most important method on the session is the onEvent method.
Session uses two MessageSender child interfaces to send network data to client, Fast for UDP transport and Reliable for TCP.
Incoming network data from remote client to jetserver is converted to a SESSION_MESSAGE event and sent to the session using the onEvent method. The session has an associated EventDispatcher which will then route this event to the onDataIn method of the DefaultSessionEventHandler and any other handlers listening for SESSION_MESSAGE events of this Session.
Session Creation
Sessions are created using a SessionBuilder which is a public static inner class of the Session. User's can of course use their own implementations of Session interface where necessary, in this scenario a builder may not be necessary depending on your implementation. PlayerSession's are created by GameRoomSession's when a remote client connects to it.

Game Room Session
Consider game rooms to be a grouping mechanism for players. Each game room can have any number of player sessions connected to them, however if it is a fast moving action game then having too large number of sessions could increase load considerably, since each movement made by one player would need to be broadcast to that many other player sessions. For a game like Tic-Tac-Toe, each game room would only have 2 players. For a shooter game, it could be much more. Example of a Game Room. Note that onLogin of the session is overriden for game specific purpose. If Game Room is loaded from database then developer should make sure to set dependencies like protocol, parent Game etc on it. Here is an example of dependencies set on a Game Room using spring config (zombieroom bean).
So what is a GameRoomSession?
A GameRoomSession is a GameRoom which is also implementing Session interface. By modeling a Game Room(which is a grouping of sessions) also as a session, message passing between player session and Game Room session becomes easy. The player sessions listen on the Game Room session for any outgoing network data using event handlers. The player sessions have a reference to their Game Room using a parentGameRoom variable and can send data to it using send(Event event) method on the GameRoom interface.
The code below shows creation of a GameRoomSession using Spring Config. The same can be done from normal java code also.

public @Bean GameRoom zombieRoom1()
{
	GameRoomSessionBuilder sessionBuilder = new GameRoomSessionBuilder();
	sessionBuilder.parentGame(zombieGame()).gameRoomName("Zombie_ROOM_1").protocol(messageBufferProtocol);
	ZombieRoom room = new ZombieRoom(sessionBuilder);
	room.setDefender(defender());
	room.setZombie(zombie());
	return room;
}

Game, Services

This section provides information on some of the key classes and services provided by default in jetserver.

  1. Game - An interface to signify a type of a game that is deployed using jetserver. You can code for any number of games in jetserver at the same time. Games can be loaded from database or configured as beans in the spring container, or created using normal java new operator. Example of a Game class created using spring config can be seen here. Game creation is a single line process -> Game game = new Game(1,"Zombie");
  2. Lookup Service - When a player connects to jetserver, it is necessary to connect the session created for the player to a game room. Lookup service is used to lookup the game room based on the login inputs provided by the player and connect to that particular room. Overriding this service is mandatory. An example service configured using spring config can be seen in the file. Note that configuring Lookup service in spring is Mandatory for jetserver.
  3. Task Manager Service - Every application generally has tasks that need to be run, either once or repeatedly. Use this task manager for such purposes. Usage can be seen in the example-games project.
    TaskManagerService taskManager = ctx.getBean(TaskManagerService.class);
    taskManager.scheduleWithFixedDelay(monitor, 500, 5000, TimeUnit.MILLISECONDS);