Pomelo Channel

  • channel频道是推送消息的工具是玩家ID的集合

可以将玩家ID加入到某个channel,同样也可以从中移除。

如果通过channel来推送消息,那么channel中的所有成员都将收到这条消息。

可以创建多个channel来自定义消息推送区域,不过这些channel相互之间是独立的。

  • channel可以看作是玩家ID的容器,主要用于需要广播推送消息的场景。

可以把玩家加入到一个channel中,当对这个channel推送消息时,所有加入到这个channel的玩家都会受到推送过来的消息。

一个玩家的ID可能会加入多个channel中,这样玩家就会接收到其加入的channel推送过来的消息。

  • channel只适用于服务器进程是本地的

应用服务器A和B并不会共享channel,也就是说在服务器A上创建的channel只能由服务器A才能给它推送消息,服务器进程A创建的channel和在服务器进程B创建的channel是两个不同的channel,相互不影响。

频道组件channel component

频道组件维护频道信息,可以被除了master之外的服务器加载。

频道组件可看作是channelService频道服务的组件包装,注册该组件后会在app上下文加上channelService,可通过app.get("channelService")获取。

const channelService = app.get('channelService'); 

可以认为一个channel就是一个用户的集合,每个用户大致对应于前端服务器中的一个session会话,用户可以通过频道组件向一个channel里面的所有用户推送消息。

由于后端服务器并不与客户端直接相连,故后端服务器会发起一个sys rpc系统远程调用来表示向客户端推送消息,接受这个远程调用的是pomelo已经实现的ChannelRemote

对频道组件进行配置

app.set("channelConfig", opts);

频道组件的配置项

配置 描述
broadcastFilter 广播的过滤函数

broadcastFilter会在执行channel频道的broadcast广播的时候,在前端服务器上在消息发送给每个session会话之前进行一个过滤,其签名形式为broadcastFilter(session, msg, filterParam),其中filterParam参数由在channelServicebroadcast调用时传入。

channelService.broadcast(type, router, {filterParam:param}, cb);

频道服务ChannelService

  • 频道服务用于服务器本地创建和维护的频道
  • 频道服务由默认加载的频道组件创建
  • 频道服务的组件可由app.get("channelService")访问获取
const channelService = app.get('channelService'); 

内置服务通过set()函数挂载到app全局对象上,相当于服务变成了单例类,从而直接从app.get("channelService")获取之前注册的服务,根据玩家uid和绑定的sid确定下来是哪个服务器,哪个channel

方法 描述
channelService.createChannel(name) 指定名称创建具名频道
channelService.getChannel(name, create) 指定名称获取频道
channelService.destroyChannel(name) 指定名称销毁频道
channelService.broadcast(stype, route, msg, opts, cb) 向已连接的客户端广播消息
channelService.pushMessageByUids(route, msg, uids, opts, cb) 指定用户UID推送消息
$ vim pomelo/lib/common/service/channelService.js

创建频道 channelService.createChannel(name)

/**
 * 创建指定名称的频道
 *
 * @param {String} name 频道名称
 * @memberOf 频道服务
 */
ChannelService.prototype.createChannel = function(name) {
  if(this.channels[name]) {
    return this.channels[name];
  }

  var c = new Channel(name, this);
  addToStore(this, genKey(this), genKey(this, name));
  this.channels[name] = c;
  return c;
};

获取频道 channelService.getChannel(name, create)

/**
 * 通过频道名称获取频道
 *
 * @param {String} name 频道名称
 * @param {Boolean} create 是否创建频道
 * @return {Channel}
 * @memberOf 频道服务
 */
ChannelService.prototype.getChannel = function(name, create) {
  var channel = this.channels[name];
  if(!channel && !!create) {
    channel = this.channels[name] = new Channel(name, this);
    addToStore(this, genKey(this), genKey(this, name));
  }
  return channel;
};

销毁频道channelService.destroyChannel(name)

/**
 * 通过频道名称销毁频道
 *
 * @param {String} name 频道名称
 * @memberOf 频道服务
 */
ChannelService.prototype.destroyChannel = function(name) {
  delete this.channels[name];
  removeFromStore(this, genKey(this), genKey(this, name));
  removeAllFromStore(this, genKey(this, name));
};

广播消息channelService.broadcast(stype, route, msg, opts, cb)

/**
 * 为所有已连接的客户端广播消息
 *
 * @param  {String}   stype      前端服务器类型
 * @param  {String}   route      路由,前端消息监听方法的路由
 * @param  {Object}   msg        发送给前端的消息内容
 * @param  {Object}   opts       用户自定义广播选项
 *                               opts.binded: push to binded sessions or all the sessions
 *                               opts.filterParam: parameters for broadcast filter.
 * @param  {Function} cb         回调函数
 * @memberOf 频道服务
 */
ChannelService.prototype.broadcast = function(stype, route, msg, opts, cb) {};

推送消息channelService.pushMessageByUids(route, msg, uids, opts, cb)

/**
 * 指定用户ID推送消息.
 * Group the uids by group. ignore any uid if sid not specified.
 *
 * @param {String} route 前端消息监听方法的路由
 * @param {Object} msg 发送给前端的消息内容
 * @param {Array} uids 消息接收者,格式 [{uid: userId, sid: frontendServerId}]
 * @param {Object} opts 用户自定义参数 
 * @param {Function} cb 回调方法 cb(err)
 * @memberOf 频道服务
 */
ChannelService.prototype.pushMessageByUids = function(route, msg, uids, opts, cb) {};

例如:

channelService.pushMessageByUids(
  "onPushMessage", 
  {from:1001, target:1002, message:"hello world"},
  [{uid:2000, sid:""}],
  function(err){
    console.error(err);
  }
)

频道channel

pomelo提供channel服务用于在具体服务器中保存用户信息并提供消息发送功能,但channel只能在具体服务器中进行存储,无法提供全局服务器中用户信息存储的功能。

方法 描述
channel.add(uid, sid) 添加用户到频道,将指定用户的UID与用户所连接的connector服务器的ID绑定。
channel.leave(uid, sid) 从频道中移除用户
channel.getUserAmount() 获取频道中成员数量
channel.getMembers() 获取频道中所有成员
channel.getMember(uid) 根据用户uid获取成员信息
channel.destroy() 销毁频道
channel.pushMessage(route, msg, opts, cb) 推送消息给频道中所有的成员

频道类型

  • pomelo中拥有两种频道分别是具名频道和匿名频道,区别在于匿名频道推送消息需定制uids。
  • pomelo服务器和客户端保持长连接,推送可以根据频道推送或根据用户连接的服务器推送。

具名频道

  • 具名频道又称为显式频道
  • 创建一个具名频道时,需要为其指定一个频道名称才会返回一个频道实例。
  • 具名频道并不会自动释放,需要调用channel.destroy()方法来手动进行释放。
  • 具名频道通常用来保持长时间关系的订阅,比如聊天服务。

具名频道需要使用channel.createChannel()方法或chanel.getChannel()方法首先获得一个频道,然后再使用channel.pushMessage()方法推送消息。

  • 创建频道channelService.createChannel(name)

createChannel用户创建一个指定名称的频道

//从应用上下文获取频道服务对象
const channelService = app.get("channelService");
//频道服务创建频道
let channel = channelService.createChannel(name);
参数 描述
name 频道名称
  • 获取频道 channelService.getChannel(name, create)
//从应用上下文获取频道服务对象
const channelService = app.get("channelService");
//从频道服务中获取频道
let channel = channelService.getChannel(name, create);
参数 描述
name 频道名称
create 是否创建

getChannel方法用于获取一定指定名称的频道,若参数create设置为true则表示频道不存在时会自动先创建。

  • 频道添加用户channel.add(uid, sid)
//将用户添加到频道
//uid表示用户唯一标识
//sid表示connector服务器的ID
channel.add(uid, sid)
  • 频道推送 channel.pushMessage(event, msg, cb)
//根据频道推送
//event表示客户端监听的push方法,比如onAdd。
//msg表示推送参数
//cb表示推送后的回调函数
channel.pushMessage(event, msg, cb);

匿名频道

匿名频道是指不用创建Channel直接使用ChannelService进行推送,匿名频道用于频道成员变动频繁或推送临时消息,比如AOI(Area of Interest)消息,当玩家在一个场景中从A点移动到B点时,服务器就需要推送一个AOI(Area of Interest)消息给周围玩家。

匿名频道支持两种推送方式分别是channelService.pushMessageByUids指定用户uid进行推送和channelService.broadcast广播推送。

  1. 指定用户uid推送channelService.pushMessageByUids
channelService.pushMessageByUids(route, msg, uids, opts, cb)
参数 描述
route 前端消息监听方法的路由
msg 发送给前端的消息内容
uids 消息接受则,格式为{uid:uid, sid:frontendServerId},uid为session绑定的uid。
opts 可选的自定义参数
cb 推送完成后的回调函数

匿名频道可通过channelService.pushMessageByUids来推送消息,没有频道名称也没有频道实例返回。

例如:使用匿名频道向指定连接的用户推送消息

/*根据客户端连接推送*/

//定义保存用户唯一编号和connector服务器的对应管理
let uids = [];
let obj = {};
obj.uid = "session uid";
obj.sid = "connector-server-1";
uids.push(obj);

//获取应用上下文中的频道服务
const channelService = app.get("channelService");

//method表示客户端监听的push方法,比如onAdd。
const method = "onMsg";

//param表示推送参数
let param = {};
param.msg = "hello world";

//使用频道服务根据uids进行推送
channelService.pushMessageByIds(method, param, uids, function(err){
  if(err){
    console.error(err);
    return;
  }
});
  1. 广播推送channelService.broadcast

大部分的消息都是通过广播推送到客户端,再由客户端播放接收的消息。channel是服务器向客户端消息广播的通道。

channelService.broadcast(stype, route, msg, opts, cb)
参数 描述
stype 前端服务器类型
route 前端消息监听方法的路由
msg 发送给前端的消息内容
opts 可选的自定义参数
cb 推送后的回调函数

例如:向全频道的所有成员推送消息

//获取应用上下文中的频道服务
const channelService = app.get("channelService");

//服务器类型
const stype = "connector";

//method表示客户端监听的push方法,比如onAdd。
const method = "onMsg";

//推送的消息
let msg = {};
msg.msg = "hello world";

//定义选项参数
let opts = {};
opts.binded = true;

//广播
channelService.broadcast(stype, method, msg, opts, function(err){
  if(err){
    console.error(err);
    return;
  }
});

具名频道和匿名频道本质上都是相同的,即使看起来是不同的。首先频道需要将连接至它们的前端服务器分组,然后需要将消息联通玩家ID通过分组一起推送到各自的前端服务器,最后分发到各自客户端。

消息推送过程

pomelo的通讯类型可以分为四种分别是request、response、notify、push。客户端发起的request请求到达服务器后,服务器处理完毕后会为其返回response响应。notify是客户端发给服务器的通知无需服务器给与回复。push是服务器主动给客户端推送的消息。,其中request、response使用pomelo.request实现,notify由pomelo.notify实现,push则由频道实现。

服务器是如何将消息推送给客户端的呢?

  1. 频道所在的服务器进程将消息推送到玩家客户端所连接的前端服务器进程(依赖底层的RPC框架)
  2. 通过前端服务器进程将消息推送给客户端,推送前会根据用户所在的前端服务器的serverId进行分组。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容

  • 翻译:死月,校对:叶岬,原文连接 Pomelo(柚子)是一个可以让开发者便捷开发的游戏服务端框架。下面是其一些Po...
    機巧死月不會碼代碼阅读 19,151评论 4 16
  • Pomelo中游戏服务器是一个多进程相互协作的环境,各个进程之间通信采用RPC(远程过程调用)的形式完成,通过底层...
    JunChow520阅读 1,918评论 0 1
  • pomelo中组件是可重用的服务单位,一个组件实例提供若干种服务,比如说处理机组件载入后处理机代码后将会把客户端消...
    JunChow520阅读 843评论 0 1
  • Chapter 8 Goroutines and Channels Go enable two styles of...
    SongLiang阅读 1,580评论 0 3
  • 文/一刀斋 有三种法,于诸世间,是“不可爱”,是“不光泽”,是“不可念”,是“不称意”。何者为三,谓“老、病、死”...
    一刀斋阅读 549评论 2 30