Skip to content
This repository
Browse code

Initial commit

  • Loading branch information...
commit 5c2217fdbeeadb70c5f4895175faec0f01a32abb 0 parents
TJ Holowaychuk authored July 11, 2012
2  .gitignore
... ...
@@ -0,0 +1,2 @@
  1
+node_modules
  2
+testing
7  .npmignore
... ...
@@ -0,0 +1,7 @@
  1
+examples
  2
+<<<<<<< HEAD
  3
+=======
  4
+Readme.md
  5
+test
  6
+testing
  7
+>>>>>>> 9288590b9bb16130c35d64447747328678b0da2b
7  Makefile
... ...
@@ -0,0 +1,7 @@
  1
+
  2
+TESTS = $(wildcard test/test.*.js)
  3
+
  4
+test:
  5
+	@./test/run $(TESTS)
  6
+	
  7
+.PHONY: test
176  Readme.md
Source Rendered
... ...
@@ -0,0 +1,176 @@
  1
+
  2
+# Super Sockets
  3
+
  4
+  Super Sockets is a message-oriented socket library for node.js heavily inspired by zeromq.
  5
+
  6
+## Features
  7
+
  8
+  - message oriented
  9
+  - automated reconnection
  10
+  - light-weight wire protocol
  11
+  - light-weight implementation (~300 SLOC)
  12
+  - supports arbitrary binary message (msgpack, json, BLOBS, etc)
  13
+  - supports JSON messages
  14
+  - push / pull pattern
  15
+  - pub / sub pattern
  16
+  - event emitter pattern
  17
+
  18
+## Push / Pull
  19
+
  20
+`PushSocket`s distribute messages round-robin.
  21
+
  22
+```js
  23
+var ss = require('super-sockets');
  24
+var sock = ss.socket('push');
  25
+
  26
+sock.bind(3000);
  27
+console.log('push server started');
  28
+
  29
+setInterval(function(){
  30
+  process.stdout.write('.');
  31
+  sock.send('hello');
  32
+}, 150);
  33
+```
  34
+
  35
+Receiver of `PushSocket` messages.
  36
+
  37
+```js
  38
+var ss = require('../../')
  39
+  , sock = ss.socket('push');
  40
+
  41
+sock.bind(3000);
  42
+console.log('push server started');
  43
+
  44
+setInterval(function(){
  45
+  process.stdout.write('.');
  46
+  sock.send('hello');
  47
+}, 150);
  48
+```
  49
+
  50
+## Pub / Sub
  51
+
  52
+`PubSocket`s send messages to all subscribers without queueing.
  53
+
  54
+_pub.js_:
  55
+
  56
+```js
  57
+var ss = require('super-sockets');
  58
+var sock = ss.socket('pub');
  59
+
  60
+sock.bind(3000);
  61
+console.log('pub server started');
  62
+
  63
+setInterval(function(){
  64
+  console.log('sending');
  65
+  sock.send('hello');
  66
+}, 500);
  67
+```
  68
+
  69
+`SubSocket` provides selective reception of messages from a `PubSocket`.
  70
+
  71
+_sub.js_:
  72
+
  73
+```js
  74
+var ss = require('super-sockets');
  75
+var sock = ss.socket('sub');
  76
+
  77
+sock.connect(3000);
  78
+
  79
+sock.on('message', function(msg){
  80
+  console.log(msg.toString());
  81
+});
  82
+```
  83
+
  84
+## EmitterSocket
  85
+
  86
+`EmitterSocket`'s send and receive messages behaving like regular node `EventEmitter`s.
  87
+This is achieved by using pub / sub sockets behind the scenes. Currently we simply choose
  88
+define the `EmitterSocket` as a `PubSocket` if you `.bind()`, and `SubSocket` if you `.connect()`,
  89
+providing the natural API you're used to:
  90
+
  91
+server.js:
  92
+
  93
+```js
  94
+var ss = require('super-sockets');
  95
+var sock = ss.socket('emitter');
  96
+
  97
+sock.bind(3000);
  98
+console.log('pub server started');
  99
+
  100
+setInterval(function(){
  101
+  sock.emit('login', { name: 'tobi' });
  102
+}, 500);
  103
+```
  104
+
  105
+client.js:
  106
+
  107
+```js
  108
+var ss = require('super-sockets');
  109
+var sock = ss.socket('emitter');
  110
+
  111
+sock.connect(3000);
  112
+console.log('sub client connected');
  113
+
  114
+sock.on('login', function(user){
  115
+  console.log('%s signed in', user.name);
  116
+});
  117
+```
  118
+
  119
+## Protocol
  120
+
  121
+  The wire protocol is simple and very much zeromq-like, where `<length>` is
  122
+  a 24 bit unsigned integer, representing a maximum length of 2^24, roughly ~16mb,
  123
+  so you should be good :p
  124
+
  125
+```
  126
+ octet:     0      1      2      3      <length>
  127
+        +------+------+------+------+------------------...
  128
+        | meta | <length>           | data ...
  129
+        +------+------+------+------+------------------...
  130
+```
  131
+
  132
+## Running tests
  133
+
  134
+```
  135
+$ npm install
  136
+$ make test
  137
+```
  138
+
  139
+## Todo
  140
+
  141
+  - more tests
  142
+  - code cov
  143
+  - acks
  144
+  - emitter style on top of pubsub with multipart
  145
+  - pluggable codecs
  146
+  - weighted fair queuing
  147
+  - use mocha for tests
  148
+  - multipart
  149
+  - subscriptions
  150
+  - ...
  151
+
  152
+## License 
  153
+
  154
+(The MIT License)
  155
+
  156
+Copyright (c) 2012 TJ Holowaychuk &lt;tj@vision-media.ca&gt;
  157
+
  158
+Permission is hereby granted, free of charge, to any person obtaining
  159
+a copy of this software and associated documentation files (the
  160
+'Software'), to deal in the Software without restriction, including
  161
+without limitation the rights to use, copy, modify, merge, publish,
  162
+distribute, sublicense, and/or sell copies of the Software, and to
  163
+permit persons to whom the Software is furnished to do so, subject to
  164
+the following conditions:
  165
+
  166
+The above copyright notice and this permission notice shall be
  167
+included in all copies or substantial portions of the Software.
  168
+
  169
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
  170
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  171
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  172
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  173
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  174
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  175
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  176
+
10  benchmark/pub.js
... ...
@@ -0,0 +1,10 @@
  1
+
  2
+var zmq = require('..')
  3
+  , pub = zmq.socket('pub');
  4
+
  5
+var n = 1000;
  6
+
  7
+pub.bind(3000, function(){
  8
+  console.log('bound to :3000');
  9
+  while (n--) pub.send('foo');
  10
+});
26  benchmark/sub.js
... ...
@@ -0,0 +1,26 @@
  1
+
  2
+var zmq = require('..')
  3
+  , sub = zmq.socket('sub');
  4
+
  5
+var start = new Date
  6
+  , n = 1000
  7
+  , ops = n;
  8
+
  9
+sub.on('message', function(msg){
  10
+  console.error(msg);
  11
+  --n || (function(){
  12
+    var duration = new Date - start;
  13
+    pub.close();
  14
+    sub.close();
  15
+    console.log();
  16
+    console.log('  pub/sub:');
  17
+    console.log('    \033[36m%d\033[m msgs', ops);
  18
+    console.log('    \033[36m%d\033[m ops/s', ops / (duration / 1000) | 0);
  19
+    console.log('    \033[36m%d\033[m ms', duration);
  20
+    console.log();
  21
+  })();
  22
+});
  23
+
  24
+sub.connect(3000, function(){
  25
+  console.log('connected to :3000');
  26
+});
14  examples/emitter/client.js
... ...
@@ -0,0 +1,14 @@
  1
+
  2
+var ss = require('../..')
  3
+  , sock = ss.socket('emitter');
  4
+
  5
+sock.connect(3000);
  6
+console.log('emitter client connected');
  7
+
  8
+sock.on('login', function(user){
  9
+  console.log('%s signed in', user.name);
  10
+});
  11
+
  12
+sock.on('logout', function(user){
  13
+  console.log('%s signed out', user.name);
  14
+});
14  examples/emitter/server.js
... ...
@@ -0,0 +1,14 @@
  1
+
  2
+var ss = require('../..')
  3
+  , sock = ss.socket('emitter');
  4
+
  5
+sock.bind(3000);
  6
+console.log('emitter server started');
  7
+
  8
+setInterval(function(){
  9
+  sock.emit('login', { name: 'tobi' });
  10
+}, 300);
  11
+
  12
+setInterval(function(){
  13
+  sock.emit('logout', { name: 'tobi' });
  14
+}, 1500);
11  examples/pubsub/pub.js
... ...
@@ -0,0 +1,11 @@
  1
+
  2
+var ss = require('../..')
  3
+  , sock = ss.socket('pub');
  4
+
  5
+sock.bind(3000);
  6
+console.log('pub server started');
  7
+
  8
+setInterval(function(){
  9
+  console.log('sending');
  10
+  sock.send('hello');
  11
+}, 500);
9  examples/pubsub/sub.js
... ...
@@ -0,0 +1,9 @@
  1
+
  2
+var ss = require('../..')
  3
+  , sock = ss.socket('sub');
  4
+
  5
+sock.connect(3000);
  6
+
  7
+sock.on('message', function(msg){
  8
+  console.log(msg.toString());
  9
+});
9  examples/pushpull/pull.js
... ...
@@ -0,0 +1,9 @@
  1
+
  2
+var ss = require('../..')
  3
+  , sock = ss.socket('pull');
  4
+
  5
+sock.connect(3000);
  6
+
  7
+sock.on('message', function(msg){
  8
+  console.log(msg.toString());
  9
+});
11  examples/pushpull/push.js
... ...
@@ -0,0 +1,11 @@
  1
+
  2
+var ss = require('../..')
  3
+  , sock = ss.socket('push');
  4
+
  5
+sock.bind(3000);
  6
+console.log('push server started');
  7
+
  8
+setInterval(function(){
  9
+  process.stdout.write('.');
  10
+  sock.send('hello');
  11
+}, 150);
2  index.js
... ...
@@ -0,0 +1,2 @@
  1
+
  2
+module.exports = require('./lib');
18  lib/codecs.js
... ...
@@ -0,0 +1,18 @@
  1
+
  2
+/**
  3
+ * Binary.
  4
+ */
  5
+
  6
+exports.none = {
  7
+  encode: function(msg){ return msg },
  8
+  decode: function(msg){ return msg }
  9
+};
  10
+
  11
+/**
  12
+ * JSON.
  13
+ */
  14
+
  15
+exports.json = {
  16
+  encode: JSON.stringify,
  17
+  decode: JSON.parse
  18
+};
78  lib/emitter.js
... ...
@@ -0,0 +1,78 @@
  1
+
  2
+/**
  3
+ * Module dependencies.
  4
+ */
  5
+
  6
+var PubSocket = require('./pub')
  7
+  , SubSocket = require('./sub')
  8
+  , Emitter = require('events').EventEmitter;
  9
+
  10
+/**
  11
+ * Expose `EmitterSocket`.
  12
+ */
  13
+
  14
+module.exports = EmitterSocket;
  15
+
  16
+/**
  17
+ * Initialzie a new `EmitterSocket`.
  18
+ *
  19
+ * @api private
  20
+ */
  21
+
  22
+function EmitterSocket(){}
  23
+
  24
+/**
  25
+ * Inherits from `Emitter.prototype`.
  26
+ */
  27
+
  28
+EmitterSocket.prototype.__proto__ = Emitter.prototype;
  29
+
  30
+/**
  31
+ * Bind as a `PubSocket`.
  32
+ *
  33
+ * @api public
  34
+ */
  35
+
  36
+EmitterSocket.prototype.bind = function(){
  37
+  this.pub = new PubSocket;
  38
+  this.pub.format('json');
  39
+  this.emit = emit;
  40
+  return this.pub.bind.apply(this.pub, arguments);
  41
+};
  42
+
  43
+/**
  44
+ * Connect as a `SubSocket`.
  45
+ *
  46
+ * @api public
  47
+ */
  48
+
  49
+EmitterSocket.prototype.connect = function(){
  50
+  var self = this;
  51
+  this.sub = new SubSocket;
  52
+  this.sub.on('message', function(args){
  53
+    self.emit.apply(self, args);
  54
+  });
  55
+  return this.sub.connect.apply(this.sub, arguments);
  56
+};
  57
+
  58
+/**
  59
+ * Close the pub or sub socket.
  60
+ *
  61
+ * @api public
  62
+ */
  63
+
  64
+EmitterSocket.prototype.close = function(){
  65
+  return (this.sub || this.pub).close();
  66
+};
  67
+
  68
+/**
  69
+ * Emit `event` and the given args to all established peers.
  70
+ *
  71
+ * @param {String} event
  72
+ * @api public
  73
+ */
  74
+
  75
+function emit(event){
  76
+  var args = [].slice.apply(arguments);
  77
+  this.pub.send(args);
  78
+};
53  lib/index.js
... ...
@@ -0,0 +1,53 @@
  1
+
  2
+/**
  3
+ * Library version.
  4
+ */
  5
+
  6
+exports.version = '0.0.1';
  7
+
  8
+/**
  9
+ * Constructors.
  10
+ */
  11
+
  12
+exports.Socket = require('./sock');
  13
+exports.Queue = require('./queue');
  14
+exports.PubSocket = require('./pub');
  15
+exports.SubSocket = require('./sub');
  16
+exports.PushSocket = require('./push');
  17
+exports.PullSocket = require('./pull');
  18
+exports.EmitterSocket = require('./emitter');
  19
+
  20
+/**
  21
+ * Socket types.
  22
+ */
  23
+
  24
+exports.types = {
  25
+  stream: exports.Socket,
  26
+  queue: exports.Queue,
  27
+  pub: exports.PubSocket,
  28
+  sub: exports.SubSocket,
  29
+  push: exports.PushSocket,
  30
+  pull: exports.PullSocket,
  31
+  emitter: exports.EmitterSocket
  32
+};
  33
+
  34
+/**
  35
+ * Codecs.
  36
+ */
  37
+
  38
+exports.codecs = require('./codecs');
  39
+
  40
+/**
  41
+ * Return a new socket of the given `type`.
  42
+ *
  43
+ * @param {String} type
  44
+ * @param {Object} options
  45
+ * @return {Socket}
  46
+ * @api public
  47
+ */
  48
+
  49
+exports.socket = function(type, options){
  50
+  var fn = exports.types[type];
  51
+  if (!fn) throw new Error('invalid socket type "' + type + '"');
  52
+  return new fn(options);
  53
+};
49  lib/pub.js
... ...
@@ -0,0 +1,49 @@
  1
+
  2
+/**
  3
+ * Module dependencies.
  4
+ */
  5
+
  6
+var Queue = require('./queue');
  7
+
  8
+/**
  9
+ * Expose `PubSocket`.
  10
+ */
  11
+
  12
+module.exports = PubSocket;
  13
+
  14
+/**
  15
+ * Initialzie a new `PubSocket`.
  16
+ *
  17
+ * @api private
  18
+ */
  19
+
  20
+function PubSocket() {
  21
+  Queue.call(this);
  22
+  this.filters = [];
  23
+}
  24
+
  25
+/**
  26
+ * Inherits from `Queue.prototype`.
  27
+ */
  28
+
  29
+PubSocket.prototype.__proto__ = Queue.prototype;
  30
+
  31
+/**
  32
+ * Send `msg` to all established peers.
  33
+ *
  34
+ * @param {Mixed} msg
  35
+ * @api public
  36
+ */
  37
+
  38
+PubSocket.prototype.send = function(msg){
  39
+  var socks = this.socks
  40
+    , fmt = this._format
  41
+    , msg = this.pack(this.encode(msg, fmt), fmt)
  42
+    , len = socks.length
  43
+    , sock;
  44
+
  45
+  for (var i = 0; i < len; ++i) {
  46
+    sock = socks[i];
  47
+    sock.write(msg);
  48
+  }
  49
+};
29  lib/pull.js
... ...
@@ -0,0 +1,29 @@
  1
+
  2
+/**
  3
+ * Module dependencies.
  4
+ */
  5
+
  6
+var Queue = require('./queue');
  7
+
  8
+/**
  9
+ * Expose `PullSocket`.
  10
+ */
  11
+
  12
+module.exports = PullSocket;
  13
+
  14
+/**
  15
+ * Initialize a new `PullSocket`.
  16
+ *
  17
+ * @api private
  18
+ */
  19
+
  20
+function PullSocket() {
  21
+  Queue.call(this);
  22
+  // TODO: selective reception
  23
+}
  24
+
  25
+/**
  26
+ * Inherits from `Queue.prototype`.
  27
+ */
  28
+
  29
+PullSocket.prototype.__proto__ = Queue.prototype;
50  lib/push.js
... ...
@@ -0,0 +1,50 @@
  1
+
  2
+/**
  3
+ * Module dependencies.
  4
+ */
  5
+
  6
+var Queue = require('./queue');
  7
+
  8
+/**
  9
+ * Expose `PushSocket`.
  10
+ */
  11
+
  12
+module.exports = PushSocket;
  13
+
  14
+/**
  15
+ * Initialzie a new `PushSocket`.
  16
+ *
  17
+ * @api private
  18
+ */
  19
+
  20
+function PushSocket() {
  21
+  Queue.call(this);
  22
+  this.n = 0;
  23
+  this.on('connect', this.flush.bind(this));
  24
+}
  25
+
  26
+/**
  27
+ * Inherits from `Queue.prototype`.
  28
+ */
  29
+
  30
+PushSocket.prototype.__proto__ = Queue.prototype;
  31
+
  32
+/**
  33
+ * Send `msg` round-robin to established peers.
  34
+ *
  35
+ * @param {Mixed} msg
  36
+ * @api public
  37
+ */
  38
+
  39
+PushSocket.prototype.send = function(msg){
  40
+  var socks = this.socks
  41
+    , len = socks.length
  42
+    , sock = socks[this.n++ % len]
  43
+    , fmt = this._format;
  44
+
  45
+  if (sock) {
  46
+    sock.write(this.pack(this.encode(msg, fmt), fmt));
  47
+  } else {
  48
+    this.buf.push(msg);
  49
+  }
  50
+};
271  lib/queue.js
... ...
@@ -0,0 +1,271 @@
  1
+
  2
+/**
  3
+ * Module dependencies.
  4
+ */
  5
+
  6
+var Socket = require('./sock')
  7
+  , codecs = require('./codecs')
  8
+  , net = require('net');
  9
+
  10
+/**
  11
+ * Expose `Queue`.
  12
+ */
  13
+
  14
+exports = module.exports = Queue;
  15
+
  16
+/**
  17
+ * Format map.
  18
+ */
  19
+
  20
+var format = { ids: {}, names: {} };
  21
+
  22
+/**
  23
+ * Build the map.
  24
+ */
  25
+
  26
+Object.keys(codecs).forEach(function(name, i){
  27
+  format.ids[name] = i;
  28
+  format.names[i] = name;
  29
+});
  30
+
  31
+/**
  32
+ * Initialize a new `Queue`.
  33
+ *
  34
+ * The "Queue" encapsulates message packing & framing,
  35
+ * and applying of codecs for each message received.
  36
+ *
  37
+ * @api private
  38
+ */
  39
+
  40
+function Queue() {
  41
+  Socket.call(this);
  42
+  var self = this;
  43
+  var sock = this.sock;
  44
+  this.socks = [];
  45
+  this.buf = [];
  46
+  this.state = 'meta';
  47
+  this.format('none');
  48
+  sock.setNoDelay();
  49
+  sock.on('data', this.frame.bind(this));
  50
+  sock.on('connect', this.flush.bind(this));
  51
+}
  52
+
  53
+/**
  54
+ * Inherit from `Socket.prototype`.
  55
+ */
  56
+
  57
+Queue.prototype.__proto__ = Socket.prototype;
  58
+
  59
+/**
  60
+ * Set format to `type`.
  61
+ *
  62
+ * @param {String} type
  63
+ * @return {Queue}
  64
+ * @api public
  65
+ */
  66
+
  67
+Queue.prototype.format = function(type){
  68
+  var id = format.ids[type];
  69
+  if (null == id) throw new Error('unknown format "' + type + '"');
  70
+  this._format = id;
  71
+  return this;
  72
+};
  73
+
  74
+/**
  75
+ * Frame the given `chunk`.
  76
+ *
  77
+ * @param {Buffer} chunk
  78
+ * @api private
  79
+ */
  80
+
  81
+Queue.prototype.frame = function(chunk){
  82
+  var i = 0
  83
+    , len = chunk.length;
  84
+
  85
+  while (i < len) {
  86
+    switch (this.state) {
  87
+      case 'meta':
  88
+        this.meta = this.unpack(chunk, i);
  89
+        this.msg = new Buffer(this.meta.length);
  90
+        this.state = 'message';
  91
+        this.offset = 0;
  92
+        i += 4;
  93
+        break;
  94
+      case 'message':
  95
+        var needed = this.meta.length
  96
+          , left = len - i
  97
+          , n = needed > left
  98
+            ? left
  99
+            : needed;
  100
+
  101
+        chunk.copy(this.msg, this.offset, i, i + n);
  102
+        this.offset += n;
  103
+        i += n;
  104
+        if (this.offset == needed) {
  105
+          this.onmessage(this.msg, this.meta);
  106
+          this.state = 'meta';
  107
+        }
  108
+        break;
  109
+    }
  110
+  }
  111
+};
  112
+
  113
+/**
  114
+ * Decode `msg` as `fmt`.
  115
+ *
  116
+ * @param {Buffer} msg
  117
+ * @param {String} fmt
  118
+ * @return {Mixed} decoded message
  119
+ * @api private
  120
+ */
  121
+
  122
+Queue.prototype.decode = function(msg, fmt){
  123
+  var decode = codecs[format.names[fmt]].decode;
  124
+  return decode(msg);
  125
+};
  126
+
  127
+/**
  128
+ * Encode `msg` as `fmt`.
  129
+ *
  130
+ * @param {Buffer} msg
  131
+ * @param {String} fmt
  132
+ * @return {Mixed} encoded message
  133
+ * @api private
  134
+ */
  135
+
  136
+Queue.prototype.encode = function(msg, fmt){
  137
+  var encode = codecs[format.names[fmt]].encode;
  138
+  return encode(msg);
  139
+};
  140
+
  141
+/**
  142
+ * Handle message decoding and emit "message".
  143
+ *
  144
+ * @param {Buffer} msg
  145
+ * @param {Object} meta
  146
+ * @api public
  147
+ */
  148
+
  149
+Queue.prototype.onmessage = function(msg, meta){
  150
+  this.emit('message', this.decode(msg, meta.format));
  151
+};
  152
+
  153
+/**
  154
+ * Pack `msg` as `format`.
  155
+ *
  156
+ * @param {String|Buffer} msg
  157
+ * @param {String} format
  158
+ * @return {Buffer}
  159
+ * @api private
  160
+ */
  161
+
  162
+Queue.prototype.pack = function(msg, format){
  163
+  // TODO: zero-copy
  164
+  if ('string' == typeof msg) msg = new Buffer(msg);
  165
+  var len = msg.length
  166
+    , buf = new Buffer(len + 4);
  167
+
  168
+  // length
  169
+  buf.writeUInt32BE(len, 0);
  170
+
  171
+  // format
  172
+  buf[0] = format;
  173
+
  174
+  // data
  175
+  msg.copy(buf, 4);
  176
+  return buf;
  177
+};
  178
+
  179
+/**
  180
+ * Unpack `msg` at `offset`.
  181
+ *
  182
+ * @param {String|Buffer} msg
  183
+ * @param {Number} offset
  184
+ * @return {Object}
  185
+ * @api private
  186
+ */
  187
+
  188
+Queue.prototype.unpack = function(buf, offset){
  189
+  // format
  190
+  var format = buf[offset];
  191
+
  192
+  // length
  193
+  buf[offset] = 0;
  194
+  var len = buf.readUInt32BE(offset);
  195
+
  196
+  return {
  197
+    length: len,
  198
+    format: format
  199
+  };
  200
+};
  201
+
  202
+/**
  203
+ * Flush queued messages.
  204
+ *
  205
+ * @api private
  206
+ */
  207
+
  208
+Queue.prototype.flush = function(){
  209
+  var buf = this.buf
  210
+    , len = buf.length;
  211
+  this.buf = [];
  212
+  for (var i = 0; i < len; ++i) {
  213
+    this.send(buf[i]);
  214
+  }
  215
+};
  216
+
  217
+// TODO: refactor this stuff...
  218
+
  219
+Queue.prototype.close = function(){
  220
+  this.server && this.server.close();
  221
+  return Socket.prototype.close.call(this);
  222
+};
  223
+
  224
+/**
  225
+ * Bind to `port` and invoke `fn()`.
  226
+ *
  227
+ * Emits:
  228
+ *
  229
+ *  - `connect` when a connection is accepted
  230
+ *  - `bind` when bound and listening
  231
+ *
  232
+ * TODO: host
  233
+ *
  234
+ * @param {Number} port
  235
+ * @param {Function} fn
  236
+ * @api public
  237
+ */
  238
+
  239
+Queue.prototype.bind = function(port, fn){
  240
+  var self = this;
  241
+
  242
+  this.server = net.createServer(function(sock){
  243
+    self.socks.push(sock);
  244
+
  245
+    self.emit('connect', sock);
  246
+
  247
+    sock.on('close', function(){
  248
+      self.socks.forEach(function(s, i){
  249
+        if (s == sock) self.socks.splice(i, 1);
  250
+      })
  251
+    });
  252
+
  253
+    sock.on('data', function(chunk){
  254
+      for (var i = o = 0, len = chunk.length; i < len; ++i) {
  255
+        if (0 == chunk[i]) {
  256
+          // TODO: this could be half a message...
  257
+          // TODO: dont append nul... use lengths
  258
+          // TODO: add Buffer support
  259
+          self.onmessage(chunk.slice(o, i));
  260
+          o = i + 1;
  261
+        }
  262
+      }
  263
+    });
  264
+  });
  265
+
  266
+  this.server.on('listening', function(){
  267
+    self.emit('bind');
  268
+  });
  269
+
  270
+  this.server.listen(port, fn);
  271
+};
95  lib/sock.js
... ...
@@ -0,0 +1,95 @@
  1
+
  2
+/**
  3
+ * Module dependencies.
  4
+ */
  5
+
  6
+var net = require('net')
  7
+  , Emitter = require('events').EventEmitter;
  8
+
  9
+/**
  10
+ * Expose `Socket`.
  11
+ */
  12
+
  13
+module.exports = Socket;
  14
+
  15
+/**
  16
+ * Initialize a new `Socket`.
  17
+ *
  18
+ * A super socket "Socket" encapsulates the
  19
+ * reconnection logic with exponential backoff,
  20
+ * serving as a base for the `Queue`.
  21
+ *
  22
+ * @api private
  23
+ */
  24
+
  25
+function Socket() {
  26
+  var self = this;
  27
+  var sock = this.sock = new net.Socket;
  28
+
  29
+  this.retryTimeout = this.retry = 100;
  30
+  this.retryMaxTimeout = 2000;
  31
+  
  32
+  sock.on('error', function(err){
  33
+    if ('ECONNREFUSED' != err.code) {
  34
+      self.emit('error', err);
  35
+    }
  36
+  });
  37
+
  38
+  sock.on('data', function(chunk){
  39
+    self.emit('data', chunk);
  40
+  });
  41
+
  42
+  sock.on('close', function(){
  43
+    self.connected = false;
  44
+    if (self.closing) return self.emit('close');
  45
+    setTimeout(function(){
  46
+      self.emit('reconnect attempt');
  47
+      sock.destroy();
  48
+      self.connect(self.port);
  49
+      self.retry = Math.min(self.retryMaxTimeout, self.retry * 1.5);
  50
+    }, self.retry);
  51
+  });
  52
+
  53
+  sock.on('connect', function(){
  54
+    self.connected = true;
  55
+    self.retry = self.retryTimeout;
  56
+    self.emit('connect'); // TODO: dont emit each time... will invoke callback too many times
  57
+    self.callback && self.callback();
  58
+  });
  59
+}
  60
+
  61
+/**
  62
+ * Inherit from `Emitter.prototype`.
  63
+ */
  64
+
  65
+Socket.prototype.__proto__ = Emitter.prototype;
  66
+
  67
+/**
  68
+ * Connect to `port` and invoke `fn()`.
  69
+ *
  70
+ * TODO: host
  71
+ *
  72
+ * @param {Number} port
  73
+ * @param {Function} fn
  74
+ * @api public
  75
+ */
  76
+
  77
+Socket.prototype.connect = function(port, fn){
  78
+  this.port = port;
  79
+  this.sock.connect(port, '127.0.0.1');
  80
+  this.callback = fn;
  81
+  return this;
  82
+};
  83
+
  84
+/**
  85
+ * Close the socket.
  86
+ *
  87
+ * @api public
  88
+ */
  89
+
  90
+Socket.prototype.close = function(){
  91
+  this.closing = true;
  92
+  // TODO: end()?
  93
+  this.sock.destroy();
  94
+  return this;
  95
+};
29  lib/sub.js
... ...
@@ -0,0 +1,29 @@
  1
+
  2
+/**
  3
+ * Module dependencies.
  4
+ */
  5
+
  6
+var Queue = require('./queue');
  7
+
  8
+/**
  9
+ * Expose `SubSocket`.
  10
+ */
  11
+
  12
+module.exports = SubSocket;
  13
+
  14
+/**
  15
+ * Initialize a new `SubSocket`.
  16
+ *
  17
+ * @api private
  18
+ */
  19
+
  20
+function SubSocket() {
  21
+  Queue.call(this);
  22
+  this.subscriptions = [];
  23
+}
  24
+
  25
+/**
  26
+ * Inherits from `Queue.prototype`.
  27
+ */
  28
+
  29
+SubSocket.prototype.__proto__ = Queue.prototype;
11  package.json
... ...
@@ -0,0 +1,11 @@
  1
+{
  2
+  "name": "super-sockets",
  3
+  "description": "High-level messaging & socket patterns implemented in pure js",
  4
+  "version": "0.0.1",
  5
+  "author": "TJ Holowaychuk <tj@vision-media.ca>",
  6
+  "devDependencies": {
  7
+    "should": "*",
  8
+    "mocha": "*"
  9
+  },
  10
+  "keywords": ["zmq", "zeromq", "pubsub", "socket"]
  11
+}
9  test/run
... ...
@@ -0,0 +1,9 @@
  1
+#!/usr/bin/env bash
  2
+
  3
+echo
  4
+for file in $@; do
  5
+  printf "\033[90m   ${file#test/}\033[0m "
  6
+  node $file && printf "\033[36m✓\033[0m\n"
  7
+  test $? -eq 0 || exit $?
  8
+done
  9
+echo
34  test/test.emitter.js
... ...
@@ -0,0 +1,34 @@
  1
+
  2
+var ss = require('../')
  3
+  , should = require('should');
  4
+
  5
+var pub = ss.socket('emitter')
  6
+  , sub = ss.socket('emitter');
  7
+
  8
+var msgs = [];
  9
+
  10
+// test basic 1-1 pub/sub emitter style
  11
+
  12
+pub.bind(3000, function(){
  13
+  sub.connect(3000, function(){
  14
+    sub.on('foo', function(){
  15
+      msgs.push(['foo']);
  16
+    });
  17
+    
  18
+    sub.on('bar', function(a, b, c){
  19
+      msgs.push(['bar', a, b, c]);
  20
+    });
  21
+    
  22
+    sub.on('baz', function(a){
  23
+      msgs.push(['baz', a]);
  24
+      pub.close();
  25
+      sub.close();
  26
+    });
  27
+
  28
+    setTimeout(function(){
  29
+      pub.emit('foo');
  30
+      pub.emit('bar', 1, 2, 3);
  31
+      pub.emit('baz', { name: 'tobi' });
  32
+    }, 20);
  33
+  });
  34
+});
45  test/test.pubsub.js
... ...
@@ -0,0 +1,45 @@
  1
+
  2
+var ss = require('../')
  3
+  , should = require('should');
  4
+
  5
+var pub = ss.socket('pub')
  6
+  , sub = ss.socket('sub');
  7
+
  8
+var n = 0
  9
+  , closed;
  10
+
  11
+// test basic 1-1 pub/sub
  12
+
  13
+pub.bind(3000, function(){
  14
+  sub.connect(3000, function(){
  15
+    sub.on('message', function(msg){
  16
+      msg.should.be.an.instanceof(Buffer);
  17
+      msg.should.have.length(3);
  18
+      msg = msg.toString();
  19
+      switch (n++) {
  20
+        case 0:
  21
+          msg.should.equal('foo');
  22
+          break;
  23
+        case 1:
  24
+          msg.should.equal('bar');
  25
+          break;
  26
+        case 2:
  27
+          msg.should.equal('baz');
  28
+          pub.close();
  29
+          sub.close();
  30
+          closed = true;
  31
+          break;
  32
+      }
  33
+    });
  34
+
  35
+    setTimeout(function(){
  36
+      pub.send('foo');
  37
+      pub.send('bar');
  38
+      pub.send('baz');
  39
+    }, 20);
  40
+  });
  41
+});
  42
+
  43
+process.on('exit', function(){
  44
+  should.equal(true, closed);
  45
+});
38  test/test.pubsub.missed-messages.js
... ...
@@ -0,0 +1,38 @@
  1
+
  2
+var ss = require('../')
  3
+  , should = require('should');
  4
+
  5
+var pub = ss.socket('pub')
  6
+  , sub = ss.socket('sub');
  7
+
  8
+var n = 0;
  9
+
  10
+// test basic 1-1 pub/sub with missed messages
  11
+
  12
+pub.bind(3000, function(){
  13
+  pub.send('foo');
  14
+  pub.send('bar');
  15
+  sub.connect(3000, function(){
  16
+    sub.on('message', function(msg){
  17
+      msg.should.be.an.instanceof(Buffer);
  18
+      msg.should.have.length(3);
  19
+      msg = msg.toString();
  20
+      switch (n++) {
  21
+        case 0:
  22
+          msg.should.equal('baz');
  23
+          break;
  24
+        case 1:
  25
+          msg.should.equal('raz');
  26
+          pub.close();
  27
+          sub.close();
  28
+          break;
  29
+      }
  30
+    });
  31
+
  32
+    setTimeout(function(){
  33
+      pub.send('baz');
  34
+      pub.send('raz');
  35
+    }, 20);
  36
+  });
  37
+});
  38
+
57  test/test.pubsub.multiple-subscribers.js
... ...
@@ -0,0 +1,57 @@
  1
+
  2
+var ss = require('../')
  3
+  , should = require('should');
  4
+
  5
+var pub = ss.socket('pub')
  6
+  , a = ss.socket('sub')
  7
+  , b = ss.socket('sub')
  8
+  , c = ss.socket('sub');
  9
+
  10
+var n = 9;
  11
+
  12
+var messages = {
  13
+    a: []
  14
+  , b: []
  15
+  , c: []
  16
+};
  17
+
  18
+// test basic 1-M pub/sub
  19
+
  20
+pub.bind(3000, function(){
  21
+  a.connect(3000, function(){
  22
+    b.connect(3000, function(){
  23
+      c.connect(3000, function(){
  24
+        setTimeout(function(){
  25
+          pub.send('foo');
  26
+          pub.send('bar');
  27
+          pub.send('baz');
  28
+        }, 20);
  29
+      });
  30
+    });
  31
+  });
  32
+});
  33
+
  34
+a.on('message', function(msg){
  35
+  messages.a.push(msg.toString());
  36
+  --n || done();
  37
+});
  38
+
  39
+b.on('message', function(msg){
  40
+  messages.b.push(msg.toString());
  41
+  --n || done();
  42
+});
  43
+
  44
+c.on('message', function(msg){
  45
+  messages.c.push(msg.toString());
  46
+  --n || done();
  47
+});
  48
+
  49
+function done() {
  50
+  messages.a.should.eql(['foo', 'bar', 'baz']);
  51
+  messages.b.should.eql(['foo', 'bar', 'baz']);
  52
+  messages.c.should.eql(['foo', 'bar', 'baz']);
  53
+  pub.close();
  54
+  a.close();
  55
+  b.close();
  56
+  c.close();
  57
+}
44  test/test.pushpull.js
... ...
@@ -0,0 +1,44 @@
  1
+
  2
+var ss = require('../')
  3
+  , should = require('should');
  4
+
  5
+var push = ss.socket('push')
  6
+  , pull = ss.socket('pull');
  7
+
  8
+// basic 1-1 push/pull