self.app.rpc.chat.chatRemote.add(session, uid, self.app.get('serverId'), rid, true, cb);
类似此类方法为rpc调用
首先调用的是同名的代理方法,代理方法比原方法多一个session参数,作为第一个参数传入
代理方法在/game-server/node_modules/pomelo/node_modules/pomelo-rpc/lib/util/proxy.js 的genFunctionProxy内生成, 所以首先调用的是该文件的57行的方法:
function() {
var args = Array.prototype.slice.call(arguments, 0);
proxyCB.call(null, serviceName, methodName, args, attach, invoke);
}
proxyCB在game-server/node_modules/pomelo/node_modules/pomelo-rpc/lib/rpc-client/client.js中,
在proxyCB中:
var routeParam = args.shift();
var cb = args.pop();
弹出args的第一个和最后一个参数,实际为最开始调用
self.app.rpc.chat.chatRemote.add(session, uid, self.app.get('serverId'), rid, true, cb)
的session和cb
然后把session传入client的route路由方法中,在app.start开始前可以加载自定义的方法,如果有对应服务器类型的自定义路由方法,则执行自定义路由,如果没有加载自定义的路由方法,则执行components/proxy.js中的defaultRoute路由方法,该方法根据session的uid用crc哈希算法计算 再与目标后端服务器数量取模 得出一个后端服务器,最后得到目标服务器的id
_station在game-server/node_modules/pomelo/node_modules/pomelo-rpc/lib/rpc-client/mailstations.js中定义实现
在mailstation中,为每一个serverId都创建一个ws-mailbox用来和后端服务器通信(如果没有设置延迟加载)
由于设置了延迟加载,所以在前端服务器第一次调用mailstation的dispatch方法时才创建目标后端服务器的邮箱,当邮箱还没有创建好之前,所有转发到后端服务器的信息将被缓存到pending中,创建目标邮箱后,先调用邮箱的connect方法,该方法内将建立前端服务器和目标服务器之间的socket连接,建立连接后将所有缓存在pending中的消息发送到后端服务器.
前端服务器消息通过目标后端服务器对应的mailbox的方法dispatch 用socket.send将消息发送
====================================
凡是定义有port端口的进程都会加载pemolo.remote组件,
该组件会通过/game-server/node_modules/pomelo/node_modules/pomelo-rpc/lib/rpc-server/server.js创建gateway对象-->remote
gateway通过acceptor(ws-acceptor.js)监听rpc的请求
acceptor接收到的消息,通过其cb(dispatcher.route)进一步处理
所以当rpc请求最终在dispatcher.route分发到目标服务器的指定方法:
var args = msg.args.slice(0);
args.push(cb);
method.apply(service, args);
上面提到rpc的代理客户端发送消息到服务器时,参数只剩下4个,并储存在msg.args中,这里可以看出,在执行真正的目标方法时,参数又添加了cb,和后端服务器的add方法申明的接口一样了.
==================================
小结:
客户端:
self.app.rpc.chat.chatRemote.add(session, uid, self.app.get('serverId'), rid, true, function(users) {
next(null, {
code: 200,
users: users
});
});
服务端:
ChatRemote.prototype.add = function(uid, sid, name, flag, cb) {
console.log('2');
var channel = this.channelService.getChannel(name, flag);
if( !! channel) {
channel.add(uid, sid);
}
cb(this.get(name, flag));
};
rpc客户端调用服务器的代理会多一个session参数,代理在正式和后端服务器通讯之前,通过session计算出要连接的后端服务器id,然后连接后端服务器,客户端的cb是rpc调用成功后执行的, 服务端的cb是执行目标方法后执行的,它们的接口一样.所以从宏观上看,代理的add方法的cb是又服务端调用的.
服务端的cb是ws-acceptor.js中的
function() {
var args = Array.prototype.slice.call(arguments, 0);
for(var i=0, l=args.length; i<l; i++) {
if(args[i] instanceof Error) {
args[i] = cloneError(args[i]);
}
}
var resp = {id: pkg.id, resp: Array.prototype.slice.call(args, 0)};
if(acceptor.cacheMsg) {
enqueue(socket, acceptor, resp);
} else {
socket.emit('message', resp);
}
可见服务端执行请求后,会通过socket连接把结果回传到客户端的ws-mailbox.js
=======================================
chanelService 保存自定义的频道名,每个频道里保存前端服务器的ID:sid和每个用户的ID:uid在groups,groups[sid]取得所有通过同一个前端服务器登陆的uid
========================================
客户端也可以直接调用后端服务器的handler,但是内部实际上是先连接到前端服务器,在components/server.js中执行
app.sysrpc[routeRecord.serverType].msgRemote.forwardMessage
通过rpc调用后端服务器的forwardMessage方法(/game-server/node_modules/pomelo/lib/common/remote/backend/msgRemote.js),在forwardMessage方法中重新调用后端服务器的server.handle方法以调用客户端的目标方法.