1 2 /** 3 * Module dependencies. 4 */ 5 6 var EventEmitter = require('events').EventEmitter 7 , IOWatcher = process.binding('io_watcher').IOWatcher 8 , zmq = require('../binding'); 9 10 /** 11 * Expose bindings as the module. 12 */ 13 14 exports = module.exports = zmq; 15 16 /** 17 * Map of socket types. 18 */ 19 20 var types = exports.types = { 21 pub: zmq.ZMQ_PUB 22 , sub: zmq.ZMQ_SUB 23 , req: zmq.ZMQ_REQ 24 , xreq: zmq.ZMQ_XREQ 25 , rep: zmq.ZMQ_REP 26 , xrep: zmq.ZMQ_XREP 27 , push: zmq.ZMQ_PUSH 28 , pull: zmq.ZMQ_PULL 29 , dealer: zmq.ZMQ_DEALER 30 , router: zmq.ZMQ_ROUTER 31 , pair: zmq.ZMQ_PAIR 32 }; 33 34 /** 35 * Map of socket options. 36 */ 37 38 var opts = exports.options = { 39 _fd: zmq.ZMQ_FD 40 , _ioevents: zmq.ZMQ_EVENTS 41 , _receiveMore: zmq.ZMQ_RCVMORE 42 , _subscribe: zmq.ZMQ_SUBSCRIBE 43 , _unsubscribe: zmq.ZMQ_UNSUBSCRIBE 44 , affinity: zmq.ZMQ_AFFINITY 45 , backlog: zmq.ZMQ_BACKLOG 46 , hwm: zmq.ZMQ_HWM 47 , identity: zmq.ZMQ_IDENTITY 48 , linger: zmq.ZMQ_LINGER 49 , mcast_loop: zmq.ZMQ_MCAST_LOOP 50 , rate: zmq.ZMQ_RATE 51 , rcvbuf: zmq.ZMQ_RCVBUF 52 , reconnect_ivl: zmq.ZMQ_RECONNECT_IVL 53 , recovery_ivl: zmq.ZMQ_RECOVERY_IVL 54 , sndbuf: zmq.ZMQ_SNDBUF 55 , swap: zmq.ZMQ_SWAP 56 }; 57 58 // Context management happens here. We lazily initialize a default context, 59 // and use that everywhere. Also cleans up on exit. 60 var ctx; 61 function defaultContext() { 62 if (ctx) return ctx; 63 64 var io_threads = 1; 65 if (process.env.ZMQ_IO_THREADS) { 66 io_threads = parseInt(process.env.ZMQ_IO_THREADS, 10); 67 if (!io_threads || io_threads < 1) { 68 console.warn('Invalid number in ZMQ_IO_THREADS, using 1 IO thread.'); 69 io_threads = 1; 70 } 71 } 72 73 74 ctx = new zmq.Context(io_threads); 75 process.on('exit', function() { 76 // ctx.close(); 77 ctx = null; 78 }); 79 80 return ctx; 81 }; 82 83 /** 84 * Create a new socket of the given `type`. 85 * 86 * @constructor 87 * @param {String|Number} type 88 * @api public 89 */ 90 91 function Socket(type) { 92 this.type = type; 93 this._zmq = new zmq.Socket(defaultContext(), types[type]); 94 this._outgoing = []; 95 this._watcher = new IOWatcher; 96 this._watcher.callback = this._flush.bind(this); 97 this._watcher.set(this._fd, true, false); 98 this._watcher.start(); 99 }; 100 101 /** 102 * Inherit from `EventEmitter.prototype`. 103 */ 104 105 Socket.prototype.__proto__ = EventEmitter.prototype; 106 107 /** 108 * Set `opt` to `val`. 109 * 110 * @param {String|Number} opt 111 * @param {Mixed} val 112 * @return {Socket} for chaining 113 * @api public 114 */ 115 116 Socket.prototype.setsockopt = function(opt, val){ 117 this._zmq.setsockopt(opts[opt] || opt, val); 118 return this; 119 }; 120 121 /** 122 * Get socket `opt`. 123 * 124 * @param {String|Number} opt 125 * @return {Mixed} 126 * @api public 127 */ 128 129 Socket.prototype.getsockopt = function(opt){ 130 return this._zmq.getsockopt(opts[opt] || opt); 131 }; 132 133 /** 134 * Socket opt accessors allowing `sock.backlog = val` 135 * instead of `sock.setsockopt('backlog', val)`. 136 */ 137 138 Object.keys(opts).forEach(function(name){ 139 Socket.prototype.__defineGetter__(name, function() { 140 return this._zmq.getsockopt(opts[name]); 141 }); 142 143 Socket.prototype.__defineSetter__(name, function(val) { 144 if ('string' == typeof val) val = new Buffer(val, 'utf8'); 145 return this._zmq.setsockopt(opts[name], val); 146 }); 147 }); 148 149 /** 150 * Async bind. 151 * 152 * Emits the "bind" event. 153 * 154 * @param {String} addr 155 * @param {Function} cb 156 * @return {Socket} for chaining 157 * @api public 158 */ 159 160 Socket.prototype.bind = function(addr, cb) { 161 var self = this; 162 self._watcher.stop(); 163 self._zmq.bind(addr, function(err) { 164 self._watcher.start(); 165 self.emit('bind'); 166 cb && cb(err); 167 }); 168 return this; 169 }; 170 171 /** 172 * Sync bind. 173 * 174 * @param {String} addr 175 * @return {Socket} for chaining 176 * @api public 177 */ 178 179 Socket.prototype.bindSync = function(addr) { 180 this._watcher.stop(); 181 try { 182 this._zmq.bindSync(addr); 183 } catch (e) { 184 this._watcher.start(); 185 throw e; 186 } 187 this._watcher.start(); 188 return this; 189 }; 190 191 /** 192 * Connect to `addr`. 193 * 194 * @param {String} addr 195 * @return {Socket} for chaining 196 * @api public 197 */ 198 199 Socket.prototype.connect = function(addr) { 200 this._zmq.connect(addr); 201 return this; 202 }; 203 204 /** 205 * Subscribe with the given `filter`. 206 * 207 * @param {String} filter 208 * @return {Socket} for chaining 209 * @api public 210 */ 211 212 Socket.prototype.subscribe = function(filter) { 213 this._subscribe = filter; 214 return this; 215 }; 216 217 /** 218 * Unsubscribe with the given `filter`. 219 * 220 221 * @param {String} filter 222 * @return {Socket} for chaining 223 * @api public 224 */ 225 226 Socket.prototype.unsubscribe = function(filter) { 227 this._unsubscribe = filter; 228 return this; 229 }; 230 231 /** 232 * Send the given `msg`. 233 * 234 * @param {String|Buffer} msg 235 * @param {Number} flags 236 * @return {Socket} for chaining 237 * @api public 238 */ 239 240 Socket.prototype.send = function(msg, flags) { 241 // allow strings etc 242 if (!Buffer.isBuffer(msg)) { 243 msg = new Buffer(String(msg), 'utf8'); 244 } 245 246 this._outgoing.push([msg, flags || 0]); 247 this._flush(); 248 249 return this; 250 }; 251 252 // The workhorse that does actual send and receive operations. 253 // This helper is called from `send` above, and in response to 254 // the watcher noticing the signaller fd is readable. 255 Socket.prototype._flush = function() { 256 var args; 257 258 // Don't allow recursive flush invocation as it can lead to stack 259 // exhaustion and write starvation 260 if (this._flushing) return; 261 262 this._flushing = true; 263 try { 264 while (true) { 265 var emitArgs 266 , flags = this._ioevents; 267 268 if (!this._outgoing.length) { 269 flags &= ~zmq.ZMQ_POLLOUT; 270 } 271 272 if (!flags) break; 273 274 if (flags & zmq.ZMQ_POLLIN) { 275 emitArgs = ['message']; 276 277 do { 278 emitArgs.push(new Buffer(this._zmq.recv())); 279 } while (this._receiveMore); 280 281 this.emit.apply(this, emitArgs); 282 if (this._zmq.state != zmq.STATE_READY) { 283 this._flushing = false; 284 return; 285 } 286 } 287 288 // We send as much as possible in one burst so that we don't 289 // starve sends if we receive more than one message for each 290 // one sent. 291 while (flags & zmq.ZMQ_POLLOUT && this._outgoing.length) { 292 args = this._outgoing.shift(); 293 this._zmq.send(args[0], args[1]); 294 flags = this._ioevents; 295 } 296 } 297 } catch (e) { 298 this.emit('error', e); 299 } 300 301 this._flushing = false; 302 }; 303 304 /** 305 * Close the socket. 306 * 307 * @return {Socket} for chaining 308 * @api public 309 */ 310 311 Socket.prototype.close = function() { 312 this._watcher.stop(); 313 this._watcher = null; 314 this._zmq.close(); 315 return this; 316 }; 317 318 /** 319 * Create a `type` socket with the given `options`. 320 * 321 * @param {String} type 322 * @param {Object} options 323 * @return {Socket} 324 * @api public 325 */ 326 327 exports.socket = 328 exports.createSocket = function(type, options) { 329 var sock = new Socket(type); 330 for (var key in options) sock[key] = options[key]; 331 return sock; 332 }; 333