├── .gitignore ├── .npmignore ├── package.json ├── LICENSE.md ├── tests.js ├── README.md └── chord.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chord", 3 | "version": "0.1.2", 4 | "description": "Chord Distributed Hash Table", 5 | "licenses": [ 6 | { 7 | "name": "MIT", 8 | "url": "http://opensource.org/licenses/mit-license.php" 9 | } 10 | ], 11 | "author": "Optimizely, Inc.", 12 | "contributors": [ 13 | { 14 | "name": "Brian Ollenberger", 15 | "email": "brian@optimizely.com", 16 | "web": "https://www.github.com/bollenberger" 17 | } 18 | ], 19 | "main": "chord.js", 20 | "scripts": { 21 | "test": "vows tests.js" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/optimizely/chord.git" 26 | }, 27 | "dependencies": { 28 | "murmurhash3": "0.2.x", 29 | "uuid": "1.4.x" 30 | }, 31 | "devDependencies": { 32 | "vows": "0.7.x" 33 | }, 34 | "engines": { 35 | "node": ">= 0.10.x" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright (c) 2013 Optimizely, Inc. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /tests.js: -------------------------------------------------------------------------------- 1 | var chord = require('./chord'); 2 | var vows = require('vows'); 3 | var assert = require('assert'); 4 | 5 | vows.describe('Chord').addBatch({ 6 | 'Adding an exponential key': { 7 | 'should add two small values': function () { 8 | assert.isTrue(chord.equal_to(chord.add_exp([0,0,0,1], 0), [0,0,0,2])); 9 | }, 10 | 11 | 'should add a value smaller than 32 bits': function () { 12 | assert.isTrue(chord.equal_to(chord.add_exp([0,0,0,0], 12), [0,0,0,4096])); 13 | }, 14 | 15 | 'should add a value larger than 32 bits': function () { 16 | assert.isTrue(chord.equal_to(chord.add_exp([0,0,0,0], 44), [0,0,4096,0])); 17 | }, 18 | 19 | 'should carry additions across words': function () { 20 | assert.isTrue(chord.equal_to(chord.add_exp([2, 0xffffffff], 0), [3, 0])); 21 | }, 22 | 23 | 'should carry and wrap': function () { 24 | assert.isTrue(chord.equal_to(chord.add_exp([0xffffffff, 0xffffffff, 0xffffffff], 0), [0,0,0])) 25 | } 26 | }, 27 | 28 | 'Comparing keys': { 29 | 'should find equal things equal': function () { 30 | assert.isTrue(chord.equal_to([1,2,3,4], [1,2,3,4])); 31 | }, 32 | 33 | 'equality should find differences in least significant word': function () { 34 | assert.isFalse(chord.equal_to([1,2,3,4], [1,2,3,5])); 35 | }, 36 | 37 | 'equality should find differences in most significant word': function () { 38 | assert.isFalse(chord.equal_to([1,2,3,4], [5,2,3,4])); 39 | }, 40 | 41 | 'should compare equal things as less than or equal': function () { 42 | assert.isTrue(chord.less_than_or_equal([1,2,3,4], [1,2,3,4])); 43 | }, 44 | 45 | 'should compare lesser things as less than or equal': function () { 46 | assert.isTrue(chord.less_than_or_equal([1,2,3,3], [1,2,3,4])); 47 | }, 48 | 49 | 'should not compare greater things as less than or equal': function () { 50 | assert.isFalse(chord.less_than_or_equal([1,2,3,5], [1,2,3,4])); 51 | }, 52 | 53 | 'should not compare equal things as less than': function () { 54 | assert.isFalse(chord.less_than([1,2,3,4], [1,2,3,4])); 55 | }, 56 | 57 | 'should compare lesser things as less than': function () { 58 | assert.isTrue(chord.less_than([1,2,3,3], [1,2,3,4])); 59 | }, 60 | 61 | 'should not compare greater things as less than': function () { 62 | assert.isFalse(chord.less_than([1,2,3,5], [1,2,3,4])); 63 | }, 64 | 65 | 'most significant word should take precedence over least for less than': function () { 66 | assert.isTrue(chord.less_than([1,2,3,4], [2,2,3,3])); 67 | }, 68 | 69 | 'specific case that broke for real': function () { 70 | assert.isTrue(chord.less_than([1195588147,3697448847,138059749,162608140], 71 | [1456031017,292686529,2153452302,2944297828])); 72 | assert.isFalse(chord.less_than([1456031017,292686529,2153452302,2944297828], 73 | [1195588147,3697448847,138059749,162608140])); 74 | } 75 | }, 76 | 77 | 'Testing key ranges': { 78 | 'key is in range': function () { 79 | assert.isTrue(chord.in_range([2], [1], [3])); 80 | assert.isTrue(chord.in_half_open_range([2], [1], [3])); 81 | }, 82 | 83 | 'key is not in range': function () { 84 | assert.isFalse(chord.in_range([4], [1], [3])); 85 | assert.isFalse(chord.in_half_open_range([4], [1], [3])); 86 | }, 87 | 88 | 'key is in reversed range': function () { 89 | assert.isTrue(chord.in_range([4], [3], [1])); 90 | assert.isTrue(chord.in_half_open_range([4], [3], [1])); 91 | }, 92 | 93 | 'key is not in reversed range': function () { 94 | assert.isFalse(chord.in_range([2], [3], [1])); 95 | assert.isFalse(chord.in_half_open_range([2], [3], [1])); 96 | }, 97 | 98 | 'key is not in empty range': function () { 99 | assert.isFalse(chord.in_range([1], [1], [1])); 100 | }, 101 | 102 | 'key is in empty range': function () { 103 | assert.isTrue(chord.in_range([2], [1], [1])); 104 | }, 105 | 106 | 'key is in half open empty range': function () { 107 | assert.isTrue(chord.in_half_open_range([1], [1], [1])); 108 | assert.isTrue(chord.in_half_open_range([2], [1], [1])); 109 | }, 110 | 111 | 'key on lower boundary is not in range': function () { 112 | assert.isFalse(chord.in_range([1], [1], [3])); 113 | assert.isFalse(chord.in_half_open_range([1], [1], [3])); 114 | }, 115 | 116 | 'key on upper boundary is not in range': function () { 117 | assert.isFalse(chord.in_range([3], [1], [3])); 118 | }, 119 | 120 | 'key on upper boundary is in half open range': function () { 121 | assert.isTrue(chord.in_half_open_range([3], [1], [3])); 122 | }, 123 | 124 | 'specific case that broke for real': function () { 125 | assert.isFalse(chord.in_range([1456031017,292686529,2153452302,2944297828], 126 | [1195588147,3697448847,138059749,162608140], 127 | [1456031017,292686529,2153452302,2944297828])); 128 | } 129 | } 130 | }).export(module); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Chord Distributed Hash Table 2 | ============================ 3 | 4 | [Chord](http://pdos.csail.mit.edu/papers/chord:sigcomm01/chord_sigcomm.pdf) 5 | is a self-organizing distributed hash table. This is an implementation of the Chord 6 | algorithm in Node.js. It provides the ability to construct a Chord cluster and to 7 | route application layer messages to a process responsible for a range of keys. 8 | 9 | It supports virtual nodes and uses UDP as its out-of-process transport layer. 10 | 11 | API 12 | --- 13 | 14 | ### chord.hash(string) 15 | 16 | Occasionally, you may wish to hash a value the same way that Chord does. It currently 17 | uses Murmurhash3-128, but `hash` will always expose the current hash algorithm. 18 | 19 | ### chord.Chord(listen_port, virtual_node_count, node_to_join, on_message) 20 | ### chord.Chord(listen_port, virtual_node_count, on_message) 21 | 22 | The primary entry point for starting a Chord server. The available arguments are: 23 | 24 | * `listen_port`: The UDP port on which to listen. 25 | * `virtual_node_count`: The number of virtual nodes to start. 26 | * `node_to_join`: The address of a node to join (optional). 27 | * `on_message`: A callback function that is notified when a message addressed to a 28 | local virtual node is received. 29 | 30 | The returned value is a `send_message` function. The `send_message` function also has a 31 | `close` property, which is a function to shut down the local virtual nodes. 32 | 33 | var server = chord.Chord(...); // start the server 34 | server(...) // send a message (see below) 35 | server.close() // stop the server 36 | 37 | ### send_message(to, id, message, reply_to) 38 | 39 | This is the function for sending a new application level message to another node in the 40 | Chord ring. It takes the following parameters: 41 | 42 | * `to`: Optional (must be null if not used). The address of the node to which to send. 43 | * `id`: The key of the DHT to which to address the message. The node which is currently 44 | responsible for that point in the hash ring will receive the message. 45 | * `message`: A JSON-able object, which will be send to the recipient. 46 | * `reply_to`: Optional. The address of the node for the recipient to reply to. This may be 47 | used as a way to share the identity of a node with another node. If the recipient replies, 48 | their message will be sent to the `reply_to` node. If no `reply_to` is specified, replies 49 | return to the original sender. 50 | 51 | ### chord.Client(on_message, listen_port) 52 | 53 | The client is intended to provide an easy way to have non-members of the Chord ring 54 | communicate with members of the Chord ring. This is useful if, for example, members of the 55 | Chord ring provide a data storage service, and clients of this data storage service need 56 | to make requests of it without themselves storing data. 57 | 58 | A client is really just a completely local Chord ring that never joins any other node. As such, 59 | it has all of the machinery necessary to speak the Chord wire protocol to another Chord ring. 60 | 61 | * `on_message`: The callback that is notified when a message is received. 62 | * `listen_port`: The port on which to listen for reply messages. 63 | 64 | ### on_message(from, id, message, reply) 65 | 66 | Whenever a client or server receives a message, its `on_message` callback is triggered. The callback 67 | receives a few parameters: 68 | 69 | * `from`: The address of the sender. There is a `from.id` property, which is the point in the hash 70 | ring of the sender's identity. 71 | * `id`: The key to which the message was sent. 72 | * `message`: The application layer message that was received. 73 | * `reply`: A callback for sending a reply message (see below). 74 | 75 | ### reply(message, to, reply_to) 76 | 77 | Send a message to source (or `reply_to`) of a received message. 78 | 79 | * `message`: The message to send back. 80 | * `to`: Optional. The address of the node to which to send the reply. 81 | * `reply_to`: Optional. The address to which the recipient will reply. If not specified, 82 | any reply will return to the originator of the message. One useful pattern is to 83 | pass `from` as the `reply_to`, which will cause the next reply to also go to the originator 84 | of the request. This can permit multi-stage operations without having to route the final reply 85 | through intermediate nodes. 86 | 87 | Setting up a cluster 88 | -------------------- 89 | When you set up a cluster, you will first create an initial node. The only thing that 90 | distinguishes your initial node from any other node is that it does not join any other 91 | node. Subsequent nodes join any existing node. The Chord protocol will distribute the 92 | existence of new nodes around the ring automatically. 93 | 94 | To create a new node: 95 | 96 | var chord = require('chord'); 97 | chord.Chord(1234, // Listen on port 1234 98 | 10, // Start 10 virtual nodes 99 | on_message); // Call the function 'on_message' when we receive something 100 | 101 | To connect a subsequent node: 102 | 103 | chord.Chord(1235, 104 | 10, 105 | {address:'127.0.0.1', port:1234}, // Connect to the existing server on port 1234 106 | on_message_2); // Provide a callback for receiving. 107 | 108 | All 20 virtual nodes will talk amongst themselves to arrange themselves in a ring. Subsequent 109 | messages will be delivered the node that owns a particular key at that time. Note that due to 110 | nodes leaving an joining, the node that owns a particular key may change over time. Your application 111 | should be designed to expect this. 112 | -------------------------------------------------------------------------------- /chord.js: -------------------------------------------------------------------------------- 1 | // TODO: Implement successor lists for increased fault tolerance. 2 | 3 | var net = require('net'), 4 | dgram = require('dgram'), 5 | uuid = require('uuid'), 6 | murmur = require('murmurhash3'); 7 | 8 | var hash = exports.hash = murmur.murmur128Sync; 9 | 10 | var serialize = function serialize(message) { 11 | return new Buffer(JSON.stringify(message)); 12 | }; 13 | var deserialize = JSON.parse; 14 | 15 | // Is key in (low, high) 16 | function in_range(key, low, high) { 17 | //return (low < high && key > low && key < high) || 18 | // (low > high && (key > low || key < high)) || 19 | // (low === high && key !== low); 20 | return (less_than(low, high) && less_than(low, key) && less_than(key, high)) || 21 | (less_than(high, low) && (less_than(low, key) || less_than(key, high))) || 22 | (equal_to(low, high) && !equal_to(key, low)); 23 | } 24 | exports.in_range = in_range; 25 | 26 | // Is key in (low, high] 27 | function in_half_open_range(key, low, high) { 28 | //return (low < high && key > low && key <= high) || 29 | // (low > high && (key > low || key <= high)) || 30 | // (low == high); 31 | return (less_than(low, high) && less_than(low, key) && less_than_or_equal(key, high)) || 32 | (less_than(high, low) && (less_than(low, key) || less_than_or_equal(key, high))) || 33 | (equal_to(low, high)); 34 | } 35 | exports.in_half_open_range = in_half_open_range; 36 | 37 | // Key comparison 38 | function less_than(low, high) { 39 | if (low.length !== high.length) { 40 | // Arbitrary comparison 41 | return low.length < high.length; 42 | } 43 | 44 | for (var i = 0; i < low.length; ++i) { 45 | if (low[i] < high[i]) { 46 | return true; 47 | } else if (low[i] > high[i]) { 48 | return false; 49 | } 50 | } 51 | 52 | return false; 53 | } 54 | exports.less_than = less_than; 55 | 56 | function less_than_or_equal(low, high) { 57 | if (low.length !== high.length) { 58 | // Arbitrary comparison 59 | return low.length <= high.length; 60 | } 61 | 62 | for (var i = 0; i < low.length; ++i) { 63 | if (low[i] < high[i]) { 64 | return true; 65 | } else if (low[i] > high[i]) { 66 | return false; 67 | } 68 | } 69 | 70 | return true; 71 | } 72 | exports.less_than_or_equal = less_than_or_equal; 73 | 74 | function equal_to(a, b) { 75 | if (a.length !== b.length) { 76 | return false; 77 | } 78 | 79 | for (var i = 0; i < a.length; ++i) { 80 | if (a[i] !== b[i]) { 81 | return false; 82 | } 83 | } 84 | 85 | return true; 86 | } 87 | exports.equal_to = equal_to; 88 | 89 | // Computes a new key equal to key + 2 ^ exponent. 90 | // Assumes key is a 4 element array of 32 bit words, most significant word first. 91 | function add_exp(key, exponent) { 92 | var result = key.concat(); // copy array 93 | var index = key.length - Math.floor(exponent / 32) - 1; 94 | 95 | result[index] += 1 << (exponent % 32); 96 | 97 | var carry = 0; 98 | while (index >= 0) { 99 | result[index] += carry; 100 | carry = 0; 101 | if (result[index] > 0xffffffff) { 102 | result[index] -= 0x100000000; 103 | carry = 1; 104 | } 105 | --index; 106 | } 107 | 108 | return result; 109 | } 110 | exports.add_exp = add_exp; 111 | 112 | exports.next_key = function next_key(key) { 113 | return add_exp(key, 0); 114 | }; 115 | 116 | exports.key_equals = equal_to; 117 | 118 | // Chord message types 119 | var NOTIFY_PREDECESSOR = 0; 120 | var NOTIFY_SUCCESSOR = 1; 121 | var FIND_SUCCESSOR = 2; 122 | var FOUND_SUCCESSOR = 3; 123 | var MESSAGE = 4; 124 | 125 | function Node(id, on_message, send) { 126 | var predecessor = null; 127 | var predecessor_ttl = 0; 128 | var self = {id: id}; 129 | var successor = self; 130 | var successor_ttl = 0; 131 | var fingers = []; 132 | 133 | function closest_preceding_node(find_id) { 134 | for (var i = fingers.length - 1; i >= 0; --i) { 135 | if (fingers[i] && in_range(fingers[i].id, id, find_id)) { 136 | return fingers[i]; 137 | } 138 | } 139 | 140 | if (in_range(successor.id, id, find_id)) { 141 | return successor; 142 | } else { 143 | return self; 144 | } 145 | } 146 | 147 | self.receive = function receive(from, message) { 148 | switch (message.type) { 149 | case NOTIFY_PREDECESSOR: 150 | if (predecessor === null || in_range(from.id, predecessor.id, id)) { 151 | predecessor = from; 152 | } 153 | 154 | send(from, {type: NOTIFY_SUCCESSOR}, predecessor); 155 | 156 | predecessor_ttl = 12; // Some significant number of check/stabilize cycles to wait until declaring a predecessor dead 157 | break; 158 | 159 | case FOUND_SUCCESSOR: 160 | if (message.hasOwnProperty('next')) { 161 | fingers[message.next] = from; 162 | } 163 | // Fall through 164 | case NOTIFY_SUCCESSOR: 165 | if (message.type === NOTIFY_SUCCESSOR) { 166 | successor_ttl = 12; 167 | } 168 | 169 | if (in_range(from.id, id, successor.id)) { 170 | successor = from; 171 | } 172 | break; 173 | 174 | case FIND_SUCCESSOR: 175 | if (in_half_open_range(message.id, id, successor.id)) { 176 | message.type = FOUND_SUCCESSOR; 177 | send(from, message, successor); 178 | } else { 179 | send(closest_preceding_node(message.id), message, from); 180 | } 181 | break; 182 | 183 | case MESSAGE: 184 | if (message.id) { 185 | if (in_half_open_range(message.id, id, successor.id)) { 186 | //console.log('delivering message ' + JSON.stringify(message) + ' to its final destination: ' + successor.id[0]); 187 | delete message.id; 188 | send(successor, message, from); 189 | } else { 190 | //console.log('forwarding message ' + JSON.stringify(message) + ' from ' + id[0] + ' to ' + closest_preceding_node(message.id).id[0]); 191 | send(closest_preceding_node(message.id), message, from); 192 | } 193 | } else if (on_message) { 194 | on_message(from, id, message.message, function reply(message, to, reply_to) { 195 | send(to ? to : from, {type: MESSAGE, message: message}, reply_to); 196 | }); 197 | } 198 | break; 199 | 200 | default: 201 | // ignore any messages that we don't recognize 202 | console.error('Unknown Chord message type ' + message.type); 203 | break; 204 | } 205 | 206 | /* 207 | message.type_name = ({ 208 | 0: 'notify_predecessor', 209 | 1: 'notify_successor', 210 | 2: 'find_successor', 211 | 3: 'found_successor', 212 | 4: 'message' 213 | })[message.type]; 214 | var pred = '?'; 215 | if (predecessor) { 216 | pred = '' + predecessor.id[0] + ' (' + predecessor_ttl + ')'; 217 | } 218 | console.log(from.id[0] + ' -> ' + id[0] + ' (' + pred + '-' + successor.id[0] + '): ' + JSON.stringify(message)) 219 | */ 220 | }; 221 | 222 | var next_finger = 0; 223 | setInterval(function fix_fingers() { 224 | send(successor, {type: FIND_SUCCESSOR, id: add_exp(id, next_finger + 1), next: next_finger}); 225 | next_finger += 13; 226 | if (next_finger >= 127) { 227 | next_finger -= 127; 228 | } 229 | }, 600).unref(); 230 | 231 | setInterval(function check_predecessor_and_stabilize() { 232 | if (--predecessor_ttl < 1) { // if predecessor has failed to stabilize for "long" time, it has failed 233 | predecessor = null; 234 | predecessor_ttl = 1; 235 | } 236 | 237 | if (--successor_ttl < 1) { 238 | successor = self; 239 | successor_ttl = 1; 240 | } 241 | 242 | send(successor, {type: NOTIFY_PREDECESSOR}); 243 | 244 | // Periodically log node state 245 | 246 | //var pred = '?'; 247 | //if (predecessor) { 248 | // pred = '' + predecessor.id[0] + ' (' + predecessor_ttl + ')'; 249 | //} 250 | //console.info(pred + ' < ' + id[0] + ' < ' + successor.id[0] + ': ' + fingers.map(function (finger) { 251 | // return finger.id[0]; 252 | //})); 253 | //console.info(pred + ' < ' + id[0] + ' < ' + successor.id[0]); 254 | 255 | }, 700).unref(); 256 | 257 | var join_retry; 258 | self.join = function join(remote) { 259 | predecessor = null; 260 | function try_to_join() { 261 | send(remote, {type: FIND_SUCCESSOR, id: id}); 262 | } 263 | join_retry = setInterval(try_to_join, 2000).unref(); 264 | try_to_join(); 265 | }; 266 | 267 | self.send_hash = function send_id(id, message, to, reply_to) { 268 | send(to, {type: MESSAGE, message: message, id: id}, reply_to); 269 | } 270 | 271 | self.send = function send(key, message, to, reply_to) { 272 | var key_hash = hash(key); 273 | self.send_hash(key_hash, message, to, reply_to); 274 | }; 275 | 276 | return self; 277 | } 278 | 279 | // Returns a function for sending messages into the chord. Takes some parameters: 280 | // to - node to send to {address: '1.2.3.4', port: 1234}; if null, sends to the local node 281 | // which is not useful for client-only nodes 282 | // id - the ID (hash) whose successor should receive the message; if null, sends to a 283 | // representative virtual node on that physical node 284 | // message - the message to send; must be msgpack-able 285 | // reply_to - optional; the node to reply to; useful for forwarding messages 286 | exports.Chord = function Chord(listen_port, virtual_nodes, join_existing_or_on_message, on_message) { 287 | var join_existing; 288 | 289 | if (join_existing_or_on_message) { 290 | if (join_existing_or_on_message.hasOwnProperty('port')) { 291 | join_existing = join_existing_or_on_message; 292 | } else if (!on_message) { 293 | on_message = join_existing_or_on_message; 294 | } 295 | } 296 | 297 | var server = dgram.createSocket('udp4'); 298 | server.bind(listen_port); 299 | 300 | var nodes = {}; 301 | var last_node = null; 302 | var last_node_send = null; 303 | 304 | server.on('message', function (packet, remote) { 305 | var message = deserialize(packet); 306 | 307 | if (message.version !== 1) { 308 | console.error("Unexpected Chord transport version " + message.version); 309 | return; 310 | } 311 | 312 | var to = last_node; 313 | if (message.to) { 314 | to = nodes[message.to]; 315 | } 316 | if (to) { 317 | var from = message.from; 318 | if (!from.address) { 319 | from = { 320 | address: remote.address, 321 | port: remote.port, 322 | id: from 323 | }; 324 | } 325 | to.receive(from, message.message); 326 | } 327 | }); 328 | 329 | // Create and connect local nodes. 330 | for (var i = 0; i < virtual_nodes; ++i) { 331 | (function () { 332 | var id = hash(uuid.v4()); 333 | 334 | last_node_send = function send(to, message, reply_to) { 335 | if (!to) { 336 | to = node; 337 | } 338 | if (to.receive) { 339 | setImmediate(to.receive, reply_to ? reply_to : node, message); 340 | } else { 341 | var from = reply_to ? (reply_to.receive ? reply_to.id : reply_to) : id; 342 | var packet = serialize({ 343 | version: 1, 344 | from: from, 345 | to: to.id, 346 | message: message 347 | }); 348 | server.send(packet, 0, packet.length, to.port, to.address); 349 | } 350 | } 351 | 352 | var node = Node(id, on_message, last_node_send); 353 | 354 | if (last_node || join_existing) { 355 | node.join(last_node || join_existing); 356 | } 357 | last_node = nodes[id] = node; 358 | })(); 359 | } 360 | 361 | // Returns a function for sending application messages over the Chord router. 362 | var chord_send_message = function chord_send_message(to, id, message, reply_to) { 363 | return last_node_send(to ? to : last_node, {type: MESSAGE, id: id, message: message}, reply_to); 364 | }; 365 | 366 | chord_send_message.close = function chord_close() { 367 | server.unref(); 368 | }; 369 | 370 | return chord_send_message; 371 | }; 372 | 373 | // A client is just a Chord node that never joins anyone else, but it still knows how 374 | // to send and receive messages. 375 | exports.Client = function Client(on_message, listen_port) { 376 | return exports.Chord(listen_port, 1, on_message); 377 | }; 378 | --------------------------------------------------------------------------------