78

I'm fairly new to node.js and I've found its quite complicated separating a project into multiple files as the project grows in size. I had one large file before which served as both a file server and a Socket.IO server for a multiplayer HTML5 game. I ideally want to separate the file server, socket.IO logic (reading information from the network and writing it to a buffer with a timestamp, then emitting it to all other players), and game logic.

Using the first example from socket.io to demonstrate my problem, there are two files normally. app.js is the server and index.html is sent to the client.

app.js:

var app = require('http').createServer(handler)
  , io = require('socket.io').listen(app)
  , fs = require('fs')

app.listen(80);

function handler (req, res) {
  fs.readFile(__dirname + '/index.html',
  function (err, data) {
    if (err) {
      res.writeHead(500);
      return res.end('Error loading index.html');
    }

    res.writeHead(200);
    res.end(data);
  });
}

io.sockets.on('connection', function (socket) {
  socket.emit('news', { hello: 'world' });
  socket.on('my other event', function (data) {
    console.log(data);
  });
});

index.html:

<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io.connect('http://localhost');
  socket.on('news', function (data) {
    console.log(data);
    socket.emit('my other event', { my: 'data' });
  });
</script>

To separate file server and game server logic I would need the function "handler" defined in one file, I would need the anonymous function used a callback for io.sockets.on() to be in another file, and I would need yet a third file to successfully include both of these files. For now I have tried the following:

start.js:

var fileserver = require('./fileserver.js').start()
  , gameserver = require('./gameserver.js').start(fileserver);

fileserver.js:

var app = require('http').createServer(handler),
    fs = require('fs');

function handler (req, res) {
  fs.readFile(__dirname + '/index.html',
  function (err, data) {
    if (err) {
      res.writeHead(500);
      return res.end('Error loading index.html');
    }

    res.writeHead(200);
    res.end(data);
  });
}

module.exports = {
    start: function() {
        app.listen(80);
        return app;
    }
}

gameserver:

var io = require('socket.io');

function handler(socket) {
    socket.emit('news', { hello: 'world' });
    socket.on('my other event', function (data) {
        console.log(data);
    });
}

module.exports = {

    start: function(fileserver) {       
        io.listen(fileserver).on('connection', handler);
    }

}

This seems to work (the static content is properly served and the console clearly shows a handshake with Socket.IO when the client connects) although no data is ever sent. It's as though socket.emit() and socket.on() are never actually called. I even modified handler() in gameserver.js to add console.log('User connected'); however this is never displayed.

How can I have Socket.IO in one file, a file server in another, and still expect both to operate correctly?

stevendesu
  • 15,753
  • 22
  • 105
  • 182
  • 4
    do you know the express js framework? http://expressjs.com/ it's great and really helps you structure your application. there are a ton of examples on github (https://github.com/visionmedia/express/tree/master/examples) maybe there's something that can help you with your problem... – Philipp Kyeck Mar 18 '12 at 23:00
  • 1
    @pkyeck: I'm reading up on expressjs now to try and figure out how it can benefit me, but so far it seems more complicated than what I need. All I really want is to separate my logic for the game server and the file server into two separate files then have a third file which properly starts both servers. – stevendesu Mar 19 '12 at 15:36
  • did you have the time to check out my "new" answer? – Philipp Kyeck Mar 21 '12 at 09:55
  • @pkyeck I've looked at it, although it seems like it's only masking the problem and not solving it. Not sockets.js is the ONE MASSIVE FILE instead of app.js. I'd like to have no single massive files but instead separate files for every function. More and more node.js seems like more of a hassle than a blessing. – stevendesu Mar 21 '12 at 13:33

4 Answers4

95

In socket.io 0.8, you should attach events using io.sockets.on('...'), unless you're using namespaces, you seem to be missing the sockets part:

io.listen(fileserver).sockets.on('connection', handler)

It's probably better to avoid chaining it that way (you might want to use the io object later). The way I'm doing this right now:

// sockets.js
var socketio = require('socket.io')

module.exports.listen = function(app){
    io = socketio.listen(app)

    users = io.of('/users')
    users.on('connection', function(socket){
        socket.on ...
    })

    return io
}

Then after creating the server app:

// main.js
var io = require('./lib/sockets').listen(app)
Ricardo Tomasi
  • 34,573
  • 2
  • 55
  • 66
6

i would do something like this.

app.js

var app = require('http').createServer(handler),
    sockets = require('./sockets'),
    fs = require('fs');

function handler (req, res) {
  fs.readFile(__dirname + '/index.html',
  function (err, data) {
    if (err) {
      res.writeHead(500);
      return res.end('Error loading index.html');
    }

    res.writeHead(200);
    res.end(data);
  });
}

sockets.startSocketServer(app);
app.listen(80);

and sockets.js

var socketio = require('socket.io'),
        io, clients = {};

module.exports = {

        startSocketServer: function (app) {
                io = socketio.listen(app);

                // configure
                io.configure('development', function () {
                        //io.set('transports', ['websocket', 'xhr-polling']);
                        //io.enable('log');
                });

                io.configure('production', function () {
                        io.enable('browser client minification');  // send minified client
                        io.enable('browser client etag');          // apply etag caching logic based on version number
                        io.set('log level', 1);                    // reduce logging
                        io.set('transports', [                     // enable all transports (optional if you want flashsocket)
                            'websocket'
                          , 'flashsocket'
                          , 'htmlfile'
                          , 'xhr-polling'
                          , 'jsonp-polling'
                        ]);
                });
                //

                io.sockets.on('connection', function (socket) {
                        console.log("new connection: " + socket.id);

                        socket.on('disconnect', function () {
                                console.log("device disconnected");

                        });

                        socket.on('connect_device', function (data, fn) {
                                console.log("data from connected device: " + data);
                                for (var col in data) {
                                        console.log(col + " => " + data[col]);
                                }


                        });
                });
        }
};

i just copy&pasted some of my old code - don't really know what changed in the last versions of socket.io, but this is more about the structure than the actual code.

and i would only use 2 files for your purposes, not 3. when you think about splitting it up further, maybe one other file for different routes ...

hope this helps.

Philipp Kyeck
  • 18,402
  • 15
  • 86
  • 123
  • Right now it's just a file server and a socket.io server, however eventually I will also have game logic to determine player positions given movement updates and I will need to have lag minimization logic which looks at the ping of each client and estimates which game state they are currently in judging from the existing data. Having some logic to fast-forward time, some logic to rewind time, some logic to move players, some logic to handle network data, and some logic to handle files meant I wanted to separate EVERYTHING into different files, ideally. Not just the socket.io stuff. – stevendesu Mar 21 '12 at 13:23
  • it's just the beginning - with the files you posted. you can do more than one `var xxx = require('./xxx');` and split your app into multiple files. i was at the mongodb conf yesterday and someone from 10gen showed a game based on node/mongo/websockets (https://github.com/christkv/mongoman) he's sending BSON data over the socket and decodes the data on the client - makes for a faster communication between client/server ... maybe it's interesting for you!? – Philipp Kyeck Mar 21 '12 at 14:14
1

I have had a crack at this as well and I am fairly happy with the result. Check out https://github.com/hackify/hackify-server for source code.

Michael Dausmann
  • 4,202
  • 3
  • 35
  • 48
0

I've another solution. You can use require.js creating a module and pass "app" as an argument. Within the module you can start socket.io and organize your sockets.

app.js:

  var requirejs = require('requirejs');

  requirejs.config({
      baseUrl: './',
      nodeRequire: require
  });

  requirejs(['sockets'], function(sockets) {

    var app = require('http').createServer()
      , fs  = require('fs')
      , io  = sockets(app);

      // do something
      // add more sockets here using "io" resource

  });

In your socket.js module you can do something like this:

  define(['socket.io'], function(socket){
    return function(app){
      var server = app.listen(3000) 
        , io     = socket.listen(server);

      io.sockets.on('connection', function (socket) {
        console.log('connected to socket');

        socket.emit('news', { hello: 'world' });
        socket.on('my other event', function (data) {
          console.log(data);
        });

        // more more more

      });

      return io;
    }
  });

I hope help you with my contribution.

Marco Godínez
  • 3,440
  • 2
  • 21
  • 30