NotificationsServer - Mini-IT/SnipeWiki GitHub Wiki
This page describes various mechanisms for asynchronous server and client notification calls. Since the slave server notification handling side is the same no matter how the notification was sent, it is in the separate section.
To send notification from one of the slave servers to cache server, use the CacheConnection.notify()
call. The notification message will be added to a queue and sent to the 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.cacheConnection.notify({
_type: 'test.testEvent',
amount: 10
});
Cache server code ("test" module):
function testEvent(c: TestSlaveClient, msg: { amount: Int })
{
// event handling
}
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.response()
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
}
The notification handler is described below in Notification handling section.
If you need to send a notification from the cache server to all slave servers of a single type, use the CacheServer.broadcast()
method.
Usage example:
server.broadcast('game', {
_type: 'test.testEvent',
amount: 10
});
The notification handler is described below in Notification handling section.
If you need to send a notification from the slave server to all slave servers of a single type, use the CacheConnection.broadcast()
method.
Usage example:
server.cacheConnection.broadcast('chat', {
_type: 'test.testEvent',
amount: 10
});
The notification handler is described below in Notification handling section.
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 the Server.notify()
method. The first argument it accepts is the 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.
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 the 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 (returns errorCode other than "ok"), run failure handler on server 1 and finish
- If the validation succeeds (errorCode = "ok"), 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 the 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 the 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 response 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.response('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.response('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.response('user.giveMoney', { errorCode: 'ok' });
}
If you need to send a notification from a slave server to a client but do not want to make a full response out of it, there is a mechanism available for that in ClientInfo.notify()
method. When calling it with the message parameters object as an argument, the message will be added to a queue. The next time this server sends a response to a client request, the notifications in the queue will be added to it in serverNotify
parameter. If the server does not send any responses to the client for 3 seconds, it will make a response with server.notify
type automatically.
The last notification mechanism in this article concerns the situation when one of slave servers needs to send a notification to a client that is logged in on one of game servers. This can be done with the Server.sendTo()
call.