NetworkDesign

From Arianne
Jump to: navigation, search



Please note: This page explains the low level network communication. You won't need to bother with these implementation details if you want to use Marauroa to write a game. We document the network design anyway for contributors to Marauroa itself. And it is helpful for people porting Marauroa to other programming languages.

Messages

Marauroa uses messages to communicate between client and server. The messages sent from the client to the server are prefixed with C2S and the ones sent from the server to the client use the prefix S2C.

Each message is implemented in its own Java class in the package marauroa.common.net.message. You can lookup up the details about every message in the javadoc. If you want to port Marauroa to another programming language, you will need know how the message are serialized exactly. The easiest way to learn that is to look at the source code of the readObject() and writeObject() methods.

There are for different client states of the game: connected, logged in, in game, logged out. Depending on the state different messages are valid:

State connected

Messages used to securely login.

The login process is a bit complicated because of security requirements. Don't be scared, just follow it step by step.

Onces the TCP connection is established the clients requests the RSA public key from the server using C2SLoginRequestKey. The server checks the protocol version implicitly included in every message. If it is compatible, it replies with a S2CLoginSendKey including the RSA public key. It is composed of two bytes arrays: The first one contains the value of 'n', and the second the value of 'e'.

The client now computes a nonce (a random number) and sends its hash as byte array to the server in a C2SLoginPromise message. The server remembers the client nonce and answers with its own nonce in a S2CLoginSendNonce.

Almost there: The client has now all the information it needs to actually send the C2SLoginSendNonceNameAndPassword: Its nonce, the username and the value rsaCrypt(xor(xor(client nonce, server nonce), password)). The first field is a bytes array containing the client nonce, the second one a string containing the username and the third one a byte array containing the encrypted password. On reception, the server checks that the hash he received at first is the hash of the nonce he just received. It then decodes the password field, and having the value of the client nonce and its nonce, it gets the value of the password.

The S2CLoginNACK message is sent from the server to the client to tell the client that its login request was rejected because the username or password is wrong, the account was banned or the server is full. The included result object will tell which of the cases prevented the login.

If the username/password combination, however, is correct then the Server must send a S2CLoginACK message to tell the client that the message has been correctly processed. It contains information about the last login, so that the user is able to recognize unauthorized usage of his account. The client state is changed to "logged in" in this case.

State logged in

Selecting a character and transmitting world meta data.

The logging in stuff was complicated. But luckily things are getting much easier now. After the login completed successfully the server sends a S2CServerInfo message. It tells the client about what kind of server is running, and details on how to contact the server administrator (e.g. their email address). The message is composed of a List of strings of the form "attribute=value". In addition this message also contains the list of defined RPClasses.

Directly afterwards a S2CCharacterList message is sent from the server to the client. It offers a choice of character to play with. This feature models the behavior of having several characters associated with a single account. Each character name must be unique at server level, and it is assigned when the character is created.

Now the client picks one of the offered characters. Games that do not support multiple characters can pick the one that matches the account name. The choice is transmitted to the server using a C2SChooseCharacter message. The name of the character must be one of the names listed in the character list.

The server will check the selected character and reply for a S2CChooseCharacterNACK if the choice was invalid. This implies that the client should send another C2SChooseCharacter message.

If the selection, however, was successful, a ChooseCharacterACK message is sent and the client state changed to "in game".

State in game

Messages sent while the game is active.

Regular Messages

The S2CPerception message is a message sent from the server to the client to notify the client about changes to the objects that exist near it. The message is based on the idea explained in the Delta Perception document.

The message is composed of:

  • A type that can be DELTA or TOTAL
  • A string indicating the name of the zone the perception is related to.
  • A time stamp value that will be just one unit bigger than the previous perception
  • A List of RPObject that contains added object
  • A List of RPObject that contains modified added object attributes
  • A List of RPObject that contains modified deleted object attributes
  • A List of RPObject that contains deleted object
  • A RPObject that describes the things the rest of players don't see about OUR own object.

Read the Delta perception algorithm to understand what it is for.


The client sends C2SKeepAlive messages regularly. If there has been no keep alive message for some time, the server will timeout the client.


Static Content Transfer

Perceptions are about dynamic content. But most games have some static data, too. Like maps, tilesets, sounds. The RPManager keeps track of situation in which the client might need some of this static data. A common case is the movement of the client from one zone to another.

If the RPManager detects such a situation, it will offer the new content to the client using a S2CTransferREQ message. The message is composed of an array of TransferContent objects containing the name of each resource, its timestamp (or checksum) and if the resource is cacheable or not.

The clients replies with a C2STransferACK acknowledging the offer. The message is composed of an array of TransferContent objects containing all the name of each resource and a flag indicating ack or not. Note: The C2STransferACK message is always sent, even if all flags indicate that no transfer should take place.

If the clients has acknowledged the need for content to be transfer, the server sends a S2CTransfer. The message contains a again an an array of TransferContent objects. This time, however, the actual data is included as well.

Actions

Actions are commands sent from the client to the server using a C2SAction message. Example for commands are "move to the right", "look at that object", "attack that rat". It is up to the server to decide whether to execute the action or reject it.

Logging Out

The client sends a logout request and the server accepts or denies it.

If the player is in some kind of combat it is often desirable to prevent him from saving his live by logging out. Therefore the client sends a logout request to the server and the game server can decide whether to accept or reject it. Of course the user can close the client window or force a disconnect of his Internet connection. But in these cases he will simple stay in a game unattended until the timeout anyway.

The clients indicates that it wants to finish the session by sending a C2SLogout.

The server can reply with a S2CLogoutNACK to reject the logout request. Or it confirms the request with a S2CLogoutACK. In this case the client state is changed to logged out.

Transmitting Messages over TCP

The idea behind Arianne's network protocol is to use a single TCP stream between the server and the clients. Different kinds of in-game actions create different types of messages that are then interpreted at the opposite side in to meaningful data. TCP takes care of sorting the packets and retransmitting lost ones.

Each message has a general header:

  • Size of message (4 bytes)
  • Protocol version (1 byte)
  • Type of message (1 byte)
  • Client ID (4 bytes)
  • Timestamp (4 bytes)

This header is followed by message specific data. Have a look at the source code of the methods readObject() and writeObject() of the message in question.

Network Manager

The Network Manager is our router that sends and receives messages to and from the network. The manager exposes the interfaces that allow:

  • Reading a message from the network
  • Sending a message to the network
  • Finalizing the manager

The read operation is a blocking type operation so we have two options, either polling (i.e. continually checking if data is there) or blocking (i.e. only processing when data is actually available, otherwise sleeping).

We choose blocking because we don't want to waste CPU time polling the network for messages, we just want to sleep until messages are available. Hence we create a Thread to read from the Network, let's call it NetworkManagerRead.

Writing messages to the network can be simply coded as a method of Network Manager, as write is an operation that is non blocking by nature.

The NetworkManager opens a Socket from which it will receive all the messages from the network. It will also write all the outbound messages to the network from this same socket. Note: Both write and read use the same Socket.

To encapsulate all this we create both the Read and Write methods as inner classes of Network Manager.

NetworkManager
  {
  socket
  messages
  pendingToSendMessages

  NetworkManagerRead isa Thread
    {
    read socket
    build message
    store in messages
    }

  NetworkManagerWrite isa Thread
    {
    get from pendingToSendMessages
    serialize message
    send socket
    }
  }

As you can see, messages are stored in a list when they are received. Hence access to the list must be synchronized.

Now lets get back to the interface as exposed to other objects. The write method is immediate, just call it with the message to send, making sure that you have correctly filled SourceAddress and ClientID. The message will then be sent to the Client.

The read method is blocking, when you call the read method it either returns a message from the queue or if the queue is empty the thread blocks (sleeps) until one arrives.

That is the basic idea of the Network Manager. Note that the manager just sends the stream of packets once and doesn't confirm if any of the messages are received. TCP takes care of that.