Skip to content

NotificationsServer

Max S edited this page Aug 31, 2015 · 4 revisions

Server Notifications

Overview

This page describes various mechanisms for asynchronous server and client notification calls. Since slave server notification handling side is the same no matter how the notification was sent, it is in the separate section.

Slave server -> cache server notification

To send notification from one of the slave servers to cache server, use the Server.cacheNotify() call. The notification message will be added to a queue and sent to cache server with the next request. The cache server will handle that request and all received notifications in a loop after that. Using the _type parameter of each notification the cache server will call the module methods directly.

Usage example (slave server code):

  server.cacheNotify({
    _type: 'test.testEvent',
    amount: 10
    });

Cache server code ("test" module):

  function testEvent(c: TestSlaveClient, msg: { amount: Int })
    {
      // event handling
    }

Cache server -> slave server notification

To send notification from cache server to one of the slave servers, use the SlaveClient.notify() call. While the message will be sent immediately, its actual handling on the slave server end will be different from the usual SlaveClient.responce() call. The notification will be passed to a special notification handler that can call a module method directly.

Usage example (cache server):

  var gameServer = server.getRandomClient('game');
  gameServer.notify({ 
    _type: 'test.testEvent',
    value: 10 
    });

Slave server "test" module contents:

  function testEvent(msg: { value: Int })
    {
      // notification handling
    }

Notification handler is described below in Notification handling section.

Cache server -> all slave servers of given type

If you need to send notifications from the cache server to all slave servers of a single type, use the CacheServer.notifyAll() method.

Usage example:

  server.notifyAll('game', {
    _type: 'test.testEvent',
    amount: 10 
    });

Notification handler is described below in Notification handling section.

Slave server -> game server with specific client

In most multiplayer games you will need to implement some interaction between two clients. There is a big chance they will be logged in on different game servers. Real-time interaction is usually handled through additional service servers that both clients need to log in to. But something like, for example, sending money to another user or sending a challenge for a duel requires a different mechanism. You can implement interaction of that type using Server.notify() method. The first argument it accepts is client ID and the second is message type and parameters. The client ID will be used to route the notification to appropriate game server.

Note that if the receiving client is offline (this is determined on the cache server), the notification will be sent to first game server in list.

The additional notification parameters are especially useful in the given examples and will be described in Notification handling section.

Game server notification handling

When you send notifications that end up on a game server you can pass some additional parameters that will trigger useful core logic. This section explains them and shows the examples.

One thing you can do is set the client ID that will be used to find the client instance and pass it to notification handler.

Usage example (slave server 1):

  server.notify(id, { 
    _type: 'test.testEvent',
    _id: id,
    amount: 10
    });

This example sends notification to the game server that has client with ID = id logged in and also passes this client instance to notification handler.

Notification handler (game server 2):

  function testEvent(client: Client, msg: { value: Int })
    {
      // client instance is fully initialized and ready to be used
    }

The other thing you can do is use request validation API. It gives you the ability to separate checks from notification handling and automatically send success or failure notification back to the calling server.

The validation behavior for request from server 1 to server 2:

  • Run validation method on server 2
  • If the validation method fails, run failure handler on server 1 and finish
  • If the validation succeeds, run the notification handler on server 2
  • Run success handler on server 1

This logic will work properly in case when the second client is currently online on the same game server (if the call was made from a game server). No calls to cache server will be made and the execution will be faster.

The type that contains the validation parameters is called NotifyValidateParams. Note that both the success and failure handlers can be null. In that case validation results will not go anywhere.

There are two more things to note here. The failure handler will receive a single object as an argument that contains both the properties of a notification object and the properties of an object returned by the validation method merged. The success handler object argument on other hand will contain the properties of a notification object and the properties of an object returned by the notification handler.

Another thing that is important to keep in mind is that when you implement logic like that, you should always consider that some time will pass between the initial notification call on one server and notification handling on another. During this time the first client can make more requests that will use the same resource. Using giving money from one user to another as an example, you have to remove the amount given before you make the actual notification call and give it back in case of failure. If you remove the money in success handler, your game server will be prone to DDOS type attacks - the malicious client will send multiple requests fast that will give the money to a second user out of nowhere. On the other hand, quest triggers should be called in success handler since there is no way to roll back user quests state.

Usage example (full implementation of giving money from one user to another):

  // client request handler
  // give money to another user (1)
  public function giveMoney(c: Client, msg: Params): Dynamic
    {
      var id2 = msg.getInt('id');

      // check for the existance of this user
      if (!server.coreUserModule.userExists(id2))
        return { errorCode: 'userNotFound' };

      var amount = 500;
      if (c.money < amount)
        return { errorCode: 'notEnoughMoney' };

      // more user 1 checks go here

      // remove money immediately
      c.money -= amount;

      // send notification
      server.notify(id2, { 
        _type: 'user.giveMoney2',
        _id: id2,
        _validate: {
          func: 'user.giveMoneyValidate', 
          onSuccess: 'user.giveMoney3',
          onFail: 'user.giveMoneyFail',
          id: c.id },
        from: c.id, 
        fromNetworkID: c.user.networkid,
        amount: amount });

      // do not give any responce to user 1 yet
      return null;
    }


  // give money to another user (validation)
  function giveMoneyValidate(c2: Client, msg: 
    { from: Int, fromNetworkID: String, amount: Int }): Dynamic
    {
      // checks for user 2 go here

      // money receive limit
      if (c2.moneyReceived >= 6)
        return { errorCode: 'moneyReceivedMax' };

      return { errorCode: 'ok' };
    }


  // give money to another user (2)
  function giveMoney2(c2: Client, msg: 
    { from: Int, fromNetworkID: String, amount: Int })
    {
      c2.money += msg.amount;
      c2.moneyReceived++;
      c2.responce('user.receiveMoney',
        { amount: msg.amount, networkid: msg.fromNetworkID  });

      return { moneyReceived: c2.moneyReceived };
    }


  // give money to another user (failure)
  function giveMoneyFail(c: Client, msg:
    { from: Int, fromNetworkID: String, amount: Int, errorCode: String })
    {
      c.money += msg.amount;
      c.responce('user.giveMoney', { errorCode: msg.errorCode });
    }


  // give money to another user (3)
  function giveMoney3(c: Client, msg: 
    { from: Int, fromNetworkID: String, amount: Int, moneyReceived: Int })
    {
      c.responce('user.giveMoney', { errorCode: 'ok' });
    }

Slave server -> client

If you need to send a notification from a slave server to a client but do not want to make a full responce out of it, there is a mechanism available for that in ClientInfo.notify() method. When calling it with message parameters object as an argument, the message will be added to a queue. The next time this server sends a responce to a client request, the notifications in the queue will be added to it in serverNotify parameter. If the server does not send any responces to the client for 3 seconds, it will make a responce with server.notify type automatically.

Slave server -> client on a different game server

The last notification mechanism in this article concerns the situation when one of slave servers needs to send notification to a client that is logged in on one of game servers. This can be done with Server.sendTo() call.