请选择 进入手机版 | 继续访问电脑版

默认
打赏 发表评论 2
想开发IM:买成品怕坑?租第3方怕贵?找开源自已撸?尽量别走弯路了... 找站长给点建议
开源框架Pomelo实践:搭建Web端高性能分布式IM聊天服务器
微信扫一扫关注!

一、关于Pomelo


1.1 Pomelo简介


Pomelo是来自网易公司的基于 Node.js 的高性能、分布式游戏服务器框架。它包括基础的开发框架和相关的扩展组件(库和工具包),可以帮助你省去游戏开发枯燥中的重复劳动和底层逻辑的开发。Pomelo 不但适用于游戏服务器开发, 也可用于开发高实时 Web 应用(如IM聊天应用、消息推送服务等),它的分布式架构可以使 Pomelo 比普通的实时 Web 框架(如Socket.io、sockjs等)扩展性更好。

2012112816134667.jpg

1.2 Pomelo 特性


  • 快速、易上手的游戏开发模型和api;
  • 高可伸缩的多进程架构, 支持MMO的场景分区和其它各类分区策略;
  • 方便的服务器扩展机制,可快速扩展服务器类型和数量;
  • 方便的请求、响应、广播、服务器通讯机制, 无需任何配置;
  • 注重性能,在性能、可伸缩性上做了大量的测试、优化;
  • 提供了较多扩展组件,包括游戏开发常用的库和工具包;
  • 提供了完整的MMO demo代码(客户端html5),可以作为很好的开发参考;
  • 基于socket.io开发,支持socket.io支持的多种语言客户端。

1.3 为什么使用pomelo?


高并发、高实时的游戏服务器的开发是很复杂的工作。跟 web 应用一样, 一个好的开源容器或开发框架可以大大减少游戏开发的复杂性,让开发变得更加容易。遗憾的是目前在游戏服务器开发领域一直没有太好的开源解决方案。 pomelo 将填补这个空白, 打造一款完全开源的高性能(并发)游戏服务器框架。

pomelo 的优势有以下几点:

  • 架构的可伸缩性好。 采用多进程单线程的运行架构,扩展服务器非常方便, node.js 的网络 io 优势提供了高可伸缩性。
  • 使用非常容易, 开发模型与web应用的开发类似,基于 convention over configuration 的理念, 几乎零配置, api 的设计也很精简, 很容易上手。
  • 框架的松耦合和可扩展性好, 遵循 node.js 微模块的原则, framework 本身只有很少的代码,所有 component、库、工具都可以用 npm module 的形式扩展进来。任何第三方都可以根据自己的需要开发自定义 module。
  • 提供完整的开源 MMO 游戏 demo 参考,一个超过1万行代码的游戏 demo,使开发者可以随时借鉴 demo 的设计与开发思路。

1.4 Pomelo官方资料



二、为什么是聊天?


Pomelo是一款游戏服务器框架, 可以实现IM聊天?

Pomelo是游戏服务器框架,本质上也是高实时、可扩展、多进程的应用框架。除了在library部分有一部分游戏专用的库,其余部分框架完全可用于开发高实时web应用。而且与现在有的node.js高实时应用框架如derby、socketstream、meteor等比起来有更好的可伸缩性。

对于大多数开发者而言,node.js的入门应用都是一个基于socket.io开发的普通聊天室, 由于它是基于单进程的node.js开发的, 在可扩展性上打了一定折扣。例如要扩展到类似irc那样的多频道聊天室, 频道数量的增多必然会导致单进程的node.js支撑不住。

而基于pomelo框架开发的聊天应用天生就是多进程的,可以非常容易地扩展服务器类型和数量。

三、从单进程到多进程,从socket.io到pomelo


一个基于socket.io的原生聊天室应用架构, 以 uberchat 为例。

它的应用架构如下图所示:
1.png

服务端由单个node.js进程组成的chat server来接收websocket请求。

它有以下缺点:

  • 可扩展性差:只支持单进程的node.js, 无法根据room/channel分区, 也无法将广播的压力与处理逻辑的压力分开。
  • 代码量多:基于socket.io做了简单封装,服务端就写了约430行代码。

用pomelo来写这个框架可完全克服以上缺点。

我们要搭建的pomelo聊天室具有如下的运行架构:
2.png

在这个架构里, 前端服务器也就是connector专门负责承载连接, 后端的聊天服务器则是处理具体逻辑的地方。

这样扩展的运行架构具有如下优势:

  • 负载分离:这种架构将承载连接的逻辑与后端的业务处理逻辑完全分离,这样做是非常必要的, 尤其是广播密集型应用(例如游戏和聊天)。密集的广播与网络通讯会占掉大量的资源,经过分离后业务逻辑的处理能力就不再受广播的影响。
  • 切换简便:因为有了前、后端两层的架构,用户可以任意切换频道或房间都不需要重连前端的websocket。
  • 扩展性好:用户数的扩展可以通过增加connector进程的数量来支撑。频道的扩展可以通过哈希分区等算法负载均衡到多台聊天服务器上。理论上这个架构可以实现频道和用户的无限扩展。

下文我们将开始用pomelo搭建这个架构,我们将发现搭建如此复杂的架构竟然不到100行代码。

四、初始化代码结构


在开始之前,首先确保在你的机器上已经安装了pomelo,具体安装步骤可参考:pomelo快速使用指南

通过以下命令可初始化应用的代码结构:

pomelo init chatofpomelo



代码结构图如下:
3.png

代码说明:

  • game-server: 游戏服务器,所有的游戏服务器逻辑都放在这个目录下,以文件app.js作为入口,运行游戏的所有逻辑和功能。
  • web-server:游戏用到的web服务器(包括登录逻辑)、客户端的js、css和静态资源等。
  • config: 游戏服务器的所有配置信息。配置信息以JSON格式定义,包含有日志、master服务器和其他服务器的配置信息。该目录还可以放置其它配置信息,包括对数据库配置信息、地图信息和数值表等信息进行定义。
  • logs: 存放游戏服务器所有日志信息。
  • shared: 存放game-server与web-server共用代码。

初始化&&测试:

  • 安装npm包:sh npm-install.sh
  • 启动游戏服务器:cd game-server && pomelo start
  • 启动web服务器:cd web-server && node app.js

web服务器中包含pomelo客户端,当web服务器启动后,客户端自动加载到浏览器中。客户端通过websocket向游戏服务器发送请求,连接成功后服务端通过pomelo向客户端推送消息。

测试是否运行成功:
在浏览器中输入 http://localhost:3001,如果web服务器运行成功,将出现如下页面:
4.png
点击'Test game server'按钮如果显示'game server is ok',pomelo弹出则证明客户端与服务端的通信成功。

五、开始聊天逻辑代码编写


5.1 聊天室的逻辑包括以下几个部分


  • 用户进入聊天室:这部分逻辑负责把用户信息注册到session,并让用户加入聊天室的channel。
  • 用户发起聊天: 这部分包括了用户从客户端发起请求,服务端接收请求等功能。
  • 广播用户的聊天: 所有在同一个聊天室的客户端收到请求并显示聊天内容。
  • 用户退出: 这部分需要做一些清理工作,包括session和channel的清理。

5.2 用户进入聊天室


实现的功能效果如下图: 用户输入用户名、聊天室名,加入聊天室:
5.png

1客户端


客户端需要发请求给服务端, 第一次请求要给connector进程,因为首次进入时需要把session信息注册上去(此tutorial中页面相关代码、布局等略去)。
pomelo.request('connector.entryHandler.enter', {username: username}, function(){
}); 

以上请求字符串’connector.entryHandler.enter’分别代表了服务器类型、服务端相应的文件名及对应的方法名。

2服务端


connector接收消息无需任何配置,只要在connector/handler/ 下新建文件entryHandler.js。我们只需实现enter方法,服务端自动会执行对应的handler,以下是handler的代码:
handler.enter = function(msg, session, next) {
        session.bind(uid);
        session.on('closed', onUserLeave.bind(null, this.app));
};

3服务端将用户加到channel


通过调用rpc方法,将登录的用户加入到channel中。
app.rpc.chat.chatRemote.add(session, uid, app.get('serverId'), function(data){});

其中app是pomelo的应用对象,app.rpc表明了是前后台服务器的Remote rpc调用,后面的参数分别代表服务器的名称、对应的文件名称及方法名。为了实现这个rpc调用,则只需要在对应的chat/remote/中新建文件chatRemote.js,并实现add方法。
remote.add = function(uid, sid, cb){
    var channel = channelService.getChannel('pomelo', true); 
    if(!!channel)
        channel.add(uid, sid);
};

在add方法中,首先从pomelo提供的channelService中取出channel,然后将用户加入到channel中即可。这样就完成了一个完整的rpc调用,在pomelo里复杂的rpc调用就是这么简单。

4用户发起聊天并由服务器接收


客户端代码:
pomelo.request('chat.chatHandler.send', {content: msg, from: username, target: target}, function(){});

服务端代码:
handler.send = function(msg, session, next) {
   var param = {route: 'onChat', msg: msg.content, from: msg.from, target: msg.target};
   // send messages
};

5服务器端广播消息,并由客户端接收


服务端,在上述的send方法中加入广播代码:
var channel = channelService.getChannel('pomelo', false);
channel.pushMessage(param);

客户端,所有频道的内的用户收到消息并显示:
pomelo.on('onChat', function() {
   addMessage(data.from, data.target, data.msg);
   $("#chatHistory").show();
};

5.3 退出房间


session断开时,通过rpc调用将用户从channel中移除。
app.rpc.chat.chatRemote.kick(session, session.uid, app.get('serverId'), 'pomelo', null);

跟用户进入房间一样,在chatRemote中加入对应的kick方法就能够实现用户退出房间的功能。
handler.kick = function(uid, sid, name){
    var channel = channelService.getChannel(name, false);
    if (!!channel) {
        channel.leave(uid,sid);
    }
};

六、跑起来


具体配置文件如下:
{
  "development":{
       "connector":[
            { "id":"connector-server-1", "host":"127.0.0.1", "clientPort":3050, "frontend": true }
        ],
       "chat":[
            { "id":"chat-server-1", "host":"127.0.0.1", "port":6050 }
        ]
   },
  "production":{
       "connector":[
            { "id":"connector-server-1", "host":"127.0.0.1", "clientPort":3050, "frontend": true }
        ],
       "chat":[
            { "id":"chat-server-1", "host":"127.0.0.1", "port":6050 }
        ]
   }
}

服务器的数量开发者可以根据用户的数量来确定,如果需要增加某个类型的服务器只需要在配置文件中相应的位置增加一行配置指定id、host及端口号即可。

运行:
pomelo start

七、扩展更多的服务器


这里我们可以看到在pomelo中扩展服务器的数量是如此的easy。

现在的运行架构如下图:
6.png

扩展成更多服务器,只要修改配置文件servers.json:
{
  "development":{
       "gate":[
            { "id":"gate-server-1", "host":"127.0.0.1", "clientPort":3014, "frontend": true }
        ],
       "connector":[
            { "id":"connector-server-1", "host":"127.0.0.1", "clientPort":3050, "frontend": true },
            { "id":"connector-server-2", "host":"127.0.0.1", "clientPort":3051, "frontend": true },
            { "id":"connector-server-3", "host":"127.0.0.1", "clientPort":3052, "frontend": true }
        ],
       "chat":[
            { "id":"chat-server-1", "host":"127.0.0.1", "port":6050 }, 
            { "id":"chat-server-2", "host":"127.0.0.1", "port":6051 },
            { "id":"chat-server-3", "host":"127.0.0.1", "port":6052 }
        ]
   },
 "production":{
       "gate":[
            { "id":"gate-server-1", "host":"127.0.0.1", "clientPort":3014, "frontend": true }
        ],
       "connector":[
            { "id":"connector-server-1", "host":"127.0.0.1", "clientPort":3050, "frontend": true },
            { "id":"connector-server-2", "host":"127.0.0.1", "clientPort":3051, "frontend": true },
            { "id":"connector-server-3", "host":"127.0.0.1", "clientPort":3052, "frontend": true }
        ],
       "chat":[
            { "id":"chat-server-1", "host":"127.0.0.1", "port":6050 }, 
            { "id":"chat-server-2", "host":"127.0.0.1", "port":6051 },
            { "id":"chat-server-3", "host":"127.0.0.1", "port":6052 }
        ]
   }
}

这样我们就轻松地由之前的单台connector、chat服务器扩展成多台connector、chat服务器的架构。扩展后前端存在多个connector服务器,为了平衡不同connector服务器的负载,我们增加了一个gate服务器。gate服务器主要负责为用户分配connector服务器,在这里我们直接根据用户名进行hash处理从而选择用户登录的connector服务器。

扩展后的运行架构如下图:
7.png

当扩展成多台服务器后,需要为不同类型的服务器添加不同的路由配置。这里主要是chat服务器的路由配置,为了减少应用复杂度,我们根据房间名进行hash处理,详细的配置说明可以参考app.js配置说明。

具体代码如下:
//routeUtil.js
exp.chat = function(session, msg, app, cb) {
    var chatServers = app.getServersByType('chat'); 
    if (!chatServers) {
             cb(new Error('can not find chat servers.'));
                return;
    }
    var res = dispatcher.dispatch(session.get('rid'), chatServers);
    cb(null, res.id);
};
//app.js
app.configure('production|development', function() {
       app.route('chat', routeUtil.chat);
});

最新的chatofpomelo是基于pomelo的0.3版本,0.3版Pomelo开始支持二进制协议,并支持对请求route的字典压缩和请求内容进行protobuf压缩。0.3版同时兼容以前版本基于socket.io的通讯协议。通过在应用中配置不同的connector component来实现协议的切换或共存。针对pomelo 0.3版本的两种不同的通信方式,chatofpomelo也分别有兼容之前的socket.io版本(chatofpomelo)和websocket版本(chatofpomelo-websocket)。socket.io版本的配置兼容之前版本,只是在servers.json中对connector的配置稍有修改,上文已经给出最新的servers.json的配置说明。

对于socket.io版本需要在app.js中对connector进行配置,具体配置如下:
// app configuration
app.configure('production|development', 'connector', function(){
        app.set('connectorConfig',
                {
                        connector : pomelo.connectors.hybridconnector,
                        heartbeat : 3,
                        useDict : true,
                        useProtobuf : true
                });
});

app.configure('production|development', 'gate', function(){
        app.set('connectorConfig',
                {
                        connector : pomelo.connectors.hybridconnector,
                });

});

上述配置是分别对connector和gate服务器进行websocket的配置,heartbeat是客户端与服务端的心跳时间,单位是秒;useDict是配置是否打开handler字典压缩;useProtobuf是配置是否开启protobuf的压缩;具体可以参考pomelo 数据压缩协议

八、本文中的源码获取方式


当前chatofpomelo有两个版本,一种是底层基于socket.io的版本,git地址如下:
git clone https://github.com/NetEase/chatofpomelo.git

另一种是底层基于websocket的版本,git地址如下:
git clone https://github.com/NetEase/chatofpomelo-websocket.git

基于Flash客户端的Demo地址如下:
https://github.com/mani95lisa/PomeloFlashDemo

九、更多WEB端即时通讯技术文章


新手入门贴:史上最全Web端即时通讯技术原理详解
Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE
SSE技术详解:一种全新的HTML5服务器推送事件技术
Comet技术详解:基于HTTP长连接的Web端实时通信技术
新手快速入门:WebSocket简明教程
WebSocket详解(一):初步认识WebSocket技术
WebSocket详解(二):技术原理、代码演示和应用案例
WebSocket详解(三):深入WebSocket通信协议细节
socket.io实现消息推送的一点实践及思路
LinkedIn的Web端即时通讯实践:实现单机几十万条长连接
Web端即时通讯技术的发展与WebSocket、Socket.io的技术实践
Web端即时通讯安全:跨站点WebSocket劫持漏洞详解(含示例代码)
开源框架Pomelo实践:搭建Web端高性能分布式IM聊天服务器
>> 更多同类文章 ……

即时通讯网 - 即时通讯开发者社区! 来源: - 即时通讯开发者社区!

本帖已收录至以下技术专辑

推荐方案
评论 2
网易的Pomelo挺有诚意。
签名: 周末了,可以浪了
框架太多,没法选了
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

返回顶部