├── .gitignore ├── History.md ├── Makefile ├── Readme.md ├── benchmarks ├── pub.js └── sub.js ├── examples ├── pub.js ├── single.js └── sub.js ├── index.js ├── lib ├── client.js └── server.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2.3.0 / 2014-07-21 2 | ================== 3 | 4 | * add "parse-error" event 5 | 6 | 2.2.0 / 2014-06-13 7 | ================== 8 | 9 | * add .send() callback support 10 | 11 | 2.1.0 / 2014-02-26 12 | ================== 13 | 14 | * add "bind" event and .address. Closes #4 15 | 16 | 2.0.0 / 2014-01-08 17 | ================== 18 | 19 | * refactor with AMP to allow variable argument support 20 | 21 | 1.0.1 / 2013-08-23 22 | ================== 23 | 24 | * fix deps, better-assert is not one 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @./node_modules/.bin/mocha \ 4 | --bail 5 | 6 | .PHONY: test 7 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Punt 3 | 4 | A small layer on top of node's core __UDP__ module to make fast volatile messaging even simpler. 5 | Punt uses the tiny [AMP](https://github.com/visionmedia/node-amp) prototol to serialize buffer, string, 6 | and json arguments. 7 | 8 | ## Installation 9 | 10 | ``` 11 | $ npm install punt 12 | ``` 13 | 14 | ## Example 15 | 16 | A small in-proc example of a server with three clients: 17 | 18 | ```js 19 | var punt = require('punt'); 20 | var server = punt.bind('0.0.0.0:5000'); 21 | var a = punt.connect('0.0.0.0:5000'); 22 | var b = punt.connect('0.0.0.0:5000'); 23 | var c = punt.connect('0.0.0.0:5000'); 24 | 25 | server.on('message', function(msg){ 26 | console.log(msg); 27 | }); 28 | 29 | setInterval(function(){ 30 | a.send({ hello: 'world' }); 31 | }, 150); 32 | 33 | setInterval(function(){ 34 | b.send('hello world'); 35 | }, 150); 36 | 37 | setInterval(function(){ 38 | c.send(Buffer.from('hello')); 39 | }, 150); 40 | ``` 41 | 42 | yielding: 43 | 44 | ``` 45 | 46 | hello world 47 | { hello: 'world' } 48 | 49 | hello world 50 | { hello: 'world' } 51 | ... 52 | ``` 53 | ## API 54 | 55 | ### Server(addr) 56 | 57 | Bind to the given `addr`. 58 | 59 | ### Client(addr) 60 | 61 | Connect to the given `addr`. 62 | 63 | ### Client#send(...) 64 | 65 | Send one or more arguments a single atomic message. The following 66 | types are supported through AMP: 67 | 68 | - strings 69 | - buffers 70 | - objects (serialized as JSON) 71 | 72 | ## License 73 | 74 | MIT 75 | -------------------------------------------------------------------------------- /benchmarks/pub.js: -------------------------------------------------------------------------------- 1 | 2 | var punt = require('..'); 3 | 4 | var c = punt.connect('0.0.0.0:5050'); 5 | 6 | var b = Buffer.from(Array(256).join('a')); 7 | function next() { 8 | var n = 100; 9 | while (n--) c.send(b); 10 | setImmediate(next); 11 | } 12 | 13 | next(); 14 | -------------------------------------------------------------------------------- /benchmarks/sub.js: -------------------------------------------------------------------------------- 1 | 2 | var punt = require('..'); 3 | var program = require('commander'); 4 | var humanize = require('humanize-number'); 5 | 6 | program 7 | .option('-T, --type ', 'socket type [sub]', 'sub') 8 | .option('-s, --size ', 'message size in bytes [256]', parseInt) 9 | .option('-d, --duration ', 'duration of test [5000]', parseInt) 10 | .parse(process.argv) 11 | 12 | var s = punt.bind('0.0.0.0:5050'); 13 | 14 | var n = 0; 15 | var ops = 5000; 16 | var results = []; 17 | var prev = start = Date.now(); 18 | var bytes = program.size || 256; 19 | 20 | s.on('message', function(){ 21 | if (n++ % ops == 0) { 22 | var ms = Date.now() - prev; 23 | var sec = ms / 1000; 24 | var persec = ops / sec | 0; 25 | results.push(persec); 26 | process.stdout.write('\r [' + persec + ' ops/s] [' + n + ']'); 27 | prev = Date.now(); 28 | } 29 | }); 30 | 31 | console.log(); 32 | 33 | function sum(arr) { 34 | return arr.reduce(function(sum, n){ 35 | return sum + n; 36 | }); 37 | } 38 | 39 | function min(arr) { 40 | return arr.reduce(function(min, n){ 41 | return n < min 42 | ? n 43 | : min; 44 | }); 45 | } 46 | 47 | function median(arr) { 48 | arr = arr.sort(); 49 | return arr[arr.length / 2 | 0]; 50 | } 51 | 52 | function done(){ 53 | var ms = Date.now() - start; 54 | var avg = n / (ms / 1000); 55 | console.log('\n'); 56 | console.log(' min: %s ops/s', humanize(min(results))); 57 | console.log(' mean: %s ops/s', humanize(avg | 0)); 58 | console.log(' median: %s ops/s', humanize(median(results))); 59 | console.log(' total: %s ops in %ds', humanize(n), ms / 1000); 60 | console.log(' through: %d mb/s', ((avg * bytes) / 1024 / 1024).toFixed(2)); 61 | console.log(); 62 | process.exit(); 63 | } 64 | 65 | process.on('SIGINT', done); 66 | setTimeout(done, program.duration || 5000); 67 | -------------------------------------------------------------------------------- /examples/pub.js: -------------------------------------------------------------------------------- 1 | 2 | var punt = require('..'); 3 | var sock = punt.connect('0.0.0.0:5000'); 4 | 5 | setInterval(function(){ 6 | sock.send('foo', 'bar'); 7 | }, 10); 8 | -------------------------------------------------------------------------------- /examples/single.js: -------------------------------------------------------------------------------- 1 | 2 | var punt = require('..'); 3 | var server = punt.bind('0.0.0.0:5000'); 4 | var a = punt.connect('0.0.0.0:5000'); 5 | var b = punt.connect('0.0.0.0:5000'); 6 | var c = punt.connect('0.0.0.0:5000'); 7 | 8 | server.on('message', function(msg){ 9 | console.log(msg); 10 | }); 11 | 12 | setInterval(function(){ 13 | a.send({ hello: 'world' }); 14 | }, 150); 15 | 16 | setInterval(function(){ 17 | b.send('hello world'); 18 | }, 150); 19 | 20 | setInterval(function(){ 21 | c.send(Buffer.from('hello world')); 22 | }, 150); 23 | 24 | setInterval(function(){ 25 | c.send(Buffer.from('hello world with fn'), function(err, bytes) { 26 | console.log('Sent %s bytes', bytes); 27 | }); 28 | }, 150); 29 | -------------------------------------------------------------------------------- /examples/sub.js: -------------------------------------------------------------------------------- 1 | 2 | var punt = require('..'); 3 | var sock = punt.bind('0.0.0.0:5000'); 4 | 5 | sock.on('message', function(a, b){ 6 | console.log(a, b); 7 | }); 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | exports.bind = require('./lib/server'); 3 | exports.connect = require('./lib/client'); 4 | -------------------------------------------------------------------------------- /lib/client.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var Message = require('amp-message'); 7 | var dgram = require('dgram'); 8 | var url = require('url'); 9 | 10 | /** 11 | * Expose `Client`. 12 | */ 13 | 14 | module.exports = Client; 15 | 16 | /** 17 | * Initialize a new `Client` with `addr`. 18 | * 19 | * @param {String} addr 20 | * @api public 21 | */ 22 | 23 | function Client(addr) { 24 | if (!(this instanceof Client)) return new Client(addr); 25 | this.addr = url.parse('udp://' + addr); 26 | this.sock = dgram.createSocket('udp4'); 27 | } 28 | 29 | /** 30 | * Send `msg`. 31 | * 32 | * @param {Buffer|String|Object} msg... 33 | * @callback Optional 34 | * @api public 35 | */ 36 | 37 | Client.prototype.send = function() { 38 | var args = [].slice.call(arguments); 39 | var fn = typeof args[args.length - 1] == 'function' ? args.pop() : null; 40 | 41 | var msg = new Message(args); 42 | var buf = msg.toBuffer(); 43 | 44 | this.sock.send(buf, 0, buf.length, this.addr.port, this.addr.hostname, fn); 45 | }; -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var Emitter = require('events').EventEmitter; 7 | var Message = require('amp-message'); 8 | var dgram = require('dgram'); 9 | var url = require('url'); 10 | 11 | /** 12 | * Expose `Server`. 13 | */ 14 | 15 | module.exports = Server; 16 | 17 | /** 18 | * Initialize a `Server` with the given `addr`. 19 | * 20 | * @param {String} addr 21 | * @api public 22 | */ 23 | 24 | function Server(addr) { 25 | if (!(this instanceof Server)) return new Server(addr).bind(); 26 | this.addr = url.parse('udp://' + addr); 27 | this.onmessage = this.onmessage.bind(this); 28 | this.sock = dgram.createSocket('udp4'); 29 | this.info = null; 30 | } 31 | 32 | /** 33 | * Inherit from `Emitter.prototype`. 34 | */ 35 | 36 | Server.prototype.__proto__ = Emitter.prototype; 37 | 38 | /** 39 | * Bind. 40 | * 41 | * @api private 42 | */ 43 | 44 | Server.prototype.bind = function(){ 45 | var self = this; 46 | var sock = this.sock; 47 | sock.bind(this.addr.port, this.addr.hostname); 48 | sock.on('message', this.onmessage); 49 | sock.on('listening', function(){ 50 | self.address = sock.address(); 51 | self.emit('bind'); 52 | }); 53 | return this; 54 | }; 55 | 56 | /** 57 | * Handle messages. 58 | * 59 | * @api private 60 | */ 61 | 62 | Server.prototype.onmessage = function(buf, rinfo){ 63 | var msg; 64 | try { 65 | msg = new Message(buf); 66 | } catch (err) { 67 | this.emit('parse-error', err, buf); 68 | return; 69 | } 70 | msg.args.unshift('message'); 71 | this.info = rinfo; 72 | this.emit.apply(this, msg.args); 73 | }; 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "punt", 3 | "version": "2.3.0", 4 | "description": "tiny / elegant UDP messaging library", 5 | "keywords": [ 6 | "message", 7 | "messaging", 8 | "udp" 9 | ], 10 | "devDependencies": { 11 | "better-assert": "~1.0.0", 12 | "commander": "~2.8.1", 13 | "humanize-number": "0.0.2", 14 | "mocha": "~2.2.5" 15 | }, 16 | "dependencies": { 17 | "amp-message": "~0.1.1" 18 | }, 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/visionmedia/punt.git" 23 | }, 24 | "scripts": {} 25 | } 26 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('better-assert'); 3 | var punt = require('..'); 4 | 5 | var s = punt.bind('0.0.0.0:5000'); 6 | var c = punt.connect('0.0.0.0:5000'); 7 | 8 | describe('.bind()', function(){ 9 | it('should support ephemeral ports', function(done){ 10 | var s = punt.bind('0.0.0.0:0'); 11 | 12 | s.on('bind', function(){ 13 | assert(s.address, '.address missing'); 14 | assert(s.address.address); 15 | assert(s.address.port); 16 | done(); 17 | }); 18 | }) 19 | }) 20 | 21 | describe('.send(buffer)', function(){ 22 | it('should transfer the message', function(done){ 23 | s.once('message', function(msg){ 24 | assert('Buffer' == msg.constructor.name); 25 | assert('Hello' == msg.toString()); 26 | assert(10 == this.info.size); 27 | done(); 28 | }); 29 | 30 | c.send(Buffer.from('Hello')); 31 | }) 32 | 33 | it('should gracefully fail message parsing', function(done){ 34 | function fail () { 35 | done(new Error('Should not emit messages on parse failure')); 36 | } 37 | s.once('message', fail); 38 | 39 | s.once('parse-error', function(err, buf){ 40 | s.removeListener('message', fail); 41 | assert(err instanceof Error); 42 | assert('Buffer' == buf.constructor.name); 43 | assert('fail' == buf.toString()); 44 | done(); 45 | }); 46 | 47 | s.onmessage(Buffer.from('fail')); 48 | }) 49 | }) 50 | 51 | describe('.send(buffer, callback)', function(){ 52 | it('should invoke the callback', function(done){ 53 | c.send(Buffer.from('Hello'), Buffer.from('World'), function(err, bytes) { 54 | assert(!err); 55 | done(); 56 | }); 57 | }) 58 | }) 59 | 60 | describe('.send(string)', function(){ 61 | it('should convert to a buffer', function(done){ 62 | s.once('message', function(msg){ 63 | assert('string' == typeof msg); 64 | assert('Hello' == msg); 65 | done(); 66 | }); 67 | 68 | c.send('Hello'); 69 | }) 70 | }) 71 | 72 | describe('.send(object)', function(){ 73 | it('should json stringify and convert to a buffer', function(done){ 74 | s.once('message', function(msg){ 75 | assert('object' == typeof msg); 76 | assert('world' == msg.hello); 77 | done(); 78 | }); 79 | 80 | c.send({ hello: 'world' }); 81 | }) 82 | }) 83 | --------------------------------------------------------------------------------