axon是一个很好的底层通信框架,在socket的基础上封装了很多功能。
它一个有四种模式:
push / pull
pub / sub
req / rep
pub-emitter / sub-emitter
1.push/pull模式:模式特点,单向单发,服务器发消息,客户端接受消息。而且是每条消息只被一个客户端接受到。
2.PUB/SUB模式:模式特点,单向群发,服务器发消息,客户端接受消息,而且是每条消息都会被每个客户端收到。
在axon模块里有两组API实现pub和sub消息推送。
pub & sub:
PubSocket.prototype.send = function(msg){
var socks = this.socks;
var len = socks.length;
var sock;
var buf = this.pack(arguments);
for (var i = 0; i < len; i++) {
sock = socks[i];
if (sock.writable) sock.write(buf);
}
return this;
};
这是PubSocket广播消息的方法,从方法里可以看出,它只是做了一个很简单的广播,没有做任何处理。
SubSocket.prototype.onmessage = function(sock){
var subs = this.hasSubscriptions();
var self = this;
return function(buf){
var msg = new Message(buf);
if (subs) {
var topic = msg.args[0];
if (!self.matches(topic)) return debug('not subscribed to "%s"', topic);
}
self.emit.apply(self, ['message'].concat(msg.args));
};
};
SubSocket.prototype.subscribe = function(re){
debug('subscribe to "%s"', re);
this.subscriptions.push(re = toRegExp(re));
return re;
};
SubSocket.prototype.unsubscribe = function(re){
debug('unsubscribe from "%s"', re);
re = toRegExp(re);
for (var i = 0; i < this.subscriptions.length; ++i) {
if (this.subscriptions[i].toString() === re.toString()) {
this.subscriptions.splice(i--, 1);
}
}
};
SubSocket.prototype.clearSubscriptions = function(){
this.subscriptions = [];
};
在sock.on('message', function(msg)的时候,如果添加了订阅项,它会只接收订阅相关的信息。如果没有添加任何订阅项,它会接收所有的信息。判断依据就是subscriptions里面的条目。完整的例子代码:
// 服务端
var axon = require('axon');
var sock = axon.socket('pub');
sock.bind(3000);
console.log('pub server started');
setInterval(function(){
sock.send('hello');
}, 500);
// 客户端
var axon = require('axon');
var sock = axon.socket('sub');
sock.connect(3000);
sock.subscribe('user:login');
sock.subscribe('upload:*:progress');
sock.on('message', function(topic, msg){
});
PubEmitter / SubEmitter:
function PubEmitterSocket() {
this.sock = new PubSocket;
this.emit = this.sock.send.bind(this.sock);
this.bind = this.sock.bind.bind(this.sock);
this.connect = this.sock.connect.bind(this.sock);
this.close = this.sock.close.bind(this.sock);
}
PubEmitterSocket是建立在pub的基础之上的,在这之后只是重新声明了几个方法名。例如,emit等价于pub的send。另外就是,pub既可以做bind服务器端,也可以做connect客户端。但是,无论是做服务器还是客户端它都是消息的推送方,也就是它是发送消息的。
SubEmitterSocket.prototype.onmessage = function(){
var listeners = this.listeners;
var self = this;
return function(buf){
var msg = new Message(buf);
var topic = msg.shift();
for (var i = 0; i < listeners.length; ++i) {
var listener = listeners[i];
var m = listener.re.exec(topic);
if (!m) continue;
listener.fn.apply(this, m.slice(1).concat(msg.args));
}
}
};
SubEmitterSocket.prototype.on = function(event, fn){
var re = this.sock.subscribe(event);
this.listeners.push({
event: event,
re: re,
fn: fn
});
return this;
};
SubEmitterSocket.prototype.off = function(event){
for (var i = 0; i < this.listeners.length; ++i) {
if (this.listeners[i].event === event) {
this.sock.unsubscribe(this.listeners[i].re);
this.listeners.splice(i--, 1);
}
}
return this;
};
在使用sub的时候,我们只能使用一种规则定制接收信息的方式,这显然是不够的。所以SubEmitterSocket增加了一个listeners数组,用来存储多套规则,这些规则也拥有他们自己对信息处理的方法。
另外就是,在把信息apply到listener的时候,listener.fn.apply(this, m.slice(1).concat(msg.args));貌似把信息转换成了json对象,这样获取到的信息可以直接用对象的方式使用。
完整的例子:
// 服务器
var axon = require('axon');
var sock = axon.socket('pub-emitter');
sock.connect(3000);
setInterval(function(){
sock.emit('login', { name: 'tobi' });
}, 500);
//客户端
var axon = require('axon');
var sock = axon.socket('sub-emitter');
sock.bind(3000);
sock.on('user:login', function(user){
console.log('%s signed in', user.name);
});
sock.on('user:*', function(action, user){
console.log('%s %s', user.name, action);
});
sock.on('*', function(event){
console.log(arguments);
});
理论上来说,我们必然使用第二种方式。
3.REP/REQ模式:模式特点,双向应答,点对点,服务器发送消息,客户端接受消息,然后客户端发送,反正双向不受限制。
RPC进程间调用,可以理解为远程调用方法。
RepSocket.prototype.onmessage = function(sock){
var self = this;
return function (buf){
var msg = new Message(buf);
var args = msg.args;
var id = args.pop();
args.unshift('message');
args.push(reply);
self.emit.apply(self, args);
function reply() {
var fn = function(){};
var args = slice(arguments);
args[0] = args[0] || null;
var hasCallback = 'function' == typeof args[args.length - 1];
if (hasCallback) fn = args.pop();
args.push(id);
if (sock.writable) {
sock.write(self.pack(args), function(){ fn(true) });
return true;
} else {
debug('peer went away');
process.nextTick(function(){ fn(false) });
return false;
}
}
};
};
rep作为服务端,其实是消息的接收方和处理方。首先它收到的是一个字符串,所以第一步就是把这个字符串解析出对应的参数列表,然后处理这些参数列表,再最后又通过socket传回去。这里的args可以是多个参数。
ReqSocket.prototype.send = function(msg){
var socks = this.socks;
var len = socks.length;
var sock = socks[this.n++ % len];
var args = slice(arguments);
if (sock) {
var hasCallback = 'function' == typeof args[args.length - 1];
if (!hasCallback) args.push(function(){});
var fn = args.pop();
fn.id = this.id();
this.callbacks[fn.id] = fn;
args.push(fn.id);
}
if (sock) {
sock.write(this.pack(args));
} else {
debug('no connected peers');
this.enqueue(args);
}
};
ReqSocket.prototype.onmessage = function(){
var self = this;
return function(buf){
var msg = new Message(buf);
var id = msg.pop();
var fn = self.callbacks[id];
if (!fn) return debug('missing callback %s', id);
fn.apply(null, msg.args);
delete self.callbacks[id];
};
};
在req里面有两个方法,一个是发送请求,一个是接受结果。我需要用到的只是发送请求。貌似不用解释很多。
完整用法:
// 客户端
var axon = require('axon');
var sock = axon.socket('req');
sock.bind(3000);
sock.send('resize', img, function(res){
});
// 服务器
var axon = require('axon');
var sock = axon.socket('rep');
sock.connect(3000);
sock.on('message', function(task, img, reply){
switch (task) {
case 'resize':
// resize the image
reply(img);
break;
}
});
首先是发送的时候,发送过来两个参数'resize', img,返回的时候返回了img这个参数 reply(img);。这些只是看代码猜的,还需要实际用的时候进行测试。
总结:node.js真心厉害,在我认为比较难的功能,尤其是通信这块,尽然用这么少的代码完成了大部分需要用到的通信模式。选择一个好的技术真的很重要