Skip to content

Commit 9cbdb66

Browse files
authored
Merge pull request #64 from janusvr/multiprocessing
Multiprocessing
2 parents 9a00a23 + a156047 commit 9cbdb66

11 files changed

+360
-556
lines changed

.travis.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
language: node_js
22
node_js:
3-
- "4"
43
- "6"
4+
- "7"
55
services:
66
- mysql
7+
- redis-server
78
before_install:
89
- mysql -e 'CREATE DATABASE janusvr;'
910
- cp test/test-config.js ./config.js

config-example.js

+16-21
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,6 @@ module.exports = {
55
/* Socket port to listen on */
66
port: 5566,
77

8-
/* Web UI */
9-
startWebServer: false,
10-
webServerPort: 8080,
11-
webServerPortHttps: 8081,
12-
/*Log Levels - default is "info"
13-
*
14-
* This setting controls all logging into
15-
* the server.log file - other than the
16-
* restart message which will ALWAYS show.
17-
*
18-
* "info" -> log info and error events
19-
* "error" -> log error events only
20-
* "silent" -> do not log at all
21-
*
22-
* debug, warn, fatal, error, http could be
23-
* implemented as well, but not done yet*/
24-
logLevel: "info",
25-
26-
// Path to log output file
27-
logFilePath: "./server.log",
28-
298
/* SSL configurations */
309
ssl: {
3110
port: 5567,
@@ -35,6 +14,22 @@ module.exports = {
3514
cert: fs.readFileSync('cert/server-cert.pem'),
3615
}
3716
},
17+
18+
/*
19+
************************************************************************
20+
*** The following options REQUIRE a redis database to function ! ***
21+
************************************************************************
22+
*/
23+
multiprocess: {
24+
enabled: false, // requires redis for IPC
25+
processes: 1
26+
},
27+
partyList: false,
28+
redis: {
29+
host: "127.0.0.1",
30+
port: 6379,
31+
//password: null
32+
},
3833

3934

4035
/*

package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,26 @@
1313
"author": "Lisa Croxford, Michael/HAZARDU5, Jerad Bitner, na/source83, oculushut, Tim Schwartz, JamesHagerman",
1414
"license": "MIT",
1515
"dependencies": {
16-
"byline": "^4.1.1",
1716
"body-parser": "*",
17+
"byline": "^4.1.1",
1818
"express": "^4.6.1",
1919
"finished": "^1.2.2",
2020
"http-string-parser": "0.0.5",
2121
"janus-method-ping": "^0.0.3",
2222
"janus-mysql-auth": "^0.0.2",
23-
"janus-mysql-userlist-official": "*",
23+
"janus-mysql-userlist-official": ">=0.0.11",
2424
"janus-mysql-popular": "*",
2525
"mysql": "^2.12.0",
2626
"npmlog": "^0.1.1",
2727
"optimist": "^0.6.1",
28+
"redis": "^2.6.3",
2829
"simplesets": "^1.2.0",
2930
"split-ca": "^1.0.0",
3031
"websocket-driver": "^0.6.4"
3132
},
3233
"devDependencies": {
3334
"chai": "^3.5.0",
35+
"janus-node-client": "0.0.1",
3436
"mocha": "^3.0.2",
3537
"superagent": "^2.2.0",
3638
"websocket": "*"

server.js

+20-249
Original file line numberDiff line numberDiff line change
@@ -1,262 +1,33 @@
11
/* global log */
2+
var cluster = require('cluster');
3+
var redis = require('redis');
4+
global.config = require('./config.js');
25

3-
var args = require('optimist').argv;
4-
global.config = require(args.config || './config.js');
5-
var net = require('net');
6-
var tls = require('tls');
7-
var events = require('events');
8-
var express = require('express');
9-
var fs = require('fs');
10-
var sets = require('simplesets');
11-
var mysql = require('mysql');
12-
var bodyParser = require('body-parser');
13-
// websocket requires
14-
var websocket = require('websocket-driver');
15-
var parser = require('http-string-parser');
16-
var WebSocketStream = require('./src/WebSocketStream');
17-
18-
global.log = require('./src/Logging');
19-
20-
var Session = require('./src/Session');
21-
var Room = require('./src/Room');
22-
var Plugins = require('./src/Plugins');
23-
24-
function Server() {
25-
var d = new Date();
26-
this._sessions = new sets.Set();
27-
this._rooms = {};
28-
this._userList = Array();
29-
this._partyList = {};
30-
this._plugins = new Plugins(this);
31-
32-
}
33-
34-
Server.prototype.getRoom = function (roomId) {
35-
if (this._rooms[roomId] === undefined) {
36-
this._rooms[roomId] = new Room(roomId);
37-
}
38-
39-
return this._rooms[roomId];
40-
};
41-
42-
// ## Check if username is in use ##
43-
Server.prototype.isNameFree = function (name) {
44-
45-
var free = true;
46-
this._sessions.each(function (s) {
47-
if (s.id === name) {
48-
free = false;
49-
}
50-
});
51-
return free;
52-
};
53-
54-
// ## Start Socket Server ##
55-
Server.prototype.start = function (callback) {
6+
if (cluster.isMaster && global.config.multiprocess.enabled) {
7+
var numCPUs = global.config.multiproccess.processes;
8+
console.log(`Starting ${numCPUs} workers`);
569
console.log('========================');
57-
console.log('Janus VR Presence Server');
10+
console.log('Janus VR Presence Server (clustered)');
5811
console.log('========================');
59-
log.info('Startup date/time: ' + Date());
60-
61-
console.log('See server.log for activity information and config.js for configuration');
62-
console.log('Log level: ' + config.logLevel);
12+
console.log('See config.js for configuration');
6313
console.log('Startup date/time: ' + Date());
64-
65-
this.server = net.createServer(this.onConnect.bind(this));
66-
this.server.listen(config.port, "::", function (err) {
67-
68-
if (err) {
69-
log.error('Socket Server error listening on port: ' + config.port);
70-
process.exit(1);
71-
}
72-
73-
log.info('Socket Server listening on port: ' + config.port);
74-
console.log('Socket Server listening on port: ' + config.port);
75-
76-
});
77-
78-
if (config.ssl) {
79-
80-
this.ssl = tls.createServer(config.ssl.options, this.onConnect.bind(this));
81-
this.ssl.listen(config.ssl.port, "::", function (err) {
82-
83-
if (err) {
84-
log.error('SSL Server error listening on port: ' + config.ssl.port);
85-
process.exit(1);
86-
}
87-
88-
console.log('SSL Server listening on port: ' + config.ssl.port);
89-
log.info('SSL Server listening on port: ' + config.ssl.port);
90-
91-
});
92-
}
93-
94-
if (config.startWebServer) {
95-
this.startWebServer();
96-
}
97-
if (callback && typeof(callback) == "function")
98-
callback();
99-
};
100-
101-
102-
// ## start web server ##
103-
Server.prototype.startWebServer = function () {
104-
var http = require('http'),
105-
https = require('https');
106-
var self = this;
107-
108-
this.ws = express();
109-
110-
this.ws.use(bodyParser.json());
111-
var router = express.Router();
112-
113-
console.log('starting web server on port ' + config.webServerPort);
114-
115-
if (global.config.hookPlugins.hasOwnProperty('enter_room') &&
116-
global.config.hookPlugins.enter_room.plugins.indexOf('janus-mysql-popular') > -1)
117-
{
118-
this._conn = mysql.createPool({
119-
host : config.MySQL_Hostname,
120-
user : config.MySQL_Username,
121-
password : config.MySQL_Password,
122-
database : config.MySQL_Database
14+
var redisClient = redis.createClient(global.config.redis);
15+
redisClient.del('userlist:multi');
16+
redisClient.del('partylist:multi');
17+
for (var i = 0; i < numCPUs; i++) {
18+
var child = cluster.fork();
19+
child.on('exit', () => {
20+
redisClient.hdel('userlist', child.process.pid);
21+
redisClient.hdel('partylist', child.process.pid);
12322
});
124-
125-
router.get('/getPopularRooms', function (req, res) {
126-
var limit = parseInt(req.query.limit, 10) || 20,
127-
offset = parseInt(req.query.offset, 10) || 0,
128-
orderBy = req.query.orderBy || "weight",
129-
desc = (req.query.desc && req.query.desc === "true") ? "DESC" : "",
130-
contains = req.query.urlContains ? "%" + req.query.urlContains + "%" : "%";
131-
var sql = "SELECT roomName, url as roomUrl, count, weight, UNIX_TIMESTAMP(lastSeen) as lastEntered, thumbnail FROM `popular` WHERE url LIKE ? ORDER BY ?? "+desc+" LIMIT ?,?";
132-
this._conn.query(sql, [contains, orderBy, offset, limit], function(err, results) {
133-
if (err) {
134-
console.log(err);
135-
res.json({"success": false, "data": [{"error": "Error querying the DB"}]});
136-
return;
137-
}
138-
139-
res.json({"success": true, "data": results});
140-
})
141-
}.bind(this));
142-
143-
router.post('/addThumb', function (req, res) {
144-
data = req.body;
145-
if (!data['token'] ||
146-
data['token'] !== global.config.popularRooms.masterToken)
147-
return res.json({"success": false, "data": [{"error": "Invalid token"}]});
148-
if (!data['roomUrl'] || !data['thumbnail'])
149-
return res.json({"success": false, "data": [{"error": "Must POST roomUrl and thumbnail parameters"}]});
150-
var roomUrl = data['roomUrl'],
151-
thumbnail = data['thumbnail'];
152-
var sql = "UPDATE popular SET thumbnail = ? WHERE url = ?";
153-
this._conn.query(sql, [thumbnail, roomUrl], (err, results) => {
154-
if (err) {
155-
console.log(err);
156-
return res.json({"success": false, "data": [{"error": "Error querying the DB"}]});
157-
}
158-
return res.json({"success": true});
159-
});
160-
}.bind(this));
16123
}
162-
router.get('/log', function (req, res) {
163-
res.writeHead(200, {'Content-Type': 'text/plain', 'Content-Length': -1, 'Transfer-Encoding': 'chunked'});
164-
var logFile = fs.createReadStream('server.log');
165-
logFile.pipe(res);
166-
});
167-
168-
router.get('/get_partylist', function (req, res) {
169-
//console.log("get_partylist: " + JSON.stringify(self._partyList));
170-
res.json(self._partyList);
171-
});
17224

173-
router.get('/', function (req, res) {
174-
res.send(200, 'Nothing to see here ... yet');
175-
});
176-
177-
178-
this.ws.use(router);
179-
180-
//this.webserver = this.ws.listen(config.webServerPort, "::");
181-
182-
this.webserver = http.createServer(this.ws)
183-
this.webserver.listen(config.webServerPort, "::");
184-
log.info('Webserver (http) started on port: ' + config.webServerPort);
185-
this.webserverHttps = https.createServer(config.ssl.options, this.ws).listen(config.webServerPortHttps);
186-
console.log('webserver', typeof(this.webserverHttps));
187-
log.info('Webserver (https) started on port: ' + config.webServerPortHttps);
188-
console.log('Start Date/Time: ' + Date());
189-
};
190-
191-
Server.prototype.close = function(cb) {
192-
this.server.close( (err) => {
193-
if (config.startWebServer && this.webserver) {
194-
this.webserver.close( (err) => {
195-
return cb(err);
196-
});
197-
}
198-
else {
199-
return cb(err);
200-
}
25+
Object.keys(cluster.workers).forEach(function(id) {
26+
console.log('SPAWNED', cluster.workers[id].process.pid);
20127
});
20228
}
20329

204-
// ## action on client connection ##
205-
Server.prototype.onConnect = function (socket) {
206-
207-
var self = this;
208-
var addr = socket.remoteAddress;
209-
var s;
210-
211-
log.info('Client connected ' + addr);
212-
213-
// setup for websocket
214-
var driver = websocket.server({'protocols': 'binary'});
215-
socket.on('error', function (err) {
216-
log.error(addr);
217-
log.error('Socket error: ', err);
218-
});
219-
socket.on('close', function () {
220-
log.info('Client disconnected: ' + addr);
221-
if (s)
222-
self._sessions.remove(s);
223-
});
224-
225-
socket.once('data', function (data) {
226-
// try to parse the packet as http
227-
var request = parser.parseRequest(data.toString());
228-
229-
if (Object.keys(request.headers).length === 0)
230-
{
231-
// there are no http headers, this is a raw tcp connection
232-
s = new Session(self, socket);
233-
self._sessions.add(s);
234-
235-
// emit the first message so the session gets it
236-
socket.emit('data', data);
237-
}
238-
});
239-
240-
driver.on('connect', function () {
241-
if (websocket.isWebSocket(driver)) {
242-
log.info('Websocket connection:', addr);
243-
driver.start();
244-
245-
s = new Session(self, new WebSocketStream(driver, socket));
246-
self._sessions.add(s)
247-
248-
driver.on('error', function (err) {
249-
log.error(addr);
250-
log.error('Websocket error: ', err);
251-
});
252-
}
253-
});
254-
socket.pipe(driver.io).pipe(socket);
255-
};
256-
if (require.main === module) {
257-
(new Server()).start();
258-
}
25930
else {
260-
module.exports = Server;
31+
var Server = require("./src/Server.js");
32+
(new Server()).start();
26133
}
262-

src/Logging.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
var npmlog = require('npmlog');
23
var args = require('optimist').argv;//Noted that this is deprecated. TODO: replace with either minimist or yargs
34
var onFinished = require('finished');

0 commit comments

Comments
 (0)