├── .gitignore ├── .jscsrc ├── .jshintrc ├── README.md ├── examples ├── acknowledgement.js ├── broadcastAndRooms.js ├── clientReconnection.js ├── customObjectSerializer.js ├── socketServer.js ├── stream.js ├── streamAcknowledgement.js ├── streamBroadcast.js ├── streamToRoom.js └── streamToSocket.js ├── index.js ├── lib ├── Constants.js ├── Reader.js ├── Serializer.js ├── Server.js ├── Sock.js ├── Socket.js └── SocketServ.js ├── package.json ├── protocol └── protocol.txt └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "airbnb", 3 | "disallowTrailingComma": true, 4 | "requireTrailingComma": null 5 | } -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": false, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "latedef": "nofunc", 6 | "noarg": true, 7 | "node": true, 8 | "strict": true, 9 | "undef": true, 10 | "unused": "vars" 11 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | fast-tcp 2 | === 3 | 4 | fast-tcp is an extremely fast TCP client and server that allows to emit and listen to events. It also provides more features like binary streaming, acknowledgements, broadcasts, rooms, etc. 5 | 6 | In order to get the maximum performance, every data type is sent using the fastest way to write it into the underline Buffer. Integer numbers are sent as signed integers of 48 bits, decimal numbers as double of 64 bits, boolean as byte, strings as utf8 string, buffers as binary, objects are serialized as binary and streams are transmitted in binary over the fast-tcp protocol. 7 | 8 | To be flexible sending objects, by default, they are serialized/deserialized using JSON.stringify/JSON.parse so, sending a Javascript object is possible out of the box. It is also possible to override the objects serialization so, you can use third-party libraries like Protocol Buffer, avro, MessagePack or even your own implementation. 9 | 10 | ## Install 11 | npm install fast-tcp 12 | 13 | ## Features 14 | * All primitive data types are supported (boolean, string, number, object, buffer) 15 | * Configurable client reconnection 16 | * Callbacks in message and stream reception (acknowledgements) 17 | * Broadcasts, rooms and client to client messages and streams 18 | * Configurable object serializer/deserializer (Protocol Buffer, avro, MessagePack, etc) 19 | * High performance binary streams over fast-tcp protocol 20 | * AS FAST AS LIGHT! 21 | 22 | ## Client Libraries 23 | * [fast-tcp-java](https://github.com/alemures/fast-tcp-java) 24 | * [fast-tcp-c](https://github.com/alemures/fast-tcp-c) - In development 25 | 26 | ## Samples 27 | 28 | #### Simple socket-server 29 | ```javascript 30 | var Server = require('fast-tcp').Server; 31 | var Socket = require('fast-tcp').Socket; 32 | 33 | var server = new Server(); 34 | server.on('connection', function (socket) { 35 | socket.on('login', function (username) { 36 | console.log('Trying to login: ' + username); 37 | }); 38 | }); 39 | server.listen(5000); 40 | 41 | var socket = new Socket({ 42 | host: 'localhost', 43 | port: 5000 44 | }); 45 | socket.emit('login', 'alejandro'); 46 | ``` 47 | 48 | #### Configurable client reconnection 49 | ```javascript 50 | var socket = new Socket({ 51 | ... 52 | reconnect: true, // (true by default) 53 | reconnectInterval: 2000 // (1000ms by default) 54 | }); 55 | ``` 56 | 57 | #### Callbacks in message and stream reception (acknowledgements) 58 | For messages: 59 | ```javascript 60 | server.on('connection', function (socket) { 61 | socket.on('login', function (username, callback) { 62 | callback(username === 'alejandro' ? true : false); 63 | }); 64 | }); 65 | 66 | // Client 67 | socket.emit('login', 'alejandro', function (response) { 68 | console.log('Response: ' + response); 69 | }); 70 | ``` 71 | For streams: 72 | ```javascript 73 | server.on('connection', function (socket) { 74 | socket.on('image', function (readStream, info, callback) { 75 | var writeStream = fs.createWriteStream(info.name); 76 | readStream.pipe(writeStream); 77 | 78 | writeStream.on('finish', function () { 79 | callback('Image "' + info.name + '" stored!'); 80 | }); 81 | }); 82 | }); 83 | 84 | // Client 85 | var writeStream = socket.stream('image', { name: 'img-copy.jpg' }, function (response) { 86 | console.log('Response: ' + response); 87 | }); 88 | fs.createReadStream('img.jpg').pipe(writeStream); 89 | ``` 90 | 91 | #### Broadcasts, rooms and client to client messages and streams 92 | From client: 93 | ```javascript 94 | // Join room 95 | socket.join('room_name'); 96 | 97 | // Leave room 98 | socket.leave('room_name'); 99 | 100 | // Leave all rooms 101 | socket.leaveAll(); 102 | 103 | // Broadcast event to everyone, exclude sender 104 | socket.emit('hello', 'Hello, World!', { broadcast: true }); 105 | 106 | // Broadcast stream to everyone, exclude sender 107 | var writeStream = socket.stream('hello', 'Hello, World!', { broadcast: true }); 108 | 109 | // Broadcast event to everyone, include sender 110 | socket.emit('hello', 'Hello, World!', { broadcast: true, sockets: [socket.id] }); 111 | 112 | // Broadcast event to everyone in room "room_name", exclude sender 113 | socket.emit('hello', 'Hello, Room!', { rooms: ['room_name'] }); 114 | 115 | // Broadcast stream to everyone in room "room_name", exclude sender 116 | var writeStream = socket.stream('hello', 'Hello, World!', { rooms: ['room_name'] }); 117 | 118 | // Broadcast event to everyone in room "room_name", include sender 119 | socket.emit('hello', 'Hello, Room!', { rooms: ['room_name'], sockets: [socket.id] }); 120 | 121 | // Send event to individual "socket_id" 122 | socket.emit('hello', 'Hello, Socket!', { sockets: ['socket_id'] }); 123 | 124 | // Open stream to individual "socket_id" 125 | var writeStream = socket.stream('hello', 'Hello, World!', { sockets: ['socket_id'] }); 126 | ``` 127 | > To use the *socket#id* attribute you must wait for the event 'connect'. 128 | 129 | From server: 130 | ```javascript 131 | // Join room 132 | server.join('room_name', 'socket_id'); 133 | 134 | // Leave room 135 | server.leave('room_name', 'socket_id'); 136 | 137 | // Leave all rooms 138 | server.leaveAll('socket_id'); 139 | 140 | // Broadcast event to everyone 141 | server.emit('hello', 'Hello, World!'); 142 | 143 | // Broadcast stream to everyone 144 | var writeStream = server.stream('hello', 'Hello, World!'); 145 | 146 | // Broadcast event to everyone, with exceptions 147 | server.emit('hello', 'Hello, World!', { except: ['socket_id'] }); 148 | 149 | // Broadcast stream to everyone, with exceptions 150 | var writeStream = server.stream('hello', 'Hello, World!', { except: ['socket_id'] }); 151 | 152 | // Broadcast event to everyone in room "room_name" 153 | server.emit('hello', 'Hello, Room!', { rooms: ['room_name'] }); 154 | 155 | // Broadcast stream to everyone in room "room_name" 156 | var writeStream = server.stream('hello', 'Hello, Room!', { rooms: ['room_name'] }); 157 | 158 | // Broadcast event to everyone in room "room_name", with exceptions 159 | server.emit('hello', 'Hello, Room!', { rooms: ['room_name'], except: ['socket_id'] }); 160 | 161 | // Broadcast stream to everyone in room "room_name", with exceptions 162 | var writeStream = server.stream('hello', 'Hello, Room!', { rooms: ['room_name'], except: ['socket_id'] }); 163 | 164 | // Send event to individual "socket_id" 165 | server.emit('hello', 'Hello, Socket!', { sockets: ['socket_id'] }); 166 | 167 | // Open stream to individual "socket_id" 168 | var writeStream = server.stream('hello', 'Hello, Socket!', { sockets: ['socket_id'] }); 169 | ``` 170 | 171 | #### Configurable object serializer/deserializer 172 | ```javascript 173 | var server = new Server({ 174 | objectDeserializer: function (buffer, event) { 175 | return User.fromBuffer(buffer); 176 | } 177 | }); 178 | 179 | server.on('connection', function (socket) { 180 | socket.on('login', function (user) { 181 | console.log(user.getUsername() + '->' + user.getPassword()); 182 | }); 183 | }); 184 | 185 | // Client 186 | var socket = new Socket({ 187 | ... 188 | objectSerializer: function (user, event) { 189 | return user.toBuffer(); 190 | } 191 | }); 192 | 193 | socket.emit('login', new User('alex', '1234')); 194 | ``` 195 | 196 | #### High performance binary streams 197 | ```javascript 198 | server.on('connection', function (socket) { 199 | socket.on('image', function (readStream, info) { 200 | readStream.pipe(fs.createWriteStream(info.name)); 201 | }); 202 | }); 203 | 204 | // Client 205 | var writeStream = socket.stream('image', { name: 'img-copy.jpg' }); 206 | fs.createReadStream('img.jpg').pipe(writeStream); 207 | ``` 208 | 209 | Check out the folder `examples/` for more samples. 210 | 211 | ## jsdoc 212 | http://alemures.github.io/fast-tcp/ 213 | -------------------------------------------------------------------------------- /examples/acknowledgement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Server = require('../index').Server; 4 | var Socket = require('../index').Socket; 5 | 6 | var server = new Server(); 7 | server.on('connection', function (socket) { 8 | socket.on('login', function (username, callback) { 9 | callback(username === 'alex' ? true : false); 10 | }); 11 | }); 12 | 13 | server.listen(5000); 14 | 15 | var socket = new Socket({ 16 | host: 'localhost', 17 | port: 5000 18 | }); 19 | socket.emit('login', 'alex', function (response) { 20 | console.log('Response: ' + response); 21 | }); 22 | -------------------------------------------------------------------------------- /examples/broadcastAndRooms.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Server = require('../index').Server; 4 | var Socket = require('../index').Socket; 5 | 6 | var server = new Server(); 7 | server.on('connection', function (socket) { 8 | }); 9 | 10 | // Wait for socket to be connected 11 | setTimeout(function () { 12 | // Broadcast event to everyone 13 | server.emit('hello', 'Hello, World!'); 14 | 15 | // Broadcast event to everyone, with exceptions 16 | server.emit('hello', 'Hello, World!', { except: ['socket_id'] }); 17 | 18 | // Broadcast event to everyone in room "room_name" 19 | server.emit('hello', 'Hello, Room!', { rooms: ['room_name'] }); 20 | 21 | // Broadcast event to everyone in room "room_name", with exceptions 22 | server.emit('hello', 'Hello, Room!', { rooms: ['room_name'], except: ['socket_id'] }); 23 | 24 | // Send event to individual "socket_id" 25 | server.emit('hello', 'Hello, Socket!', { sockets: ['socket_id'] }); 26 | }, 500); 27 | 28 | server.listen(5000); 29 | 30 | var socket = new Socket({ 31 | host: 'localhost', 32 | port: 5000 33 | }); 34 | 35 | socket.on('connect', function () { 36 | // Broadcast event to everyone, exclude sender 37 | socket.emit('hello', 'Hello, World!', { broadcast: true }); 38 | 39 | // Broadcast event to everyone, include sender 40 | socket.emit('hello', 'Hello, World!', { broadcast: true, sockets: [socket.id] }); 41 | 42 | // Broadcast event to everyone in room "room_name", exclude sender 43 | socket.emit('hello', 'Hello, Room!', { rooms: ['room_name'] }); 44 | 45 | // Broadcast event to everyone in room "room_name", include sender 46 | socket.emit('hello', 'Hello, Room!', { rooms: ['room_name'], sockets: [socket.id] }); 47 | 48 | // Send event to individual "socket_id" 49 | socket.emit('hello', 'Hello, Socket!', { sockets: ['socket_id'] }); 50 | }); 51 | -------------------------------------------------------------------------------- /examples/clientReconnection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Socket = require('../index').Socket; 4 | 5 | var socket = new Socket({ 6 | host: 'localhost', 7 | port: 5000, 8 | reconnect: true, // (true by default) 9 | reconnectInterval: 2000 // (1000ms by default) 10 | }); 11 | 12 | // It's required, otherwise node.js will throw an "Unhandled 'error' event" 13 | socket.on('error', function (err) { 14 | console.error(err); 15 | }); 16 | -------------------------------------------------------------------------------- /examples/customObjectSerializer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Server = require('../index').Server; 4 | var Socket = require('../index').Socket; 5 | 6 | function User(username, password) { 7 | this.username = username; 8 | this.password = password; 9 | } 10 | 11 | User.prototype.getUsername = function () { 12 | return this.username; 13 | }; 14 | 15 | User.prototype.getPassword = function () { 16 | return this.password; 17 | }; 18 | 19 | User.fromBuffer = function (buffer) { 20 | return new User(buffer.toString().split(':')[0], buffer.toString().split(':')[1]); 21 | }; 22 | 23 | User.prototype.toBuffer = function () { 24 | return new Buffer(this.username + ':' + this.password); 25 | }; 26 | 27 | var server = new Server({ 28 | // Creates an object instance from a binary buffer 29 | objectDeserializer: function (buffer, event) { 30 | return User.fromBuffer(buffer); 31 | } 32 | }); 33 | 34 | server.on('connection', function (socket) { 35 | socket.on('login', function (user) { 36 | console.log(user.getUsername() + ' -> ' + user.getPassword()); 37 | }); 38 | }); 39 | 40 | server.listen(5000); 41 | 42 | var socket = new Socket({ 43 | host: 'localhost', 44 | port: 5000, 45 | 46 | // Convert the object to a binary buffer 47 | objectSerializer: function (user, event) { 48 | return user.toBuffer(); 49 | } 50 | }); 51 | 52 | socket.emit('login', new User('alex', '1234')); 53 | -------------------------------------------------------------------------------- /examples/socketServer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Server = require('../index').Server; 4 | var Socket = require('../index').Socket; 5 | 6 | var server = new Server(); 7 | server.on('connection', function (socket) { 8 | socket.on('login', function (username) { 9 | console.log('Trying to login: ' + username); 10 | }); 11 | }); 12 | 13 | server.listen(5000); 14 | 15 | var socket = new Socket({ 16 | host: 'localhost', 17 | port: 5000 18 | }); 19 | socket.emit('login', 'alex'); 20 | -------------------------------------------------------------------------------- /examples/stream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | 5 | var Server = require('../index').Server; 6 | var Socket = require('../index').Socket; 7 | 8 | var server = new Server(); 9 | server.on('connection', function (socket) { 10 | socket.on('image', function (readStream, info) { 11 | readStream.pipe(fs.createWriteStream(info.name)); 12 | }); 13 | }); 14 | 15 | server.listen(5000); 16 | 17 | var socket = new Socket({ 18 | host: 'localhost', 19 | port: 5000 20 | }); 21 | 22 | var writeStream = socket.stream('image', { name: 'img-copy.jpg' }); 23 | fs.createReadStream('img.jpg').pipe(writeStream); 24 | -------------------------------------------------------------------------------- /examples/streamAcknowledgement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | 5 | var Server = require('../index').Server; 6 | var Socket = require('../index').Socket; 7 | 8 | var server = new Server(); 9 | server.on('connection', function (socket) { 10 | socket.on('image', function (readStream, info, callback) { 11 | var writeStream = fs.createWriteStream(info.name); 12 | readStream.pipe(writeStream); 13 | 14 | writeStream.on('finish', function () { 15 | callback('Image "' + info.name + '" stored!'); 16 | }); 17 | }); 18 | }); 19 | 20 | server.listen(5000); 21 | 22 | var socket = new Socket({ 23 | host: 'localhost', 24 | port: 5000 25 | }); 26 | 27 | var writeStream = socket.stream('image', { name: 'img-copy.jpg' }, function (response) { 28 | console.log('Response: ' + response); 29 | }); 30 | 31 | fs.createReadStream('img.jpg').pipe(writeStream); 32 | -------------------------------------------------------------------------------- /examples/streamBroadcast.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | 5 | var Server = require('../index').Server; 6 | var Socket = require('../index').Socket; 7 | 8 | var server = new Server(); 9 | server.listen(5000); 10 | 11 | var socket = new Socket({ 12 | host: 'localhost', 13 | port: 5000 14 | }); 15 | 16 | fs.createReadStream('img.jpg').pipe(socket.stream('image', { name: 'img-copy.jpg' }, 17 | { broadcast: true })); 18 | 19 | var socket2 = new Socket({ 20 | host: 'localhost', 21 | port: 5000 22 | }); 23 | 24 | socket2.on('image', function (readStream, info) { 25 | readStream.pipe(fs.createWriteStream('socket2-' + info.name)); 26 | }); 27 | 28 | var socket3 = new Socket({ 29 | host: 'localhost', 30 | port: 5000 31 | }); 32 | 33 | socket3.on('image', function (readStream, info) { 34 | readStream.pipe(fs.createWriteStream('socket3-' + info.name)); 35 | }); 36 | -------------------------------------------------------------------------------- /examples/streamToRoom.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | 5 | var Server = require('../index').Server; 6 | var Socket = require('../index').Socket; 7 | 8 | var server = new Server(); 9 | server.listen(5000); 10 | 11 | var socket = new Socket({ 12 | host: 'localhost', 13 | port: 5000 14 | }); 15 | 16 | socket.on('image', function (readStream, info) { 17 | readStream.pipe(fs.createWriteStream('socket1-' + info.name)); 18 | }); 19 | 20 | setTimeout(function () { 21 | fs.createReadStream('img.jpg').pipe(socket.stream('image', { name: 'img-copy.jpg' }, 22 | { rooms: ['images'] })); 23 | }, 50); 24 | 25 | var socket2 = new Socket({ 26 | host: 'localhost', 27 | port: 5000 28 | }); 29 | 30 | socket2.join('images'); 31 | 32 | socket2.on('image', function (readStream, info) { 33 | readStream.pipe(fs.createWriteStream('socket2-' + info.name)); 34 | }); 35 | 36 | var socket3 = new Socket({ 37 | host: 'localhost', 38 | port: 5000 39 | }); 40 | 41 | // socket3.join('images'); 42 | 43 | socket3.on('image', function (readStream, info) { 44 | readStream.pipe(fs.createWriteStream('socket3-' + info.name)); 45 | }); 46 | 47 | var socket4 = new Socket({ 48 | host: 'localhost', 49 | port: 5000 50 | }); 51 | 52 | socket4.join('images'); 53 | 54 | socket4.on('image', function (readStream, info) { 55 | readStream.pipe(fs.createWriteStream('socket4-' + info.name)); 56 | }); 57 | 58 | -------------------------------------------------------------------------------- /examples/streamToSocket.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | 5 | var Server = require('../index').Server; 6 | var Socket = require('../index').Socket; 7 | 8 | var server = new Server(); 9 | server.listen(5000); 10 | 11 | var socket = new Socket({ 12 | host: 'localhost', 13 | port: 5000 14 | }); 15 | 16 | socket.on('my-socket-id', function (socketId) { 17 | fs.createReadStream('img.jpg').pipe(socket.stream('image', { name: 'img-copy.jpg' }, 18 | { sockets: [socketId] })); 19 | }); 20 | 21 | var socket2 = new Socket({ 22 | host: 'localhost', 23 | port: 5000 24 | }); 25 | 26 | socket2.on('connect', function () { 27 | socket2.emit('my-socket-id', socket2.id, { broadcast: true }); 28 | }); 29 | 30 | socket2.on('image', function (readStream, info) { 31 | readStream.pipe(fs.createWriteStream(info.name)); 32 | }); 33 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports.Server = require('./lib/Server'); 2 | module.exports.Socket = require('./lib/Socket'); 3 | -------------------------------------------------------------------------------- /lib/Constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Constants = { 4 | // General 5 | 6 | // Server 7 | 8 | // Socket 9 | RECONNECT: true, 10 | RECONNECT_INTERVAL: 1000, 11 | AUTO_CONNECT: true, 12 | USE_QUEUE: true, 13 | QUEUE_SIZE: Infinity 14 | }; 15 | 16 | module.exports = Constants; 17 | -------------------------------------------------------------------------------- /lib/Reader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Reader() { 4 | // Main buffer 5 | this._buffer = null; 6 | this._offset = 0; 7 | this._bytesRead = 0; 8 | this._messageLength = 0; 9 | 10 | // Chunk 11 | this._offsetChunk = 0; 12 | } 13 | 14 | Reader.prototype.read = function (chunk) { 15 | this._offsetChunk = 0; 16 | var buffers = []; 17 | 18 | while (this._offsetChunk < chunk.length) { 19 | if (this._bytesRead < 4) { 20 | if (this._readMessageLength(chunk)) { 21 | this._createBuffer(); 22 | } else { 23 | break; 24 | } 25 | } 26 | 27 | if (this._bytesRead < this._buffer.length && !this._readMessageContent(chunk)) { 28 | break; 29 | } 30 | 31 | // Buffer ready, store it and keep reading the chunk 32 | buffers.push(this._buffer); 33 | this._offset = 0; 34 | this._bytesRead = 0; 35 | this._messageLength = 0; 36 | } 37 | 38 | return buffers; 39 | }; 40 | 41 | // Read an uInt32LE (4 bytes) 42 | Reader.prototype._readMessageLength = function (chunk) { 43 | for (; this._offsetChunk < chunk.length && this._bytesRead < 4; 44 | this._offsetChunk++, this._bytesRead++) { 45 | this._messageLength |= chunk[this._offsetChunk] << (this._bytesRead * 8); 46 | } 47 | 48 | return this._bytesRead === 4; 49 | }; 50 | 51 | Reader.prototype._readMessageContent = function (chunk) { 52 | var bytesToRead = this._buffer.length - this._bytesRead; 53 | var bytesInChunk = chunk.length - this._offsetChunk; 54 | var end = bytesToRead > bytesInChunk ? chunk.length : this._offsetChunk + bytesToRead; 55 | 56 | chunk.copy(this._buffer, this._offset, this._offsetChunk, end); 57 | 58 | var bytesRead = end - this._offsetChunk; 59 | 60 | this._bytesRead += bytesRead; 61 | this._offset += bytesRead; 62 | this._offsetChunk = end; 63 | 64 | return this._bytesRead === this._buffer.length; 65 | }; 66 | 67 | Reader.prototype._createBuffer = function () { 68 | // allocUnsafe is used because every byte will be overriden 69 | this._buffer = new Buffer(4 + this._messageLength); 70 | this._buffer.writeUInt32LE(this._messageLength, this._offset); 71 | this._offset += 4; 72 | }; 73 | 74 | module.exports = Reader; 75 | -------------------------------------------------------------------------------- /lib/Serializer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ut = require('utjs'); 4 | 5 | function Serializer(opts) { 6 | opts = opts || {}; 7 | 8 | this._objectSerializer = ut.isFunction(opts.objectSerializer) ? opts.objectSerializer : 9 | this._defaultObjectSerializer; 10 | 11 | this._objectDeserializer = ut.isFunction(opts.objectDeserializer) ? opts.objectDeserializer : 12 | this._defaultObjectDeserializer; 13 | } 14 | 15 | Serializer.VERSION = 1; 16 | 17 | Serializer.DT_STRING = 1; 18 | Serializer.DT_BINARY = 2; 19 | Serializer.DT_INTEGER = 3; 20 | Serializer.DT_DECIMAL = 4; 21 | Serializer.DT_OBJECT = 5; 22 | Serializer.DT_BOOLEAN = 6; 23 | Serializer.DT_EMPTY = 7; 24 | 25 | Serializer.MT_ERROR = 0; 26 | Serializer.MT_REGISTER = 1; 27 | Serializer.MT_DATA = 2; 28 | Serializer.MT_DATA_TO_SOCKET = 3; 29 | Serializer.MT_DATA_TO_ROOM = 4; 30 | Serializer.MT_DATA_BROADCAST = 5; 31 | Serializer.MT_DATA_WITH_ACK = 6; 32 | Serializer.MT_ACK = 7; 33 | Serializer.MT_JOIN_ROOM = 8; 34 | Serializer.MT_LEAVE_ROOM = 9; 35 | Serializer.MT_LEAVE_ALL_ROOMS = 10; 36 | Serializer.MT_DATA_STREAM_OPEN = 11; 37 | Serializer.MT_DATA_STREAM = 12; 38 | Serializer.MT_DATA_STREAM_CLOSE = 13; 39 | Serializer.MT_DATA_STREAM_OPEN_WITH_ACK = 14; 40 | Serializer.MT_DATA_STREAM_OPEN_TO_SOCKET = 15; 41 | Serializer.MT_DATA_STREAM_OPEN_TO_ROOM = 16; 42 | Serializer.MT_DATA_STREAM_OPEN_BROADCAST = 17; 43 | 44 | Serializer.prototype.serialize = function (event, data, mt, messageId) { 45 | var dt; 46 | 47 | switch (typeof data) { 48 | case 'string': 49 | dt = Serializer.DT_STRING; 50 | break; 51 | case 'number': 52 | dt = data % 1 === 0 ? Serializer.DT_INTEGER : Serializer.DT_DECIMAL; 53 | break; 54 | case 'object': 55 | if (data === null) { 56 | dt = Serializer.DT_EMPTY; 57 | } else if (data instanceof Buffer) { 58 | dt = Serializer.DT_BINARY; 59 | } else { 60 | data = this._objectSerializer(data, event); 61 | dt = Serializer.DT_OBJECT; 62 | } 63 | 64 | break; 65 | case 'boolean': 66 | data = data ? 1 : 0; 67 | dt = Serializer.DT_BOOLEAN; 68 | break; 69 | default: 70 | data = null; 71 | dt = Serializer.DT_EMPTY; 72 | } 73 | 74 | return this._serialize(event, data, mt, messageId, dt); 75 | }; 76 | 77 | Serializer.prototype.deserialize = function (buff) { 78 | var offset = 0; 79 | 80 | // Message length (unused) 81 | // var messageLength = buff.readUInt32LE(offset); 82 | offset += 4; 83 | 84 | // Version (unused) 85 | var version = buff[offset++]; 86 | 87 | if (version !== Serializer.VERSION) { 88 | return { mt: Serializer.MT_ERROR, data: 'Serializer version mismatch. Remote ' + version + 89 | ' Local ' + Serializer.VERSION }; 90 | } 91 | 92 | // Flags (unused) 93 | //var flags = buff[offset]; 94 | offset++; 95 | 96 | // Data type 97 | var dt = buff[offset++]; 98 | 99 | // Message type 100 | var mt = buff[offset++]; 101 | 102 | // Message id 103 | var messageId = buff.readUInt32LE(offset); 104 | offset += 4; 105 | 106 | // Event 107 | var eventLength = buff.readUInt16LE(offset); 108 | offset += 2; 109 | 110 | var event = buff.toString(undefined, offset, offset + eventLength); 111 | offset += eventLength; 112 | 113 | // Data 114 | var dataLength = buff.readUInt32LE(offset); 115 | offset += 4; 116 | 117 | var data; 118 | switch (dt) { 119 | case Serializer.DT_STRING: 120 | data = buff.toString(undefined, offset, offset + dataLength); 121 | break; 122 | case Serializer.DT_OBJECT: 123 | data = this._objectDeserializer(buff.slice(offset, offset + dataLength), event); 124 | break; 125 | case Serializer.DT_BINARY: 126 | data = buff.slice(offset, offset + dataLength); 127 | break; 128 | case Serializer.DT_INTEGER: 129 | data = buff.readIntLE(offset, dataLength); 130 | break; 131 | case Serializer.DT_DECIMAL: 132 | data = buff.readDoubleLE(offset); 133 | break; 134 | case Serializer.DT_BOOLEAN: 135 | data = buff[offset] ? true : false; 136 | } 137 | 138 | return { event: event, data: data, messageId: messageId, mt: mt }; 139 | }; 140 | 141 | Serializer.prototype._serialize = function (event, data, mt, messageId, dt) { 142 | var eventLength = Buffer.byteLength(event); 143 | var dataLength = this._getDataLength(data, dt); 144 | 145 | // version(1), flags(1), dt,(1) mt,(1), messageId(4), 146 | // eventLength(2), event(), dataLength(4), data() 147 | var messageLength = 8 + 2 + eventLength + 4 + dataLength; 148 | 149 | var buff = new Buffer(4 + messageLength); 150 | var offset = 0; 151 | 152 | // Message length 153 | buff.writeUInt32LE(messageLength, offset); 154 | offset += 4; 155 | 156 | // Version 157 | buff[offset] = Serializer.VERSION; 158 | offset++; 159 | 160 | // Flags (unused so far) 161 | buff[offset] = 0; 162 | offset++; 163 | 164 | // Data type 165 | buff[offset] = dt; 166 | offset++; 167 | 168 | // Message type 169 | buff[offset] = mt; 170 | offset++; 171 | 172 | // Message id 173 | buff.writeUInt32LE(messageId, offset); 174 | offset += 4; 175 | 176 | // Event 177 | buff.writeUInt16LE(eventLength, offset); 178 | offset += 2; 179 | 180 | buff.write(event, offset, eventLength); 181 | offset += eventLength; 182 | 183 | // Data 184 | buff.writeUInt32LE(dataLength, offset); 185 | offset += 4; 186 | 187 | switch (dt) { 188 | case Serializer.DT_STRING: 189 | buff.write(data, offset, dataLength); 190 | break; 191 | case Serializer.DT_BINARY: 192 | case Serializer.DT_OBJECT: 193 | data.copy(buff, offset, 0, dataLength); 194 | break; 195 | case Serializer.DT_INTEGER: 196 | buff.writeIntLE(data, offset, dataLength); 197 | break; 198 | case Serializer.DT_DECIMAL: 199 | buff.writeDoubleLE(data, offset); 200 | break; 201 | case Serializer.DT_BOOLEAN: 202 | buff[offset] = data; 203 | } 204 | 205 | return buff; 206 | }; 207 | 208 | Serializer.prototype._getDataLength = function (data, dt) { 209 | switch (dt) { 210 | case Serializer.DT_STRING: 211 | return Buffer.byteLength(data); 212 | case Serializer.DT_BINARY: 213 | case Serializer.DT_OBJECT: 214 | return data.length; 215 | case Serializer.DT_INTEGER: 216 | return 6; 217 | case Serializer.DT_DECIMAL: 218 | return 8; 219 | case Serializer.DT_BOOLEAN: 220 | return 1; 221 | default: 222 | return 0; 223 | } 224 | }; 225 | 226 | Serializer.prototype._defaultObjectSerializer = function (data, event) { 227 | return new Buffer(JSON.stringify(data)); 228 | }; 229 | 230 | Serializer.prototype._defaultObjectDeserializer = function (data, event) { 231 | return JSON.parse(data.toString()); 232 | }; 233 | 234 | module.exports = Serializer; 235 | -------------------------------------------------------------------------------- /lib/Server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PassThrough = require('stream').PassThrough; 4 | var net = require('net'); 5 | var util = require('util'); 6 | var ut = require('utjs'); 7 | var EventEmitter = require('events'); 8 | 9 | var SocketServ = require('./SocketServ'); 10 | var Serializer = require('./Serializer'); 11 | 12 | /** 13 | * The server class. 14 | * 15 | * @param {Object} [opts] The Server and net.Server options. 16 | * @param {Function} [opts.objectSerializer=JSON.stringify] Serializes an object into a binary 17 | * buffer. This functions allows you to implement custom serialization protocols for 18 | * the data or even use other known protocols like "Protocol Buffers" or "MessagePack". 19 | * @param {Function} [opts.objectDeserializer=JSON.parse] Deserializes a binary buffer into an 20 | * object. This functions allows you to implement custom serialization protocols for 21 | * the data or even use other known protocols like "Protocol Buffers" or "MessagePack". 22 | * @constructor 23 | * @fires Server#listening 24 | * @fires Server#close 25 | * @fires Server#connection 26 | * @fires Server#error 27 | */ 28 | function Server(opts) { 29 | opts = ut.isObject(opts) ? opts : {}; 30 | Server.super_.call(this); 31 | 32 | /** 33 | * The connected sockets. The key is the Socket.id and the value a Socket instance. 34 | * @type {Object} 35 | */ 36 | this.sockets = {}; 37 | 38 | /** 39 | * The rooms with at least one socket or more. The key is the room name and 40 | * the value an array of sockets. 41 | * @type {Object} 42 | */ 43 | this.rooms = {}; 44 | 45 | this._serializer = new Serializer(opts); 46 | this._server = net.createServer(opts); 47 | this._bindEvents(); 48 | } 49 | 50 | util.inherits(Server, EventEmitter); 51 | 52 | Server.prototype._superEmit = Server.prototype.emit; 53 | 54 | /** 55 | * Emit an event, if no sockets or rooms are provided, the event 56 | * will be broadcasted to all connected sockets. 57 | * 58 | * @param {String} event The event name. 59 | * @param {String|Number|Object|Buffer|Boolean} data The data to send. 60 | * @param {Object} [opts] The options. 61 | * @param {String[]} [opts.sockets=[]] The list of socket ids to send. 62 | * @param {String[]} [opts.rooms=[]] The list of rooms to send. 63 | * @param {String[]} [opts.except=[]] The list of socket ids to exclude. 64 | */ 65 | Server.prototype.emit = function (event, data, opts) { 66 | opts = ut.isObject(opts) ? opts : {}; 67 | this._emit(event, data, opts); 68 | }; 69 | 70 | /** 71 | * Creates and returns a stream.Writable instance that can be used to stream 72 | * binary data. If no opts.sockets or opts.rooms are provided, the stream 73 | * will be broadcasted to all connected sockets. 74 | * 75 | * @param {String} event The event name. 76 | * @param {String|Number|Object|Buffer|Boolean} data The data to send. 77 | * @param {Object} [opts] The options. 78 | * @param {String[]} [opts.sockets=[]] The list of socket ids to send. 79 | * @param {String[]} [opts.rooms=[]] The list of rooms to send. 80 | * @param {String[]} [opts.except=[]] The list of socket ids to exclude. 81 | */ 82 | Server.prototype.stream = function (event, data, opts) { 83 | opts = ut.isObject(opts) ? opts : {}; 84 | return this._stream(event, data, opts); 85 | }; 86 | 87 | /** 88 | * Join to a room. 89 | * 90 | * @param {String} room The room name. 91 | * @param {String} socketId The socket id. 92 | */ 93 | Server.prototype.join = function (room, socketId) { 94 | var socket = this.sockets[socketId]; 95 | 96 | if (socket === undefined) { 97 | return; 98 | } 99 | 100 | if (this.rooms[room] === undefined) { 101 | this.rooms[room] = []; 102 | } 103 | 104 | var sockets = this.rooms[room]; 105 | if (sockets.indexOf(socket) === -1) { 106 | sockets.push(socket); 107 | socket._rooms[room] = true; 108 | } 109 | }; 110 | 111 | /** 112 | * Leave a room. 113 | * 114 | * @param {String} room The room name. 115 | * @param {String} socketId The socket id. 116 | */ 117 | Server.prototype.leave = function (room, socketId) { 118 | var socket = this.sockets[socketId]; 119 | var sockets = this.rooms[room]; 120 | 121 | if (socket !== undefined && sockets !== undefined) { 122 | var index = sockets.indexOf(socket); 123 | if (index > -1) { 124 | sockets.splice(index, 1); 125 | if (sockets.length === 0) { 126 | delete this.rooms[room]; 127 | } 128 | 129 | delete socket._rooms[room]; 130 | } 131 | } 132 | }; 133 | 134 | /** 135 | * Leave all rooms. 136 | * 137 | * @param {String} socketId The socket id. 138 | */ 139 | Server.prototype.leaveAll = function (socketId) { 140 | var socket = this.sockets[socketId]; 141 | 142 | if (socket !== undefined) { 143 | for (var room in socket._rooms) { 144 | this.leave(room, socketId); 145 | } 146 | } 147 | }; 148 | 149 | /** 150 | * Start the server. It calls the underline net.Server#listen with the given arguments so 151 | * multiple optional arguments can be used. 152 | * 153 | * @see {@link https://nodejs.org/dist/latest-v6.x/docs/api/net.html#net_server_listen_port_hostname_backlog_callback} 154 | * @param {Number} [port] The port to listen to, this is the most basic usage, check the link 155 | * for more advanced usages. 156 | */ 157 | Server.prototype.listen = function () { 158 | this._server.listen.apply(this._server, arguments); 159 | }; 160 | 161 | /** 162 | * Disconnect all the clients and close the server. 163 | */ 164 | Server.prototype.close = function () { 165 | for (var socketId in this.sockets) { 166 | this.sockets[socketId].end(); 167 | } 168 | 169 | this._server.close(); 170 | }; 171 | 172 | Server.prototype._emit = function (event, data, opts) { 173 | var socketIds = ut.isArray(opts.sockets) ? opts.sockets : []; 174 | var rooms = ut.isArray(opts.rooms) ? opts.rooms : []; 175 | var except = ut.isArray(opts.except) ? opts.except : []; 176 | 177 | if (socketIds.length > 0) { 178 | this._emitToSockets(event, data, socketIds, except); 179 | } 180 | 181 | if (rooms.length > 0) { 182 | this._emitToRooms(event, data, rooms, except); 183 | } 184 | 185 | if (socketIds.length + rooms.length === 0) { 186 | this._emitBroadcast(event, data, except); 187 | } 188 | }; 189 | 190 | Server.prototype._emitToSockets = function (event, data, socketIds, except) { 191 | for (var i = 0; i < socketIds.length; i++) { 192 | var socket = this.sockets[socketIds[i]]; 193 | if (socket !== undefined && except.indexOf(socket.id) === -1) { 194 | socket.emit(event, data); 195 | } 196 | } 197 | }; 198 | 199 | Server.prototype._emitToRooms = function (event, data, rooms, except) { 200 | for (var i = 0; i < rooms.length; i++) { 201 | var sockets = this.rooms[rooms[i]]; 202 | if (sockets !== undefined) { 203 | for (var j = 0; j < sockets.length; j++) { 204 | var socket = sockets[j]; 205 | if (except.indexOf(socket.id) === -1) { 206 | socket.emit(event, data); 207 | } 208 | } 209 | } 210 | } 211 | }; 212 | 213 | Server.prototype._emitBroadcast = function (event, data, except) { 214 | for (var socketId in this.sockets) { 215 | if (except.indexOf(socketId) === -1) { 216 | this.sockets[socketId].emit(event, data); 217 | } 218 | } 219 | }; 220 | 221 | Server.prototype._stream = function (event, data, opts) { 222 | var socketIds = ut.isArray(opts.sockets) ? opts.sockets : []; 223 | var rooms = ut.isArray(opts.rooms) ? opts.rooms : []; 224 | var except = ut.isArray(opts.except) ? opts.except : []; 225 | 226 | if (socketIds.length > 0) { 227 | return this._streamToSockets(event, data, socketIds, except); 228 | } 229 | 230 | if (rooms.length > 0) { 231 | return this._streamToRooms(event, data, rooms, except); 232 | } 233 | 234 | return this._streamBroadcast(event, data, except); 235 | }; 236 | 237 | Server.prototype._streamToSockets = function (event, data, socketIds, except) { 238 | var writableStream = new PassThrough(); 239 | 240 | for (var i = 0; i < socketIds.length; i++) { 241 | var socket = this.sockets[socketIds[i]]; 242 | if (socket !== undefined && except.indexOf(socket.id) === -1) { 243 | writableStream.pipe(socket.stream(event, data)); 244 | } 245 | } 246 | 247 | return writableStream; 248 | }; 249 | 250 | Server.prototype._streamToRooms = function (event, data, rooms, except) { 251 | var writableStream = new PassThrough(); 252 | 253 | for (var i = 0; i < rooms.length; i++) { 254 | var sockets = this.rooms[rooms[i]]; 255 | if (sockets !== undefined) { 256 | for (var j = 0; j < sockets.length; j++) { 257 | var socket = sockets[j]; 258 | if (except.indexOf(socket.id) === -1) { 259 | writableStream.pipe(socket.stream(event, data)); 260 | } 261 | } 262 | } 263 | } 264 | 265 | return writableStream; 266 | }; 267 | 268 | Server.prototype._streamBroadcast = function (event, data, except) { 269 | var writableStream = new PassThrough(); 270 | 271 | for (var socketId in this.sockets) { 272 | if (except.indexOf(socketId) === -1) { 273 | writableStream.pipe(this.sockets[socketId].stream(event, data)); 274 | } 275 | } 276 | 277 | return writableStream; 278 | }; 279 | 280 | Server.prototype._bindEvents = function () { 281 | var _this = this; 282 | 283 | this._server.on('listening', function () { 284 | /** 285 | * Listening event from net.Server. 286 | * 287 | * @event Server#listening 288 | */ 289 | _this._superEmit('listening'); 290 | }); 291 | 292 | this._server.on('connection', function (socket) { 293 | _this._bindEventsSocket(socket); 294 | }); 295 | 296 | this._server.on('close', function () { 297 | /** 298 | * Close event from net.Server. 299 | * 300 | * @event Server#close 301 | */ 302 | _this._superEmit('close'); 303 | }); 304 | 305 | this._server.on('error', function (err) { 306 | _this._onError(err); 307 | }); 308 | }; 309 | 310 | Server.prototype._bindEventsSocket = function (sock) { 311 | var socket = new SocketServ(sock, this); 312 | var _this = this; 313 | 314 | socket.on('close', function () { 315 | _this.leaveAll(socket.id); 316 | delete _this.sockets[socket.id]; 317 | }); 318 | 319 | this.sockets[socket.id] = socket; 320 | 321 | // Sends the id to socket client 322 | socket._send('', socket.id, Serializer.MT_REGISTER); 323 | 324 | /** 325 | * A new Socket was connected. 326 | * 327 | * @event Server#connection 328 | */ 329 | this._superEmit('connection', socket); 330 | }; 331 | 332 | Server.prototype._onError = function (err) { 333 | if (this.listenerCount('error') > 0) { 334 | /** 335 | * Error event from net.Server. 336 | * 337 | * @event Server#error 338 | */ 339 | this._superEmit('error', err); 340 | } else { 341 | console.error('Missing error handler on `Server`.'); 342 | console.error(err.stack); 343 | } 344 | }; 345 | 346 | module.exports = Server; 347 | -------------------------------------------------------------------------------- /lib/Sock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Writable = require('stream').Writable; 4 | var Readable = require('stream').Readable; 5 | var net = require('net'); 6 | var util = require('util'); 7 | var EventEmitter = require('events'); 8 | var ut = require('utjs'); 9 | 10 | var Serializer = require('./Serializer'); 11 | var Reader = require('./Reader'); 12 | var Constants = require('./Constants'); 13 | 14 | var MAX_MESSAGE_ID = Math.pow(2, 32) - 1; 15 | 16 | /** 17 | * The super class of Socket. 18 | * 19 | * @constructor 20 | * @abstract 21 | * @fires Sock#reconnecting 22 | * @fires Sock#socket_connect 23 | * @fires Sock#socket_drain 24 | * @fires Sock#end 25 | * @fires Sock#close 26 | * @fires Sock#error 27 | */ 28 | function Sock(serializer, opts) { 29 | opts = opts || {}; 30 | Sock.super_.call(this); 31 | 32 | this._serializer = serializer; 33 | this._reader = new Reader(); 34 | this._opts = opts; 35 | 36 | this._shouldReconnect = ut.isBoolean(opts.reconnect) ? opts.reconnect : Constants.RECONNECT; 37 | 38 | this._reconnectInterval = ut.isNumber(opts.reconnectInterval) ? opts.reconnectInterval : 39 | Constants.RECONNECT_INTERVAL; 40 | 41 | this._autoConnect = ut.isBoolean(opts.autoConnect) ? opts.autoConnect : Constants.AUTO_CONNECT; 42 | 43 | this._useQueue = ut.isBoolean(opts.useQueue) ? opts.useQueue : Constants.USE_QUEUE; 44 | 45 | this._queueSize = ut.isNumber(opts.queueSize) ? opts.queueSize : Constants.QUEUE_SIZE; 46 | 47 | this._messageListener = ut.isFunction(opts.messageListener) ? opts.messageListener : null; 48 | 49 | /** 50 | * A unique identifier. It will be set up asynchronously from the server in the event 'connect'. 51 | * @type {String} 52 | */ 53 | this.id = null; 54 | 55 | this._messageId = 1; 56 | this._acks = {}; 57 | 58 | this._socket = null; 59 | this._socketConfig = {}; 60 | this._connected = false; 61 | this._manuallyClosed = false; 62 | 63 | this._queue = []; 64 | 65 | this._streams = {}; 66 | 67 | if (this._autoConnect) { 68 | this.connect(); 69 | } 70 | } 71 | 72 | util.inherits(Sock, EventEmitter); 73 | 74 | Sock.prototype._superEmit = Sock.prototype.emit; 75 | 76 | /** 77 | * Send a FIN packet. 78 | */ 79 | Sock.prototype.end = function () { 80 | if (this._connected) { 81 | this._manuallyClosed = true; 82 | this._socket.end(); 83 | } 84 | }; 85 | 86 | /** 87 | * Close the socket. 88 | */ 89 | Sock.prototype.destroy = function () { 90 | if (this._connected) { 91 | this._manuallyClosed = true; 92 | this._socket.destroy(); 93 | } 94 | }; 95 | 96 | /** 97 | * Connect the socket. The socket will be connected automatically by default 98 | * so this method is only useful when you use otps.autoConnect: false, 99 | * in the constructor. 100 | */ 101 | Sock.prototype.connect = function () { 102 | if (!this._connected) { 103 | this._manuallyClosed = false; 104 | this._socket = net.createConnection(this._opts); 105 | this._bindEvents(); 106 | } 107 | }; 108 | 109 | /** 110 | * Sets the socket to timeout after 'timeout' milliseconds of inactivity on the socket. 111 | * 112 | * @param {Number} timeout The timeout in milliseconds or 0 to disable it. 113 | */ 114 | Sock.prototype.setTimeout = function (timeout) { 115 | this._socketConfig.timeout = timeout; 116 | 117 | if (this._connected) { 118 | this._socket.setTimeout(timeout); 119 | } 120 | 121 | return this; 122 | }; 123 | 124 | /** 125 | * Disables the Nagle algorithm. 126 | * 127 | * @param {Boolean} [noDelay=true] True to disable de Nagle algorithm, false to enable it again. 128 | */ 129 | Sock.prototype.setNoDelay = function (noDelay) { 130 | this._socketConfig.noDelay = noDelay; 131 | 132 | if (this._connected) { 133 | this._socket.setNoDelay(noDelay); 134 | } 135 | 136 | return this; 137 | }; 138 | 139 | /** 140 | * Enable/disable keep-alive functionality, and optionally set the initial delay before the first 141 | * keepalive probe is sent on an idle socket. 142 | * 143 | * @param {Boolean} [enable=false] True to enable the TCP keep-alive, false to disable it. 144 | * @param {Number} [initialDelay=0] Set the delay in milliseconds between the last data packet 145 | * received and the first keepalive probe. 146 | */ 147 | Sock.prototype.setKeepAlive = function (enable, initialDelay) { 148 | this._socketConfig.keepAliveEnable = enable; 149 | this._socketConfig.keepAliveInitialDelay = initialDelay; 150 | 151 | if (this._connected) { 152 | this._socket.setKeepAlive(enable, initialDelay); 153 | } 154 | 155 | return this; 156 | }; 157 | 158 | /** 159 | * Get the version of the underlying serializer. 160 | * 161 | * @return {Number} The serializer version. 162 | */ 163 | Sock.prototype.getSerializerVersion = function () { 164 | return Serializer.VERSION; 165 | }; 166 | 167 | Sock.prototype._send = function (event, data, mt, opts) { 168 | opts = opts || {}; 169 | var messageId = opts.messageId || this._nextMessageId(); 170 | 171 | if (opts.cb !== undefined) { 172 | this._acks[messageId] = opts.cb; 173 | } 174 | 175 | var buff = this._serializer.serialize(event, data, mt, messageId); 176 | 177 | if (this._connected) { 178 | return this._socket.write(buff); 179 | } else if (this._useQueue) { 180 | if (this._queue.length + 1 > this._queueSize) { 181 | this._queue.shift(); 182 | } 183 | 184 | this._queue.push(buff); 185 | return false; 186 | } 187 | }; 188 | 189 | Sock.prototype._sendStream = function (event, data, mt, cb) { 190 | var opts = { messageId: this._nextMessageId() }; 191 | var _this = this; 192 | 193 | if (cb !== undefined) { 194 | opts.cb = cb; 195 | } 196 | 197 | this._send(event, data, mt, opts); 198 | 199 | var writeStream = new Writable({ 200 | write: function (chunk, encoding, cb) { 201 | if (_this._send(event, chunk, Serializer.MT_DATA_STREAM, opts)) { 202 | cb(); 203 | } else { 204 | _this._socket.once('drain', cb); 205 | } 206 | } 207 | }); 208 | 209 | writeStream.on('finish', function () { 210 | _this._send(event, '', Serializer.MT_DATA_STREAM_CLOSE, opts); 211 | }); 212 | 213 | return writeStream; 214 | }; 215 | 216 | Sock.prototype._reconnect = function () { 217 | var _this = this; 218 | setTimeout(function () { 219 | /** 220 | * The socket is trying to reconnect. 221 | * 222 | * @event Sock#reconnecting 223 | */ 224 | _this._superEmit('reconnecting'); 225 | _this.connect(); 226 | }, this._reconnectInterval); 227 | }; 228 | 229 | Sock.prototype._configure = function () { 230 | var config = this._socketConfig; 231 | 232 | if (config.timeout !== undefined) { 233 | this.setTimeout(config.timeout); 234 | } 235 | 236 | if (config.noDelay !== undefined) { 237 | this.setNoDelay(config.noDelay); 238 | } 239 | 240 | if (config.keepAliveEnable !== undefined || config.keepAliveInitialDelay !== undefined) { 241 | this.setKeepAlive(config.keepAliveEnable, config.keepAliveInitialDelay); 242 | } 243 | }; 244 | 245 | Sock.prototype._bindEvents = function () { 246 | var _this = this; 247 | 248 | this._socket.on('connect', function () { 249 | _this._connected = true; 250 | _this._configure(); 251 | 252 | // Send all queued events 253 | _this._flushQueue(); 254 | 255 | // Resume all waiting streams 256 | _this._socket.emit('drain'); 257 | 258 | /** 259 | * Connected underlying net.Socket, all messages in queue will 260 | * be sent and new messages will be sent directly. 261 | * 262 | * @event Sock#socket_connect 263 | */ 264 | _this._superEmit('socket_connect'); 265 | }); 266 | 267 | this._socket.on('data', function (chunk) { 268 | var buffers = _this._reader.read(chunk); 269 | 270 | for (var i = 0; i < buffers.length; i++) { 271 | _this._onMessage(_this._serializer.deserialize(buffers[i])); 272 | } 273 | }); 274 | 275 | this._socket.on('end', function () { 276 | /** 277 | * End event from net.Socket. 278 | * 279 | * @event Sock#end 280 | */ 281 | _this._superEmit('end'); 282 | }); 283 | 284 | this._socket.on('close', function (isError) { 285 | _this._connected = false; 286 | _this._socket = null; 287 | 288 | if (_this._shouldReconnect && !_this._manuallyClosed) { 289 | _this._reconnect(); 290 | } 291 | 292 | /** 293 | * Close event from net.Socket. 294 | * 295 | * @event Sock#close 296 | */ 297 | _this._superEmit('close'); 298 | }); 299 | 300 | this._socket.on('error', function (err) { 301 | _this._onError(err); 302 | }); 303 | 304 | this._socket.on('timeout', function () { 305 | _this._socket.destroy(); 306 | _this._onError(ut.error('connect TIMEOUT')); 307 | }); 308 | 309 | this._socket.on('drain', function () { 310 | /** 311 | * Emitted when the write buffer of the internal socket becomes empty. 312 | * Can be used to throttle uploads. 313 | * 314 | * @event Sock#socket_drain 315 | */ 316 | _this._superEmit('socket_drain'); 317 | }); 318 | }; 319 | 320 | Sock.prototype._onMessage = function (msg) { 321 | var readStream; 322 | 323 | switch (msg.mt) { 324 | case Serializer.MT_DATA: 325 | this._superEmit(msg.event, msg.data); 326 | break; 327 | case Serializer.MT_DATA_STREAM_OPEN: 328 | readStream = this._openDataStream(msg); 329 | this._superEmit(msg.event, readStream, msg.data); 330 | break; 331 | case Serializer.MT_DATA_STREAM_OPEN_WITH_ACK: 332 | readStream = this._openDataStream(msg); 333 | this._superEmit(msg.event, readStream, msg.data, this._ackCallback(msg.messageId)); 334 | break; 335 | case Serializer.MT_DATA_STREAM: 336 | this._transmitDataStream(msg); 337 | break; 338 | case Serializer.MT_DATA_STREAM_CLOSE: 339 | this._closeDataStream(msg); 340 | break; 341 | case Serializer.MT_DATA_WITH_ACK: 342 | this._superEmit(msg.event, msg.data, this._ackCallback(msg.messageId)); 343 | break; 344 | case Serializer.MT_ACK: 345 | this._acks[msg.messageId](msg.data); 346 | delete this._acks[msg.messageId]; 347 | break; 348 | default: 349 | if (this._messageListener) { 350 | this._messageListener(msg); 351 | } 352 | } 353 | }; 354 | 355 | Sock.prototype._openDataStream = function (msg) { 356 | var _this = this; 357 | var readStream = new Readable({ 358 | read: function (size) { 359 | if (_this._socket.isPaused()) { 360 | _this._socket.resume(); 361 | } 362 | } 363 | }); 364 | 365 | this._streams[msg.messageId] = readStream; 366 | return readStream; 367 | }; 368 | 369 | Sock.prototype._transmitDataStream = function (msg) { 370 | var readStream = this._streams[msg.messageId]; 371 | 372 | if (!readStream.push(msg.data)) { 373 | this._socket.pause(); 374 | } 375 | }; 376 | 377 | Sock.prototype._closeDataStream = function (msg) { 378 | var readStream = this._streams[msg.messageId]; 379 | 380 | readStream.push(null); 381 | delete this._streams[msg.messageId]; 382 | }; 383 | 384 | Sock.prototype._ackCallback = function (messageId) { 385 | var _this = this; 386 | 387 | return function ackCallback(data) { 388 | _this._send('', data, Serializer.MT_ACK, { messageId: messageId }); 389 | }; 390 | }; 391 | 392 | Sock.prototype._nextMessageId = function () { 393 | if (++this._messageId > MAX_MESSAGE_ID) { 394 | this._messageId = 1; 395 | } 396 | 397 | return this._messageId; 398 | }; 399 | 400 | Sock.prototype._flushQueue = function () { 401 | if (this._queue.length > 0) { 402 | for (var i = 0; i < this._queue.length; i++) { 403 | this._socket.write(this._queue[i]); 404 | } 405 | 406 | this._queue.length = 0; 407 | } 408 | }; 409 | 410 | Sock.prototype._onError = function (err) { 411 | if (this.listenerCount('error') > 0) { 412 | /** 413 | * Error event from net.Socket or Socket. 414 | * 415 | * @event Sock#error 416 | */ 417 | this._superEmit('error', err); 418 | } else { 419 | console.error('Missing error handler on `Socket`.'); 420 | console.error(err.stack); 421 | } 422 | }; 423 | 424 | module.exports = Sock; 425 | -------------------------------------------------------------------------------- /lib/Socket.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var ut = require('utjs'); 5 | 6 | var Serializer = require('./Serializer'); 7 | var Sock = require('./Sock'); 8 | 9 | /** 10 | * The client socket. 11 | * 12 | * @param {Object} opts The Socket and net.Socket options. 13 | * @param {Boolean} [opts.reconnect=true] Enable or disable the reconnection. 14 | * @param {Number} [opts.reconnectInterval=1000] The reconnection interval. 15 | * @param {Boolean} [opts.autoConnect=true] Enable or disable the 16 | * auto connection after instance the class. 17 | * @param {Boolean} [opts.useQueue=true] Enable or disable the usage of an internal 18 | * queue that will containt the emitted messages while the socket isn't 19 | * connected. The enqueued messages will be sent as soon as the socket 20 | * is connected. 21 | * @param {Number} [opts.queueSize=Infinity] The max size of the queue. If the queue is 22 | * full, new messages will replace old ones. 23 | * @param {Function} [opts.objectSerializer=JSON.stringify] Serializes an object into a binary 24 | * buffer. This functions allows you to implement custom serialization protocols for 25 | * the data or even use other known protocols like "Protocol Buffers" or "MessagePack". 26 | * @param {Function} [opts.objectDeserializer=JSON.parse] Deserializes a binary buffer into an 27 | * object. This functions allows you to implement custom serialization protocols for the 28 | * data type "object" or even use other known protocols like "Protocol Buffers" or 29 | * "MessagePack". 30 | * @constructor 31 | * @augments Sock 32 | * @fires Socket#connect 33 | * @fires Socket#reconnecting 34 | * @fires Socket#socket_connect 35 | * @fires Socket#socket_drain 36 | * @fires Socket#end 37 | * @fires Socket#close 38 | * @fires Socket#error 39 | */ 40 | function Socket(opts) { 41 | opts.messageListener = this._msgListener; 42 | Socket.super_.call(this, new Serializer(opts), opts); 43 | } 44 | 45 | util.inherits(Socket, Sock); 46 | 47 | /** 48 | * Emit an event. 49 | * 50 | * @param {String} event The event name. 51 | * @param {String|Number|Object|Buffer|Boolean} data The data to send. 52 | * @param {Object|Function} [param] The options or callback. 53 | * @param {String[]} [param.sockets=[]] The list of socket ids to send. 54 | * @param {String[]} [param.rooms=[]] The list of rooms to send. 55 | * @param {Boolean} [param.broadcast=false] Send to all connected sockets. 56 | * @return {Boolean} true if the entire data was flushed successfully. false if all or part of 57 | * the data was queued in user memory. 'socket_drain' will be emitted when the buffer 58 | * is again free. 59 | */ 60 | Socket.prototype.emit = function (event, data, param) { 61 | var opts = ut.isObject(param) ? param : {}; 62 | var cb = ut.isFunction(param) ? param : null; 63 | 64 | return this._emit(event, data, opts, cb); 65 | }; 66 | 67 | /** 68 | * Creates and returns a stream.Writable instance that can be used to stream 69 | * binary data. 70 | * 71 | * @param {String} event The event name. 72 | * @param {String|Number|Object|Buffer|Boolean} data The data to send along with the stream. 73 | * @param {Object|Function} [param] The options or callback. 74 | * @param {String[]} [param.sockets=[]] The list of socket ids to send. 75 | * @param {String[]} [param.rooms=[]] The list of rooms to send. 76 | * @param {Boolean} [param.broadcast=false] Send to all connected sockets. 77 | * @return {stream.Writable} A stream.Writable instance. 78 | */ 79 | Socket.prototype.stream = function (event, data, param) { 80 | var opts = ut.isObject(param) ? param : {}; 81 | var cb = ut.isFunction(param) ? param : null; 82 | 83 | return this._stream(event, data, opts, cb); 84 | }; 85 | 86 | /** 87 | * Join to a room. 88 | * 89 | * @param {String|String[]} room The room name. 90 | */ 91 | Socket.prototype.join = function (room) { 92 | room = ut.isString(room) ? room : room.join(','); 93 | this._send('', room, Serializer.MT_JOIN_ROOM); 94 | }; 95 | 96 | /** 97 | * Leave a room. 98 | * 99 | * @param {String|String[]} room The room name. 100 | */ 101 | Socket.prototype.leave = function (room) { 102 | room = ut.isString(room) ? room : room.join(','); 103 | this._send('', room, Serializer.MT_LEAVE_ROOM); 104 | }; 105 | 106 | /** 107 | * Leave all rooms. 108 | */ 109 | Socket.prototype.leaveAll = function () { 110 | this._send('', null, Serializer.MT_LEAVE_ALL_ROOMS); 111 | }; 112 | 113 | Socket.prototype._emit = function (event, data, opts, cb) { 114 | var socketIds = ut.isArray(opts.sockets) ? opts.sockets : []; 115 | var rooms = ut.isArray(opts.rooms) ? opts.rooms : []; 116 | var broadcast = ut.isBoolean(opts.broadcast) ? opts.broadcast : false; 117 | 118 | if (socketIds.length + rooms.length === 0 && !broadcast) { 119 | if (cb !== null) { 120 | return this._send(event, data, Serializer.MT_DATA_WITH_ACK, { cb: cb }); 121 | } 122 | 123 | return this._send(event, data, Serializer.MT_DATA); 124 | } 125 | 126 | var flushedData = true; 127 | 128 | if (broadcast) { 129 | flushedData = this._send(event, data, Serializer.MT_DATA_BROADCAST); 130 | } 131 | 132 | if (socketIds.length > 0) { 133 | flushedData = this._send(socketIds.join(',') + '|' + event, data, Serializer.MT_DATA_TO_SOCKET); 134 | } 135 | 136 | if (rooms.length > 0) { 137 | flushedData = this._send(rooms.join(',') + '|' + event, data, Serializer.MT_DATA_TO_ROOM); 138 | } 139 | 140 | return flushedData; 141 | }; 142 | 143 | Socket.prototype._stream = function (event, data, opts, cb) { 144 | var socketIds = ut.isArray(opts.sockets) ? opts.sockets : []; 145 | var rooms = ut.isArray(opts.rooms) ? opts.rooms : []; 146 | var broadcast = ut.isBoolean(opts.broadcast) ? opts.broadcast : false; 147 | 148 | if (broadcast) { 149 | return this._sendStream(event, data, Serializer.MT_DATA_STREAM_OPEN_BROADCAST); 150 | } 151 | 152 | if (socketIds.length > 0) { 153 | return this._sendStream(socketIds.join(',') + '|' + event, data, 154 | Serializer.MT_DATA_STREAM_OPEN_TO_SOCKET); 155 | } 156 | 157 | if (rooms.length > 0) { 158 | return this._sendStream(rooms.join(',') + '|' + event, data, 159 | Serializer.MT_DATA_STREAM_OPEN_TO_ROOM); 160 | } 161 | 162 | if (cb !== null) { 163 | return this._sendStream(event, data, Serializer.MT_DATA_STREAM_OPEN_WITH_ACK, cb); 164 | } 165 | 166 | return this._sendStream(event, data, Serializer.MT_DATA_STREAM_OPEN); 167 | }; 168 | 169 | Socket.prototype._msgListener = function (msg) { 170 | switch (msg.mt) { 171 | case Serializer.MT_REGISTER: 172 | this.id = msg.data; 173 | 174 | /** 175 | * The socket is connected and registered so {@link Socket#id} 176 | * now contains the socket identification. 177 | * 178 | * @event Socket#connect 179 | */ 180 | this._superEmit('connect'); 181 | break; 182 | case Serializer.MT_ERROR: 183 | this._onError(ut.error(msg.data)); 184 | } 185 | }; 186 | 187 | module.exports = Socket; 188 | -------------------------------------------------------------------------------- /lib/SocketServ.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var ut = require('utjs'); 5 | 6 | var Serializer = require('./Serializer'); 7 | var Sock = require('./Sock'); 8 | 9 | function SocketServ(socket, server) { 10 | SocketServ.super_.call(this, server._serializer, { 11 | autoConnect: false, 12 | reconnect: false, 13 | useQueue: false, 14 | messageListener: this._msgListener 15 | }); 16 | 17 | this._server = server; 18 | this._rooms = {}; 19 | 20 | // Sock fields 21 | this.id = this._generateSocketId(); 22 | this._socket = socket; 23 | this._connected = true; 24 | 25 | this._bindEvents(); 26 | } 27 | 28 | util.inherits(SocketServ, Sock); 29 | 30 | SocketServ.prototype.emit = function (event, data, param) { 31 | var opts = ut.isObject(param) ? param : {}; 32 | var cb = ut.isFunction(param) ? param : null; 33 | 34 | return this._emit(event, data, opts, cb); 35 | }; 36 | 37 | SocketServ.prototype.stream = function (event, data, param) { 38 | var opts = ut.isObject(param) ? param : {}; 39 | var cb = ut.isFunction(param) ? param : null; 40 | 41 | return this._stream(event, data, opts, cb); 42 | }; 43 | 44 | SocketServ.prototype.join = function (room) { 45 | var roomArr = ut.isString(room) ? [room] : room; 46 | 47 | for (var i = 0; i < roomArr.length; i++) { 48 | this._server.join(roomArr[i], this.id); 49 | } 50 | }; 51 | 52 | SocketServ.prototype.leave = function (room) { 53 | var roomArr = ut.isString(room) ? [room] : room; 54 | 55 | for (var i = 0; i < roomArr.length; i++) { 56 | this._server.leave(roomArr[i], this.id); 57 | } 58 | }; 59 | 60 | SocketServ.prototype.leaveAll = function () { 61 | this._server.leaveAll(this.id); 62 | }; 63 | 64 | SocketServ.prototype._emit = function (event, data, opts, cb) { 65 | var socketIds = ut.isArray(opts.sockets) ? opts.sockets : []; 66 | var rooms = ut.isArray(opts.rooms) ? opts.rooms : []; 67 | var broadcast = ut.isBoolean(opts.broadcast) ? opts.broadcast : false; 68 | 69 | if (socketIds.length + rooms.length === 0 && !broadcast) { 70 | if (cb !== null) { 71 | return this._send(event, data, Serializer.MT_DATA_WITH_ACK, { cb: cb }); 72 | } 73 | 74 | return this._send(event, data, Serializer.MT_DATA); 75 | } 76 | 77 | if (broadcast) { 78 | this._server.emit(event, data, { except: [this.id] }); 79 | } 80 | 81 | if (socketIds.length > 0) { 82 | this._server.emit(event, data, { sockets: socketIds }); 83 | } 84 | 85 | if (rooms.length > 0) { 86 | this._server.emit(event, data, { rooms: rooms, except: [this.id] }); 87 | } 88 | 89 | return true; 90 | }; 91 | 92 | SocketServ.prototype._stream = function (event, data, opts, cb) { 93 | var socketIds = ut.isArray(opts.sockets) ? opts.sockets : []; 94 | var rooms = ut.isArray(opts.rooms) ? opts.rooms : []; 95 | var broadcast = ut.isBoolean(opts.broadcast) ? opts.broadcast : false; 96 | 97 | if (broadcast) { 98 | return this._server.stream(event, data, { except: [this.id] }); 99 | } 100 | 101 | if (socketIds.length > 0) { 102 | return this._server.stream(event, data, { sockets: socketIds }); 103 | } 104 | 105 | if (rooms.length > 0) { 106 | return this._server.stream(event, data, { rooms: rooms, except: [this.id] }); 107 | } 108 | 109 | if (cb !== null) { 110 | return this._sendStream(event, data, Serializer.MT_DATA_STREAM_OPEN_WITH_ACK, cb); 111 | } 112 | 113 | return this._sendStream(event, data, Serializer.MT_DATA_STREAM_OPEN); 114 | }; 115 | 116 | SocketServ.prototype._msgListener = function (msg) { 117 | var arr; 118 | var readStream; 119 | var writableStream; 120 | 121 | switch (msg.mt) { 122 | case Serializer.MT_JOIN_ROOM: 123 | this.join(msg.data.split(',')); 124 | break; 125 | case Serializer.MT_LEAVE_ROOM: 126 | this.leave(msg.data.split(',')); 127 | break; 128 | case Serializer.MT_LEAVE_ALL_ROOMS: 129 | this.leaveAll(); 130 | break; 131 | case Serializer.MT_DATA_BROADCAST: 132 | this.emit(msg.event, msg.data, { broadcast: true }); 133 | break; 134 | case Serializer.MT_DATA_TO_ROOM: 135 | arr = msg.event.split('|'); // [0] = rooms, [1] = event 136 | this.emit(arr[1], msg.data, { rooms: arr[0].split(',') }); 137 | break; 138 | case Serializer.MT_DATA_TO_SOCKET: 139 | arr = msg.event.split('|'); // [0] = socketIds, [1] = event 140 | this.emit(arr[1], msg.data, { sockets: arr[0].split(',') }); 141 | break; 142 | case Serializer.MT_DATA_STREAM_OPEN_BROADCAST: 143 | readStream = this._openDataStream(msg); 144 | writableStream = this.stream(msg.event, msg.data, { broadcast: true }); 145 | readStream.pipe(writableStream); 146 | break; 147 | case Serializer.MT_DATA_STREAM_OPEN_TO_ROOM: 148 | arr = msg.event.split('|'); // [0] = rooms, [1] = event 149 | readStream = this._openDataStream(msg); 150 | writableStream = this.stream(arr[1], msg.data, { rooms: arr[0].split(',') }); 151 | readStream.pipe(writableStream); 152 | break; 153 | case Serializer.MT_DATA_STREAM_OPEN_TO_SOCKET: 154 | arr = msg.event.split('|'); // [0] = socketIds, [1] = event 155 | readStream = this._openDataStream(msg); 156 | writableStream = this.stream(arr[1], msg.data, { sockets: arr[0].split(',') }); 157 | readStream.pipe(writableStream); 158 | } 159 | }; 160 | 161 | SocketServ.prototype._generateSocketId = function () { 162 | var socketId; 163 | 164 | do { 165 | socketId = ut.randomString(5); 166 | } while (this._server.sockets[socketId] !== undefined); 167 | 168 | return socketId; 169 | }; 170 | 171 | module.exports = SocketServ; 172 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fast-tcp", 3 | "version": "3.3.2", 4 | "description": "fast-tcp is an extremely fast TCP client and server that allows to emit and listen to events", 5 | "author": "Alejandro Santiago Nieto", 6 | "license": "MIT", 7 | "dependencies": { 8 | "utjs": "2.x.x" 9 | }, 10 | "devDependencies": { 11 | "chai": "3.x.x", 12 | "jscs": "3.x.x", 13 | "jsdoc": "3.x.x", 14 | "jshint": "2.x.x", 15 | "mocha": "3.x.x" 16 | }, 17 | "main": "index.js", 18 | "files": [ 19 | "index.js", 20 | "lib" 21 | ], 22 | "scripts": { 23 | "test": "mocha test/*.js", 24 | "jshint": "jshint index.js lib/*.js test/*.js examples/*.js", 25 | "jscs": "jscs index.js lib/*.js test/*.js examples/*.js", 26 | "jscs:fix": "jscs --fix index.js lib/*.js test/*.js examples/*.js", 27 | "check": "npm run test && npm run jshint && npm run jscs && npm outdated", 28 | "doc": "jsdoc ./lib/ ./package.json ./README.md -d ./doc" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/alemures/fast-tcp.git" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /protocol/protocol.txt: -------------------------------------------------------------------------------- 1 | = fast-tcp Protocol = 2 | 3 | Message Format 4 | -------------- 5 | 6 | +---------------+---------+-------+----------+-------------+-----------+-------------+--------+------------+------+ 7 | | uInt32LE | byte | byte | byte | byte | uInt32LE | uInt16LE | string | uInt32LE | * | 8 | +---------------+---------+-------+----------+-------------+-----------+-------------+--------+------------+------+ 9 | | messageLength | version | flags | dataType | messageType | messageId | eventLength | event | dataLength | data | 10 | +---------------+---------+-------+----------+-------------+-----------+-------------+--------+------------+------+ 11 | 12 | * The data type can be byte, int48LE, double64LE, utf8 string or binary. 13 | 14 | 15 | Version 16 | ------- 17 | 18 | - 1. Actual 19 | 20 | 21 | Flags 22 | ----- 23 | 24 | +---+---+---+---+---+---+---+---+ 25 | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 26 | +---+---+---+---+---+---+---+---+ 27 | | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 28 | +---+---+---+---+---+---+---+---+ 29 | 30 | 31 | Data Type 32 | --------- 33 | 34 | - 1. string 35 | - 2. binary 36 | - 3. integer 37 | - 4. decimal 38 | - 5. object (Transmitted as binary) 39 | - 6. boolean (Transmitted as byte) 40 | - 7. empty (Length = 0) 41 | 42 | 43 | Message Type 44 | ------------ 45 | 46 | - 0. Error 47 | - 1. Register 48 | - 2. Data 49 | - 3. Data to socket 50 | - 4. Data to room 51 | - 5. Data broadcast 52 | - 6. Data with ack 53 | - 7. Ack 54 | - 8. Join room 55 | - 9. Leave room 56 | - 10. Leave all rooms 57 | - 11. Data stream open 58 | - 12. Data stream 59 | - 13. Data stream close 60 | - 14. Data stream open with ack 61 | - 15. Data stream open to socket 62 | - 16. Data stream open to room 63 | - 17. Data stream open to broadcast 64 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var describe = require('mocha').describe; 4 | var it = require('mocha').it; 5 | var chai = require('chai'); 6 | 7 | describe('test', function () { 8 | it('should be true', function () { 9 | chai.assert(true, 'true'); 10 | }); 11 | }); 12 | --------------------------------------------------------------------------------