Skip to content
This repository has been archived by the owner on Sep 25, 2023. It is now read-only.

Pomelo Framework Reference

changchang edited this page Jan 10, 2013 · 8 revisions

Pomelo is a game server framework that help game developers to develp their game easily and quickly. The following is the main design ideas of pomelo framework.

#Overview

In this section, we will show how Pomelo servers look like.

There are all kinds of task in game server usually, such as managing client connections, managing status of the game world and execuate the game logic. Each task may require different resources, for example: IO or CPU. And it is hard to do all the jobs in one process obviously. So it is a trend to break the whole game server into some small and simple server processes each of which just focus on a single service such as connection service, scene service or chat service. All these service processes cooperate with each other to form the whole large game world.

To develop a game server based on the model above from scratch, it would spend a lot of time and hard work to handle the miscellaneous jobs such as planning the server side resources, establishing and managing the network connections, sending and receiving the message between processes and so on. What is worse that all these jobs would be repeated when starting a new game. There should be a rescuer to cover up all these noicy and repeated jobs, such as BigWorld (well-known and powerful but also expensive and complicated). Now Pomelo provides another choice (open source, easy and quick) to achieve this goal.

Pomelo framework includes following functional modules:

![server arch](http://pomelo.netease.com/resource/documentImage/pomelo-arch.png) Architecture of Pomelo
  • Server manager module is reponsible to define the server types, create and monitor all the service processes.
  • Network module is the basic of communication between processes and provides RPC and channels to hide all the low level details.
  • Application module stands for a process context which takes care the configuration and lifecycle management of the associated service process.

#Server Type Pomelo provides a flexible server type system based on which could customize all kinds server templates for game server cluster. Pomelo classifies server type into two categories: frontend server and backend server.

![server type](http://pomelo.netease.com/resource/documentImage/server-type.png) Server types in Pomelo

In one word, frontend server is responsible to communicate with clients and forward requests to the backend servers whie backend server is the place to implement the game logic.

In native, frontend and backend server are server containers. Base on the server container, developers could customize their own server types as they wish. Developer has the total rights to decide the server should provide which kind of service. For example, you would get a chat server if you deploy the chat service codes in the backend server. So the developers on Pomelo just need to figure out how many frontend server and how many backend server for the game server and deploy their codes on the server nodes. And Pomelo would start and take care all the server processes.

![container](http://pomelo.netease.com/resource/documentImage/container.png) Server containers

#Mechanism for client request

In this section, we will talk about how the servers process the client request.

##Request and response

In Pomelo, the messages from client fall into two categories of request and notify. The differences between them is shown as below:

![request and response](http://pomelo.netease.com/resource/documentImage/request-and-response.png) Request and notify

Request is a bidirectional message which means server would send back a response message when it receives a request message from client and Pomelo would fired the callback associated with the request. Client lauches a request is as below:

pomleo.request('connector.helloHandler.ask', {msg: 'What is your name?'}, function(resp) {
  // We can get the name from response
});

While notify is a unidirectional message which server would not give a response. Client lauches a notify is as below:

pomelo.notify('connector.helloHandler.sayHi', {msg: 'Hi'});

##How to process the client message

Pomelo divides the process flow into to parts: handler and filters. The handler is responsible to provide the game logics while the filters should take care the pre and after jobs, such as logging and timeout handling. The division of work shown as below:

![request flow](http://pomelo.netease.com/resource/documentImage/request-flow.png) Process the client request

###Before filter

The message from client would pass through the before filter chain which do the pre processing, such as checking the login status of current player and logging. The before filter interface is as below:

  filter.before = function(msg, session, next)

Msg is the message object from client; Session is the session object for current player; Next is the callback function to fire the following flow.

Before filter should invoke the next function then the message would go into the next before filter in the chain and come to the handler finally when it go through the before filter chain. If passing an error to the first parameter of the next function, it means some error has happened and have to stop the processing flow, for example current player has not logined yet. Then the message would be pass to a global error handler (introduced latter).

###Handler

Handler is the place to implement game logics. Handler interface is as below:

  handler.methodName = function(msg, session, next)

The parameters are similar with before filter. To process a request message, handler should pass the response object which is a simple json object as the second parameter to the next callback function. And just leave the second parameter null for notify message.

If there is some error happened during handler processing, just pass an error object to the first parameter for the next function as before filter.

###Error Handler

Error handler is the optional place to process global error, such as unkown error processing and report error. Error handler is set as below:

app.set('errorHandler', handleFunc);

And the error handler interface is declare as below:

  errorHandler = function(err, msg, resp, session, next)

Err is the error object pass by before filter or handler; Resp is the response message pass by handler which would be send to client. And the rest parameters is the same as above.

###After filter

All the flow above would come into after filter finally. After filter is responsible to do the after process, such as releasing the request context resouces or recording the usetime of the request. But it should not modify the response message for the message has been send to client before it entering the after filter chain.

The after filter interface declared as below:

  filter.after = function(err, msg, resp, session, next)

All parameters are the same with above.

###Session

Session is a object with key/value pairs to keep the player status, eg: current player id. There are two kinds of session in Pomelo: global session and local session.

Global session is generated and located in the frontend server which the client connects with directly. It is the global place to storage the player informations. It would clone a copy and forward to the backend server attached with the client message. Then the backend server would get a session copy which known as local session.

Local session should be a read-only object and is a local read/write object at lease. The modification on local session would not influence the global one. Anyone who want to sync the local session with global session has to call the push methods of local session. More details reference the API DOC LocalSessionService.

#Channel and broadcast

In this section, we will figure out how the servers push messages to client.

##Channel

There are many messages to push in game server, for example: when a player move from A to B in a scene, the server has to push AOI messages to the other players around. And channel is a utility to push message.

Channel is a container of player id. You can add player id into channel or remove player id from channel. If you push a message by channel, all the members in the channel would receive the message. You can create any number of channel as you wish to customize the zone of message pushing and they are independent each other.

##Category of channel

There are two kinds of channel in Pomelo: named channel and anonymous channel.

Named channel should be specified a name when it was created and it would return a channel instance. And named channel would not be release automatically and should call the channel.destroy method to release it explicitly. Named channel is used to keep a long time relationship of subscription, such as chat service.

Anonymous channel is used by channelService.pushMessageByUids, no name and no instance returned. Anonymous is suitable for the situation that the members of channel are changed frequently or temporary message, such as AOI message.

More details of the usage of channel please reference the API DOC.

Both of the two channel are the same in native, though they are much different in appearance. First, channel would group by the frontend server they connect with. And then channel would push the message with player ids to each frontend server by group and the later push the message to each client finally.

![channel](http://pomelo.netease.com/resource/documentImage/channel.png) Broadcast by channel

#RPC framework

In this section, we will sovle the problems of how the servers communicate with each other.

###Usage of RPC

The communication between processes is important and complicated since the processes would cooperate with each other. And the Pomelo RPC framework is a helpful utility to link the processes together.

Followings are some points that the Pomelo RPC framework should consider.

Routing rules. The routing rules decide which process should the message be forwarded to. The rules would be different with the message type. But it may also be affected by the status of current player or something else. Fox example, a simple move request from client, it should be forwarded to the process that manages scene one when current player is in the scene one. And if the player teleports to scene two, then all the move requests after that should be forward to the scene two process. Further more, the rules would be different in each game. So there should be a open mechanism to customize the routing rules for the developers.

Protocols. The requirement for the communication protocol between processes may be different in different games as well. For example, some server would like TCP while others would prefer UDP.

Pomelo RPC framework introduces abstract layers to simplify and resolve the problems above.

##RPC client

The RPC client layers achitecture as below:

![rpc client](http://pomelo.netease.com/resource/documentImage/rpc-client.png) Architecture of RPC client

The RPC message in figure is a typed message that constains a description of the RPC request, including the RPC request type, arguments and so on. The session is a collection of status of the player who lauches the RPC request.

  • Mail box layer - The mail box layer solves the problem of communication protocol. One mail box stands for a remote server and a mail box uses the remote server id as its own id so that it easy to find out the associated mail box instance by the remote server id. All the details of the communication between current server and the remote server are covered by the mail box instance, such as how to establish the connection and what protocol should be used and how to close the connection. It could implement different mail boxes to support different protocol and it is easy to switch the communication protocol since it just need to choose proper kind of mail box in mail box layer.
  • Mail station layer - The mail station layer maintains all the mail box instances for current process. It would forward the RPC message from upper layer to the proper mail box instance by mail box id. Mail station receives a mail box factory function which decides which kind of mail box should be used for a remote server and return the associated mail box instance. It would ask the mail box factory for mail box instance on the first time of current server try to connect to a remote server. So developers could customize the communication mechanism by the mail box factory function.
  • Route layer - The route layer is used to provide the routing rules. It recieves a route function and use it to caculate the destination process id with the RPC message and session pass by upper layer. And then the id would pass to the mail station mentioned above.
  • Proxy layer - The proxy layer provides the local proxy instances which make the remote method call just like invoking a local method and hides all the details of RPC. The only different of the local proxy method from remote method is that it adds a session parameter which including the status of current player in the first parameter slot of the method. Following is a simple example.

Remote service:

  remote.echo = function(msg, cb) {
    // …
  };

Local proxy:

  proxy.echo = function(session, msg, cb) {
    // …
  };

There is another approach to invoke the remote call by rpcInvoke function if the destination server id is available directly.

##RPC server

The layers of RPC server as below:

![rpc server](http://pomelo.netease.com/resource/documentImage/rpc-server.png) Architecture of RPC server
  • Acceptor layer - The acceptor layer exports the remote services by network. It would listen the port, receive and parse the RPC message by the specified protocol. It should be noted that the acceptor should cooperate with the mail box of remote peer, that means they should use the same protocol to make sure they can communicate with each other without problem. Acceptor is also customized by the acceptor factory function. And acceptor would pass the RPC message to the upper layers.
  • Dispatch layer - The dispatch layer parses the RPC message, exports the message type and RPC arguments and then dispatches the RPC request to the destination remote service.
  • Remote service layer - The remote service layer implements the service logics which is provided by the game developers and loaded by Pomelo framework automatically.

#Extension of server

In this section, we will discuss how to extend the ability of a server process.

As mentioned above, we create many kinds of server. And each of them has its own abilities. For example, the frontend server has the ability of receiving messages from client while the backend server has the ability of receiving messages forwarded by frontend servers. And then how should we maintain and reuse thess abilities? Further more, how should we extend the abilities of a process in a more flexible and elegant way?

Combination would be an appropriate approach. Pomelo introduces the component system to achieve the goals above.

##Component

###What is component In Pomelo, a component is a resusable service unit. A component instance provides some kind of service. For example, the handler component loads the handler codes and pass the client message to the requested handler.

An component instance could be registered into a process context(known as app) and the latter would obtain the ability provided by the component instance. Component instances can cooperate with each other by app. For example, a connector component receives a client request and pass it to app and a handler component may fetch it from app later.

The component system model is described as below:

![component-system](http://pomelo.netease.com/resource/documentImage/component-system.png) Component system

In code, component is a simple class that implements some necessary lifecycle interfaces and app would fire the lifecycle callbacks for each component instance during each phase.

![components](http://pomelo.netease.com/resource/documentImage/components.png) Lifecycles of component
  • start(cb) - Server start lifecycle callback which would be invoke during the process starting stage. NOTICE: Each component has to invoke the cb function to continue the next steps. The component also could pass a error argument to cb to denote that current component fails to start which would let app to terminate the process.
  • afterStart(cb) - Server after start lifecycle callback which would be invoke when all the registed components in current process have started. It gives the components a chance to do some cooperating initialization.
  • stop(force, cb) - Server stop lifecycle callback which would be invoke when the server process is going to stop. Components can do some clear job, such as flush data to database in this lifecycle. Force argument is true means all the components should stop immediately.

###Abstraction levels of Pomelo

Based on the component system, app fact is the backbone of the process. It loads all the registed components and drives them throughout the lifecycles. But app would not be involved into the details of each components. All the jobs to customize a server process is just picking out the necessary components and composing them into the app. So the app is clear and flexible and the components is highly reusable. Further more, the component system summarizes all the serve types into a uniform process finally.

![components](http://pomelo.netease.com/resource/documentImage/abstract-level.png) Abstraction level of Pomelo

###How to register a component

Register a commponent to app as below:

app.load([name], comp, [opts])
  • name - optional component name. Named component instance can be accessed by app.components.name after loaded.
  • comp - component instance or component factory function. If comp is a function, app would take it as factory function and ask it for a component instance. Factory function takes two arguments app and opts(see below) and return a new component instance.
  • opts - optional argument that would be pass as the second argument of component factory function.

#Summary We break the whole game server into small services to clean up the architecture and improve the scalability of game server. And then we summarize all the service types into two kinds of server containers frontend and backend server to simplify the model. And we discuss how the message flows from client to server, from server to client and between the servers. At last, we introduce the component system to combine all the pieces above together to form a unified process. Overall Pomelo provides a scalable and flexible framework to support the game server develop and hide all the noisy and complicated jobs from game developers.

Enjoy the game develop and Pomelo!

More information please refer to API DOC, quick start and architecture overview

Clone this wiki locally