123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- /**
- * Module dependencies
- */
- var EventEmitter = require('events').EventEmitter;
- var util = require('util');
- var Protocol = require('./protocol');
- var Protobuf = require('./protobuf');
- var Package = Protocol.Package;
- var Message = Protocol.Message;
- var wsClient = require('websocket').client;
- var JS_WS_CLIENT_TYPE = 'js-websocket';
- var JS_WS_CLIENT_VERSION = '0.0.1';
- var RES_OK = 200;
- var RES_OLD_CLIENT = 501;
- var ePomelo = function (opts) {
- EventEmitter.call(this);
- this._socket = null;
- this._id = 1;
- this._callbacks = {};
- this._routeMap = {};
- this.dict = {};
- this.abbrs = {};
- this.protoVersion = 0;
- this.serverProtos = {};
- this.clientProtos = {};
- this.handshakeBuffer = {};
- this.heartbeatInterval = 0;
- this.heartbeatTimeout = 0;
- this.nextHeartbeatTimeout = 0;
- this.gapThreshold = 100; // heartbeat gap threashold
- this.heartbeatId = null;
- this.heartbeatTimeoutId = null;
- this.initCallback = null;
- };
- util.inherits(ePomelo, EventEmitter);
- module.exports = ePomelo;
- var pomelo = ePomelo.prototype;
- pomelo.init = function(params, cb) {
- var self = this;
- self.handshakeBuffer = {
- 'sys': {
- type: JS_WS_CLIENT_TYPE,
- version: JS_WS_CLIENT_VERSION,
- protoVersion: 0,
- rsa: {}
- },
- 'user': {
- }
- };
- if(!!cb && typeof cb === 'function') {
- self.initCallback = cb;
- }
- var client = new wsClient();
- client.connect({protocol:"ws:", hostname: params.host, port:params.port});
- client.on('connect', function (socket) { //socket 其实就是WebScoketConnection对象
- var obj = Package.encode(Package.TYPE_HANDSHAKE, Protocol.strencode(JSON.stringify(self.handshakeBuffer)));
- socket.send(obj);
- socket.on('message', function(msg){
- processPackage(self, Package.decode(msg.binaryData));
- });
- socket.on('error', function(msg){
- console.log('socket error', msg);
- self.emit('error', msg);
- });
- socket.on('close', function(msg){
- console.log('socket close', msg);
- self.emit('close', msg);
- });
- self._socket = socket;
- });
- }
- pomelo.request = function(route, msg, cb) {
- var self = this;
- if(arguments.length === 2 && typeof msg === 'function') {
- cb = msg;
- msg = {};
- } else {
- msg = msg || {};
- }
- route = route || msg.route;
- if(!route) {
- return;
- }
- var reqId = self._id++;
- sendMessage(self, reqId, route, msg);
- self._callbacks[reqId] = cb;
- self._routeMap[reqId] = route;
- };
- pomelo.notify = function (route, msg) {
- this.request(route, msg);
- };
- pomelo.disconnect = function() {
- var self = this;
- self._socket.emit('end');
- self._socket.close();
- if(self.heartbeatId) {
- clearTimeout(self.heartbeatId);
- self.heartbeatId = null;
- }
- if(self.heartbeatTimeoutId) {
- clearTimeout(self.heartbeatTimeoutId);
- self.heartbeatTimeoutId = null;
- }
- };
- /*
- * handshake、heartbeat、onData、onKick四个函数对应于与服务通信中不同的事件处理
- */
- var handshake = function(pomelo, data){
- data = JSON.parse(Protocol.strdecode(data));
- if(data.code === RES_OLD_CLIENT) {
- pomelo.emit('error', 'client version not fullfill');
- return;
- }
- if(data.code !== RES_OK) {
- pomelo.emit('error', 'handshake fail');
- return;
- }
- if(data.sys && data.sys.heartbeat) {
- pomelo.heartbeatInterval = data.sys.heartbeat * 1000; // heartbeat interval
- pomelo.heartbeatTimeout = pomelo.heartbeatInterval * 2; // max heartbeat timeout
- } else {
- pomelo.heartbeatInterval = 0;
- pomelo.heartbeatTimeout = 0;
- }
- //Init compress dict 保存全局的dict信息
- pomelo.dict = data.sys.dict;
- if(pomelo.dict){
- pomelo.abbrs = {};
- for(var route in pomelo.dict){ //保存dict的映射信息
- pomelo.abbrs[pomelo.dict[route]] = route;
- }
- }
- //Init protobuf protos
- var protos = data.sys.protos;
- if(protos){
- pomelo.protoVersion = protos.version || 0;
- pomelo.serverProtos = protos.server || {};
- pomelo.clientProtos = protos.client || {};
- Protobuf.init({encoderProtos: protos.client, decoderProtos: protos.server});
- }
- var obj = Package.encode(Package.TYPE_HANDSHAKE_ACK);
- pomelo._socket.send(obj);
- if(!!pomelo.initCallback && 'function' === typeof pomelo.initCallback) { //注意:只所以初使化的回调在这里返回,是为了保证C-S连接全部完成之后,client才可以请求
- pomelo.initCallback();
- }
- };
- var heartbeat = function(pomelo, data) {
- if(!pomelo.heartbeatInterval) {
- // no heartbeat
- return;
- }
- var obj = Package.encode(Package.TYPE_HEARTBEAT);
- if(pomelo.heartbeatTimeoutId) {
- clearTimeout(pomelo.heartbeatTimeoutId);
- pomelo.heartbeatTimeoutId = null;
- }
- if(pomelo.heartbeatId) {
- // already in a heartbeat interval
- return;
- }
- pomelo.heartbeatId = setTimeout(function() {
- pomelo.heartbeatId = null;
- pomelo._socket.send(obj);
- pomelo.nextHeartbeatTimeout = Date.now() + pomelo.heartbeatTimeout;
- pomelo.heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb.bind(null, pomelo), pomelo.heartbeatTimeout);
- }, pomelo.heartbeatInterval);
- };
- var heartbeatTimeoutCb = function(pomelo) {
- var gap = pomelo.nextHeartbeatTimeout - Date.now();
- if(gap > pomelo.gapThreshold) {
- pomelo.heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb.bind(null, pomelo), gap);
- } else {
- pomelo.emit('heartbeat timeout');
- pomelo.disconnect();
- }
- }
- var onData = function(pomelo, data) {
- var msg = data;
- msg = defaultDecode(pomelo, msg);
- processMessage(pomelo, msg);
- };
- var onKick = function(pomelo, data) {
- data = JSON.parse(Protocol.strdecode(data));
- pomelo.emit('onKick', data);
- };
- var handlers = {};
- handlers[Package.TYPE_HANDSHAKE] = handshake;
- handlers[Package.TYPE_HEARTBEAT] = heartbeat;
- handlers[Package.TYPE_DATA] = onData;
- handlers[Package.TYPE_KICK] = onKick;
- /*
- * processPackage、processMessage 两函数处理服务器返回的消息
- */
- var processPackage = function(pomelo, msgs) {
- if(Array.isArray(msgs)) {
- for(var i=0; i<msgs.length; i++) {
- var msg = msgs[i];
- handlers[msg.type](pomelo, msg.body);
- }
- } else {
- handlers[msgs.type](pomelo, msgs.body);
- }
- };
- var processMessage = function(pomelo, msg) {
- if(!msg.id) {
- // server push message
- //pomelo.emit(msg.route, msg.body);
- pomelo.emit('processMessage', msg);
- return;
- }
- //if have a id then find the callback function with the request
- var cb = pomelo._callbacks[msg.id];
- delete pomelo._callbacks[msg.id];
- if(typeof cb !== 'function') {
- return;
- }
- cb(msg.body);
- return;
- };
- /*
- * sendMessage发送给服务器
- */
- var sendMessage = function(pomelo, reqId, route, msg) {
- /* 暂时不处理Crypto
- if(useCrypto) {
- msg = JSON.stringify(msg);
- var sig = rsa.signString(msg, "sha256");
- msg = JSON.parse(msg);
- msg['__crypto__'] = sig;
- }*/
- msg = defaultEncode(pomelo, reqId, route, msg);
- var packet = Package.encode(Package.TYPE_DATA, msg);
- pomelo._socket.send(packet);
- };
- var defaultEncode = function(pomelo, reqId, route, msg) {
- var type = reqId ? Message.TYPE_REQUEST : Message.TYPE_NOTIFY;
- //compress message by protobuf
- if(pomelo.clientProtos && pomelo.clientProtos[route]) {
- msg = Protobuf.encode(route, msg);
- } else {
- msg = Protocol.strencode(JSON.stringify(msg));
- }
- var compressRoute = 0;
- if(pomelo.dict && pomelo.dict[route]) {
- route = pomelo.dict[route];
- compressRoute = 1;
- }
- return Message.encode(reqId, type, compressRoute, route, msg);
- };
- var defaultDecode = function(pomelo, data) {
- //probuff decode
- var msg = Message.decode(data);
- if(msg.id > 0){
- msg.route = pomelo._routeMap[msg.id];
- delete pomelo._routeMap[msg.id];
- if(!msg.route){
- return;
- }
- }
- msg.body = deCompose(pomelo, msg);
- return msg;
- };
- var deCompose = function(pomelo, msg){
- var route = msg.route;
- //Decompose route from dict
- if(msg.compressRoute) {
- if(!pomelo.abbrs[route]){
- return {};
- }
- route = msg.route = pomelo.abbrs[route];
- }
- if(pomelo.serverProtos && pomelo.serverProtos[route]){
- return Protobuf.decode(route, msg.body);
- }else{
- return JSON.parse(Protocol.strdecode(msg.body));
- }
- return msg;
- };
|