1. 安装Pomelo并新建一个项目

$ npm install -g pomelo
$ mkdir pomelo-protobuf-socket-demo
$ cd pomelo-protobuf-socket-demo
$ pomelo init    # select 1.native socket

初始化时选择 1.native socket

将会自动生成如下的目录树:

- pomelo-protobuf-socket-demo/
  - game-server/
    - app/
    - config/
    - logs/
      app.js
      package.json
  - shared/
  - web-server/
    - bin/
    - public
      app.js
      package.json
    npm-install.bat
    npm-install.sh

直接执行npm-install.*文件将安装相应的依赖,也可以分别进入game-server或者web-server目录下运行 npm install,比如我们这里只是将其作为socket服务器,web-server不会用到,就可以将其删除。

2. 自动生成的数据

  • app子目录

这个目录下放置所有的游戏服务器代码的地方,用户在这里实现不同类型的服务器,添加对应的Handler,Remote等等。

  • config子目录

game-server下config包括了游戏服务器的所有配置信息。配置信息以JSON文件的格式进行定义,包含有日志、master、server等服务器的配置信息。该目录还可以进行扩展,对数据库配置信息、地图信息和数值表等信息进行定义。总而言之,这里是放着所有游戏服务器相关的配置信息的地方。

  • logs子目录

日志是项目中不可或缺的,可以对项目的运行情况进行很好的备份,也是系统运维的参考数据之一,logs存放了游戏服务器所有的日志信息。

查看下自动生成的game-server下的app.js

var pomelo = require('pomelo');
/**
 * Init app for client.
 */
var app = pomelo.createApp();
app.set('name', 'pomelo-protobuf-socket-demo');
// app configuration
app.configure('production|development', 'connector', function(){
  app.set('connectorConfig',
    {
      connector : pomelo.connectors.hybridconnector,
      heartbeat : 3,
      useDict : true,
      useProtobuf : true
    });
});
// start app
app.start();
process.on('uncaughtException', function (err) {
  console.error(' Caught exception: ' + err.stack);
});

将会使用混合连接,也就是socket和websocket会自动适配,传输数据会使用protobuf压缩。

3. 服务器操作

启动

$ cd game-server

$ pomelo start

查看

$ pomelo list

内容定义:

  • serverId:服务器的serverId,同config配置表中的id。
  • serverType:服务器的serverType,同config配置表中的type。
  • pid:服务器对应的进程pid。
  • headUsed:该服务器已经使用的堆大小(单位:兆)。
  • uptime:该服务器启动时长(单位:分钟)。

关闭

$ cd game-server

$ pomelo stop

或者

$ cd game-server

$ pomelo kill

4. 创建client

需要用到Node的net模块,pomelo的协议处理pomelo-protocol,以及数据压缩模块pomelo-protobuf

客户端连接后并发送握手信息

var handshakeBuffer = {
    'sys': {type: 'socket', version: '0.0.1'},
    'user': {}
};
console.log('connect to ' + params.host + ":" + params.port);
var socket = new net.Socket();
socket.binaryType = 'arraybuffer';
socket.connect(params.port, params.host, function(){
    console.log('Connected ... ');
    var obj = Package.encode(Package.TYPE_HANDSHAKE,
    Protocol.strencode(JSON.stringify(handshakeBuffer)));
    socket.write(obj);
});

监听接收数据,收到握手后返回握手成功

socket.on('data', function(data){
    var da = Package.decode(data);
    console.log("data type:"+da.type);
    if (da.type == Package.TYPE_HANDSHAKE){
        var obj = Package.encode(Package.TYPE_HANDSHAKE_ACK);
        socket.write(obj);
    }
}

收到心跳

socket.on('data', function(data){
    var da = Package.decode(data);
    console.log("data type:"+da.type);
    if (da.type == Package.TYPE_HEARTBEAT){
        // 定期发送心跳,另外发送或者每次收到后发送
        var hb = Package.encode(Package.TYPE_HEARTBEAT);
        socket.write(hb);
    }
}

收到数据

socket.on('data', function(data){
    var da = Package.decode(data);
    console.log("data type:"+da.type);
    if (da.type == Package.TYPE_DATA){
      // receive response data
    }
}

收到断开信息

socket.on('data', function(data){
    var da = Package.decode(data);
    console.log("data type:"+da.type);
    if (da.type == Package.TYPE_KICK){
      console.log("connection is closed by server...");
    }
}

发送protobuf压缩后的数据

var protos = Protobuf.parse(require('./Protos.json'));
Protobuf.init({encoderProtos:protos, decoderProtos:protos});
var user = Protobuf.encode('connector.entryHandler.entry', {name: 'name', password: 'pass'});
var msg = Message.encode(1, Message.TYPE_REQUEST, 0, 'connector.entryHandler.entry', user);
var pkg = Package.encode(Package.TYPE_DATA, msg);
socket.write(pkg);

监听错误信息

socket.on('error', function(data){
  console.log(data);
});

监听服务器端口

socket.on('close', function(){
  console.log('connection is close');
});

5. 服务器端会自动处理protobuf

config目录下的clientProtos.json为接收到的protobuf数据结构,serverProtos.json为服务器返回结构的数据结构,这样的话在服务端处理时会自动解包收到的protobuf数据和自动打包处理结果为protobuf.服务器端和客户端必须定义相同的protobuf数据结构才能识别处理数据。

6. 移除心跳

为什么要移除心跳,一般用socket编程不都要处理心跳的么?为什么我们还要考虑把pomelo已经有的心跳给移除嗯?

这是因为在考虑客户端是移动应用,客户端在操作使用的时候,会有数据传输的,就没必要心跳,长时间未操作或者网络不稳定可能会断开,这个也不考虑了,就让他断开吧。也就是在操作的时候心跳是多余的,未操作的时候就不要让移动客户端长时间连着,一直心跳也费流量,未操作是也链接着也飞电。还不如让其断开,再次操作时从新连接。

将服务器代码hybridsocket.js第45行ST_INITED 改为 ST_WORKING,也就是初始状态并不是连接后等待握手,而是直接可以工作状态。

this.state = ST_WORKING; // ST_INITED --> ST_WORKING

这样的话,客户端就直接连接然后发送数据,接收处理数据,其中的握手,握手确认,和心态什么的操作一律省略掉。