Awesome
pomelo-kcp
KCP Protocol for Pomelo
HowTo
Install Pomelo
$sudo npm install -g pomelo
Create Pomelo Project
$pomelo init pomelo_test_server
$cd pomelo_test_server
Add pomelo-kcp dependencies
$cd pomelo_test_server
$cd game_server
$vim package.json
package.json
{
"name":"pomelo-server",
"version":"0.0.1",
"private":false,
"dependencies":{
"pomelo":"1.2.3"
}
}
add pomelo-kcp dependencie
{
"name":"pomelo-server",
"version":"0.0.1",
"private":false,
"dependencies":{
"pomelo":"1.2.3",
"pomelo-kcp":"^0.0.4"
}
}
Install dependencies modules
$cd pomelo_test_server/game_server
$npm install
Change Application Connector
Edit app.js
$vim pomelo_test_server/game_server/app.js
Use KCP Connector
var pomelo = require('pomelo');
// Require pomelo-kcp !!!
var kcpconnector = require('pomelo-kcp');
/**
* Init app for client.
*/
var app = pomelo.createApp();
app.set('name', 'pomelo-server');
// app configuration
app.configure('production|development', 'connector', function(){
app.set('connectorConfig',
{
// connector : pomelo.connectors.hybridconnector,
// Use KCP Connector !!!
connector : kcpconnector.kcpconnector,
// KCP Options :
nodelay : 0,
interval : 100, // ms
resend : 0,
nc : 0,
sndwnd : 32,
rcvwnd : 32,
mtu : 1400,
// You can use Pomelo protocol if you want to
usePomeloPackage : true
heartbeat : 3,
useDict : true,
useProtobuf : true
});
});
// start app
app.start();
process.on('uncaughtException', function (err) {
console.error(' Caught exception: ' + err.stack);
});
Start Pomelo
$cd pomelo_test_server/game_server
$pomelo start
Test
$node pomelo_test_server/game_server/node_modules/pomelo-kcp/test/udpclient.js
udpclient.js
/**
* Copyright 2016 leenjewel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var dgram = require('dgram');
var kcp = require('./../node_modules/node-kcp');
var pomelocoder = require('./../lib/connectors/pomelocoder');
var PomeloClient = function(host, port, opts){
if (!(this instanceof PomeloClient)) {
return new PomeloClient(host, port, opts);
}
EventEmitter.call(this);
this.opts = opts || {};
this.host = host;
this.port = port;
this.heartbeatId = undefined;
var self = this;
var conv = opts.conv || 123;
this.kcpobj = new kcp.KCP(conv, self);
var nodelay = opts.nodelay || 0;
var interval = opts.interval || 100;
var resend = opts.resend || 0;
var nc = opts.nc || 0;
this.kcpobj.nodelay(nodelay, interval, resend, nc);
var sndwnd = opts.sndwnd || 32;
var rcvwnd = opts.rcvwnd || 32;
this.kcpobj.wndsize(sndwnd, rcvwnd);
var mtu = opts.mtu || 1400;
this.kcpobj.setmtu(mtu);
this.socket = dgram.createSocket('udp4');
this.socket.on('error', function(error){
console.log(`client error:\n${err.stack}`);
self.socket.close();
});
this.socket.on('message', function(msg, peer){
self.kcpobj.input(msg);
var data = self.kcpobj.recv();
if (!!data) {
if (self.opts && self.opts.usePomeloPackage) {
var pkg = pomelocoder.decodePackage(data);
if (pomelocoder.isHandshakePackage(pkg.type)) {
self.emit('handshake', JSON.parse(pkg.body));
} else if (pomelocoder.isHeartbeatPackage(pkg.type)) {
self.emit('heartbeat', pkg);
} else if (pomelocoder.isDataPackage(pkg.type)) {
pkg = pomelocoder.decodeMessage(data);
self.emit('data', JSON.parse(pkg.body));
} else if (pomelocoder.isKickPackage(pkg.type)) {
self.emit('kick', pkg);
} else {
self.emit('message', data);
}
} else {
self.emit('data', JSON.parse(data));
}
}
});
this.kcpobj.output((data, size, context) => {
self.socket.send(data, 0, size, context.port, context.host);
});
this.on('handshake', function(pkg){
console.log('handshake ...');
self.ack();
self.init(pkg);
});
this.on('heartbeat', function(pkg){
console.log('heartbeat...'+pomelocoder.getHeartbeatInterval());
if (!self.heartbeatId) {
self.emit('connected', pkg);
}
self.heartbeatId = setTimeout(function(){
self.heartbeat();
}, pomelocoder.getHeartbeatInterval());
});
this.on('kick', function(pkg){
console.log('kick ...');
self.socket.close();
});
this.check();
if (!opts.usePomeloPackage) {
setTimeout(function() {
self.emit('connected');
}, 0);
}
};
util.inherits(PomeloClient, EventEmitter);
PomeloClient.prototype.check = function() {
var self = this;
this.kcpobj.update(Date.now());
setTimeout(function(){
self.check();
}, this.kcpobj.check(Date.now()));
};
PomeloClient.prototype.send = function(data) {
this.kcpobj.send(data);
this.kcpobj.flush();
};
PomeloClient.prototype.request = function(msg) {
if (this.opts && this.opts.usePomeloPackage) {
msg = pomelocoder.messagePackage(
msg.id,
msg.route,
msg.body
);
this.send(msg);
} else {
this.send(JSON.stringify(msg));
}
};
PomeloClient.prototype.handshake = function() {
if (this.opts && this.opts.usePomeloPackage) {
this.send(pomelocoder.handshakePackage());
}
};
PomeloClient.prototype.init = function(data) {
var self = this;
if (this.opts && this.opts.usePomeloPackage) {
pomelocoder.initProtocol(data);
}
};
PomeloClient.prototype.ack = function() {
if (this.opts && this.opts.usePomeloPackage) {
this.send(pomelocoder.handshakeAckPackage());
}
};
PomeloClient.prototype.heartbeat = function() {
if (this.opts && this.opts.usePomeloPackage) {
this.send(pomelocoder.heartbeatPackage());
}
};
// Run Test
var reqid = 1;
var client = new PomeloClient('127.0.0.1', 3010, {usePomeloPackage: true});
client.handshake();
client.on('connected', function(userdata){
console.log('onConnected and send request...');
client.request({
id: reqid++,
route: 'connector.entryHandler.entry',
body: {}
});
});
client.on('data', function(userdata){
setTimeout(function() {
console.log('onData : '+JSON.stringify(userdata)+' and send request : '+(reqid+1));
client.request({
id: reqid++,
route: 'connector.entryHandler.entry',
body: {}
});
}, 500);
});