You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
289 lines
7.3 KiB
289 lines
7.3 KiB
"use strict"; |
|
|
|
var util = require("./util"); |
|
var wrap = util.wrap; |
|
var defer = util.defer; |
|
var TrieAscoltatore = require("./trie_ascoltatore"); |
|
var AbstractAscoltatore = require('./abstract_ascoltatore'); |
|
var debug = require("debug")("ascoltatori:zmq"); |
|
var steed = require("steed")(); |
|
|
|
/** |
|
* ZeromqAscoltatore is a class that inherits from AbstractAscoltatore. |
|
* It is implemented through the `zmq` package. |
|
* ZeromqAscoltatore operates in a true peer-to-peer fashion, so there is |
|
* no central broker. |
|
* The two or more instances MUST be aware of each other and connect to each |
|
* other by the `connect` method. |
|
* All the instances transmit to everyone ALL messages in a broadcast fashion, |
|
* however loops are avoided. |
|
* |
|
* The options are: |
|
* - `port`, the zmq port where messages will be published; |
|
* - `controlPort`, the zmq port where control messages will be exchanged; |
|
* - `remotePorts`, the remote control ports that will be connected to; |
|
* - `zmq`, the zmq module (it will automatically be required if not present); |
|
* - `delay`, a delay that is applied to the `ready` and `closed` events (the default is 5ms); |
|
* |
|
* @api public |
|
*/ |
|
function ZeromqAscoltatore(opts) { |
|
AbstractAscoltatore.call(this); |
|
|
|
this._opts = opts || {}; |
|
this._opts.delay = this._opts.delay || 5; |
|
this._opts.zmq = this._opts.zmq || require("zmq"); |
|
|
|
this._ascoltatore = new TrieAscoltatore(opts); |
|
this._startSubs(); |
|
this._startPub(); |
|
this._startControl(); |
|
|
|
this._connectedControls = []; |
|
} |
|
|
|
/** |
|
* Inherits from AbstractAscoltatore |
|
* |
|
* @api private |
|
*/ |
|
ZeromqAscoltatore.prototype = Object.create(AbstractAscoltatore.prototype); |
|
|
|
/** |
|
* Create 0MQ connection using of the given type. |
|
* |
|
* @api private |
|
*/ |
|
function createConn(opts, type) { |
|
var conn = opts.zmq.socket(type); |
|
conn.identity = util.buildIdentifier(); |
|
|
|
debug("created " + type + " connection"); |
|
return conn; |
|
} |
|
|
|
/** |
|
* Starts a connection to all the remote ports. |
|
* |
|
* @api private |
|
*/ |
|
ZeromqAscoltatore.prototype._startSubs = function() { |
|
var that = this; |
|
if (this._sub_conns === undefined) { |
|
that._sub_conns = []; |
|
that._opts.remotePorts = that._opts.remotePorts || []; |
|
that._opts.remotePorts.forEach(function(port) { |
|
that.connect(port); |
|
}); |
|
} |
|
return this._sub_conns; |
|
}; |
|
|
|
ZeromqAscoltatore.prototype._startPub = function() { |
|
var that = this; |
|
if (that._pub_conn === undefined) { |
|
that._pub_conn = createConn(that._opts, "pub"); |
|
debug("opening pub port " + that._opts.port); |
|
that._pub_conn.bind(that._opts.port, function(err) { |
|
if (err) { |
|
throw err; |
|
} |
|
|
|
debug("bound the publish connection to port " + that._opts.port); |
|
|
|
setTimeout(function() { |
|
that._connectSub(that._opts.port, function() { |
|
that.emit("ready"); |
|
}); |
|
}, that._opts.delay); |
|
}); |
|
} |
|
return that._pub_conn; |
|
}; |
|
|
|
ZeromqAscoltatore.prototype._startControl = function() { |
|
var that = this; |
|
if (that._control_conn === undefined) { |
|
that._control_conn = createConn(that._opts, "req"); |
|
debug("opening control port " + that._opts.controlPort); |
|
that._control_conn.bind(that._opts.controlPort, function(err) { |
|
if (err) { |
|
throw err; |
|
} |
|
|
|
debug("bound the control connection to port " + that._opts.controlPort); |
|
|
|
that._control_conn_interval = setInterval(function() { |
|
var packet = that._sub_conns.map(function(c) { |
|
return c.port; |
|
}).join(","); |
|
debug("sending control packet " + packet); |
|
that._control_conn.send(packet); |
|
}, 250); |
|
|
|
that._control_conn.on("message", function(data) { |
|
debug("received connect response from " + data); |
|
that._connectSub(data); |
|
}); |
|
}); |
|
} |
|
}; |
|
|
|
/** |
|
* Connect the Ascoltatore to the remote ZeromqAscoltatore exposed |
|
* through the given port |
|
* |
|
* @param {String} port The control port of the remote ascoltatore |
|
* @param {Function} callback |
|
* @api public |
|
*/ |
|
ZeromqAscoltatore.prototype.connect = function connect(port, callback) { |
|
var that = this, |
|
conn = null; |
|
|
|
conn = createConn(that._opts, "rep"); |
|
|
|
conn.connect(port); |
|
|
|
that._connectedControls.push(conn); |
|
|
|
conn.on("message", function(data) { |
|
debug("received connect request from " + data); |
|
conn.send(that._opts.port); |
|
|
|
var dests = String(data).split(",").filter(function(dest) { |
|
var found = true; |
|
that._sub_conns.forEach(function(conn) { |
|
if (conn.port === dest) { |
|
found = false; |
|
} |
|
}); |
|
return found; |
|
}).map(function(dest) { |
|
return function(cb) { |
|
that._connectSub(dest, cb); |
|
}; |
|
}); |
|
|
|
steed.parallel(dests, function() { |
|
setTimeout(function() { |
|
wrap(callback)(); |
|
}, that._opts.delay); |
|
}); |
|
}); |
|
}; |
|
|
|
/** |
|
* Connect the Ascoltatore to the remote ZMQ port. |
|
* |
|
* @param {String} port The control port of the remote ascoltatore |
|
* @param {Function} callback |
|
* @api private |
|
*/ |
|
ZeromqAscoltatore.prototype._connectSub = function(port, callback) { |
|
var that = this, |
|
conn = createConn(that._opts, "sub"); |
|
|
|
port = String(port); |
|
|
|
debug("connecting to port " + port); |
|
conn.port = port; |
|
conn.connect(port); |
|
conn.subscribe(""); |
|
that._sub_conns.push(conn); |
|
|
|
conn.on("message", function(data) { |
|
data = data.toString(); |
|
var topic = null, |
|
message = null; |
|
|
|
topic = data.substr(0, data.indexOf(" ")); |
|
message = data.substr(data.indexOf(" ") + 1); |
|
|
|
that._ascoltatore.publish(topic, message); |
|
|
|
debug("new message received for topic " + topic); |
|
}); |
|
|
|
setTimeout(function() { |
|
debug("connected and subscribed to " + port); |
|
defer(callback); |
|
}, this._opts.delay); |
|
|
|
return this; |
|
}; |
|
|
|
/** |
|
* Private stuff |
|
* |
|
* @api private |
|
*/ |
|
ZeromqAscoltatore.prototype.subscribe = function subscribe(topic, callback, done) { |
|
this._raiseIfClosed(); |
|
debug("registered new subscriber for topic " + topic); |
|
this._ascoltatore.subscribe(topic, callback, done); |
|
}; |
|
|
|
ZeromqAscoltatore.prototype.publish = function publish(topic, message, done) { |
|
this._raiseIfClosed(); |
|
|
|
var toSend = topic + " " + message; |
|
|
|
this._pub_conn.send(toSend); |
|
debug("new message published to " + topic); |
|
defer(done); // simulate some steedhronicity |
|
}; |
|
|
|
ZeromqAscoltatore.prototype.unsubscribe = function unsubscribe(topic, callback, done) { |
|
this._raiseIfClosed(); |
|
debug("deregistered subscriber for topic " + topic); |
|
this._ascoltatore.unsubscribe(topic, callback); |
|
defer(done); // simulate some steedhronicity |
|
}; |
|
|
|
ZeromqAscoltatore.prototype.close = function close(done) { |
|
var that = this; |
|
|
|
if (this._closed) { |
|
defer(done); |
|
return; |
|
} |
|
|
|
if (that._sub_conns !== undefined) { |
|
that._sub_conns.forEach(function(s) { |
|
s.close(); |
|
}); |
|
delete that._sub_conns; |
|
} |
|
|
|
that._connectedControls.forEach(function(s) { |
|
s.close(); |
|
}); |
|
|
|
if (that._pub_conn !== undefined) { |
|
that._pub_conn.close(); |
|
delete that._pub_conn; |
|
} |
|
|
|
if (that._control_conn !== undefined && that._control_conn._zmq.state === 0) { |
|
that._control_conn.close(); |
|
clearInterval(that._control_conn_interval); |
|
delete that._control_conn_interval; |
|
} |
|
|
|
setTimeout(function() { |
|
debug("closed"); |
|
that._ascoltatore.close(); |
|
that.emit("closed"); |
|
defer(done); |
|
}, this._opts.delay); |
|
}; |
|
|
|
util.aliasAscoltatore(ZeromqAscoltatore.prototype); |
|
|
|
/** |
|
* Export ZeromqAscoltatore |
|
* |
|
* @api public |
|
*/ |
|
module.exports = ZeromqAscoltatore;
|
|
|