Showing posts with label socket.io. Show all posts
Showing posts with label socket.io. Show all posts

Wednesday, October 22, 2014

Socket.io with Express

Today I introduced the world to 2-player Solitaire. It was the joke in today's comic, but I thought that it might work in real life too. At the very least, it helps add dimension to the joke.

In it, you and a friend are looking at the same card table. For each valid card placement, you get 1 point. Your goal is to put the cards on valid placements before your opponent does and thereby score the most points.

I've been working on it for about 3 weeks now, which is why I haven't had time to put much code in this blog. But now that it's ready (don't hesitate to send a bug report if you find anything weird) I have time to talk about different parts of it on a technical level.

It is a web application, using just HTML5 and JavaScript. It is playable on both mobile devices and your desktop. Since the main premise is a shared game board, I used Node and Socket.io to share the real-time game data between two web browser clients. I've been using Socket.io for years now, but this was only the second time I've used the 1.0 release combined with the Express framework. The first time I struggled with some things, so I thought I'd share the basic application outline that I came up with to make your life easier.

I'm using Express 4.8.7 and Socket.io 1.0.6 in this project. To set up your application to handle both "normal" HTTP traffic and the various Socket.io communication types, you have to deviate slightly from a typical Node/Express setup.

var express = require("express");
var http = require("http");

var app = express();
var server = http.Server(app);
var io = require("socket.io")(server);

//------------ Socket.io stuff

io.on("connection", function(socket) {
 
    // set up handlers for different events here
    socket.on("some_event", function(data) {
        // do something
    });

});

//------------ web application routes

app.get("/some/path", function(req, res, next) {
 
    res.send("some response");
 
});

// ------------ start the listening

var srv = server.listen(5000, function() {
    console.log("listening on port %d", srv.address().port);
});

The main difference between this configuration and a more typical Express app is what you see on line 4. You have to require the "http" module explicitly, and use it to create a Server instance using your Express app instance. You then pass that Server to Socket.io as I do on line 6.

After that, use your io and app objects to set up your application behavior via event handlers and routes. At the end, start your Server listening. Note that on line 29, it is the http server instance you use to start the listening instead of the Express app object.

Try that, and you're off to a good start. As always, the source code for the entire game is available on GitHub. Check it out if you want to see how I did everything else, or keep reading here and I'll talk about other parts of it soon.

Amphibian.com comic for 22 October 2014

Saturday, February 23, 2013

Node.js and Socket.io Allow Frogs to Play Together

It can be lonely out there for frogs. They need to interact with other frogs, and not just on Facebook. In the real world. But this isn't the real world, it's a virtual world that is supposed to be sorta like the real world. For frogs. Let's just say that if you want to connect multiple client browsers together to create some sort of online frog collaboration environment, there is no better way than with Node.js and Socket.io.

FFZ is where I play around with browser tech like WebSockets, Canvas, and HTML5 Audio. I like to make things with frogs in them. In this application, you can move a frog around a small world filled with trees, flowers, rocks, and streams. But it's even more fun when another person is on the site at the same time you are - you'll play in a collaborative environment. You will see their frog move in real-time and they'll see yours. It amuses my young children for hours. Just press "R" to ribbit.

How do I hook everybody's browser up together? Well first of all, don't expect it to work with Internet Explorer. Seriously. IE, how can you even show your face in public anymore? Chrome and Firefox work like browsers should. I'm talking WebSockets. Sure, you can do WebSockets the old-fashioned way...but Socket.io is a great utility that abstracts much of the complexity away from you. It's built for Node.js, the awesome server-side Javascript engine. Node allows you to hook up tons of clients together with very little overhead because of its event-driven IO model.

My FFZ "server" is a Node.js program that listens for events from the client browsers running the application and publishes events out to the clients as well. When one frog moves, it publishes its changed position to the server, which turns around the publishes it to all the other browsers so they can update the positions of that frog on their screens. And thanks to Socket.io, it's extremely simple.

Here's my Node server code:


var http = require("http");
var sockio = require("socket.io");

var frogs = [];

var io = sockio.listen(8080);
io.configure(function() {
    io.set('log level', 2);
    io.set('transports', [
      'websocket'
    ]);
});

io.sockets.on("connection", function(socket) {
      
    socket.on("frogmove", function(fdata) {
        socket.broadcast.emit("fm", fdata);
    });

    socket.on("objmove", function(odata) {
        socket.broadcast.emit('sm', odata);
    });
      
    socket.on("ribbit", function(fdata) {
        socket.broadcast.emit("rbbt", fdata);
    });
      
    socket.on("startup", function(msg) {
        console.log("frog " + msg.fid + " connected");
        socket.broadcast.emit("newfrog", msg.fid);
 socket.set("frog id", msg.fid);
 frogs.forEach(function(i) {
            socket.emit("newfrog", i);
 });
 frogs.push(msg.fid);
    });
      
    socket.on("disconnect", function() {
        socket.get("frog id", function(err, fid) {
            console.log("frog " + fid + " disconnected");
     socket.broadcast.emit("byefrog", fid);
         if (frogs.indexOf(fid) != -1) {
                frogs.splice(frogs.indexOf(fid), 1);
            }
 });
    });

});


So hopefully the first part of the code is fairly self-explanatory. In the configuration of the socket listener, I set the "transports" to be just "websocket" because I don't want it to automatically downgrade to Flash or long-polling. Those things are fine I suppose but I wanted to use FFZ to try out WebSockets. (I actually did try Flash and long-polling with FFZ. Flash is ok but long-polling just doesn't work with an application like this - there was just too much data being transmitted and the user experience was poor.)

The second block is where the real magic happens. On a "connection" event, we set up the event listeners for that socket. The first three events are very simple. When a socket, which represents a connection to a client, gets a "frogmove" event, for example, it just turns around and broadcasts it out again. The "broadcast" method sends a message to all known sockets except oneself - so the sender isn't going to get the message back but every other socket that Socket.io knows about will get it. This is how I handle the simple events - moving frogs and objects and making frogs ribbit.

The "startup" event gets a little more complicated. When a new client connects, they need to let all the other clients know to add them to their screens, but the new client also needs to know where all the existing frogs are so they can be added to their screen. So here I use the "socket.broadcast.emit" method again to tell everyone else about the new frog - and then I use "socket.emit" to send a "newfrog" event back to the socket that just joined. This is a bit of a trick because these aren't exactly new frogs (they existed before the new client joined) but the client will behave the same way as if a new frog joined - by adding the other frog(s) to the screen and tracking them for future updates. Finally, I add the new client id to the frogs array so the server knows about this frog in the future, at least until it disconnects.

That brings me to the "disconnect" event. Again, I use the broadcast to tell everyone else to stop displaying and tracking the frog that just left. I also then remove it from the frogs array.

So there you have it. A server in Node.js that keeps all the frogs playing nicely together. Easy peasy lemon squeezy. Now let's take a gander at the client code too. It's almost as simple.

The first thing to do in the client is to load the Socket.io code. But it serves itself! That's right, when Socket.io wants a sandwich it makes it and brings it to itself. You just source the JavaScript right from your Node.js server. So for example, if you look at the server code above you will notice that I'm running on port 8080 and I serve everything from amphibian.com. So I have this in my HTML:

<script type="text/javascript" src="http://www.amphibian.com:8080/socket.io/socket.io.js"></script>

Bam! You've got the Socket.io client now! By the way, you can actually serve the client manually if you need to for some obscure reason. See https://github.com/LearnBoost/socket.io-client

This is what I do to get the socket connected and set up the client's frog to publish events:


<script type="text/javascript">

sckt = io.connect("http://www.amphibian.com:8080");

sckt.on("connect", function() {

    console.log("socket connected");
    frog.bind('move', function(fdata) {
        // fdata will have the frog's id and position info
        sckt.emit("frogmove", fdata);
    });

    frog.bind('ribbit', function(fdata) {
        // fdata will have the frog's id and position info
        sckt.emit("ribbit", fdata);
    });

});

</script>


The "connect" call is fairly simple, you just give it the URL. Then you get up your connect event callback. When the "connect" event occurs, I bind some events on the client's frog to functions which will emit data over the socket. (I use MicroEvent.js to do this. You can read more about that here. It is awesome.) This works pretty much the same way as on the server side. Calling "emit" with an event name and some data sends that event+data to the server where (hopefully) there is a callback set up listening for that event. So in the code above, I emit the "frogmove" event and the "ribbit" event, both of which are listened for in the server code shown previously.

Remember that the server essentially rebroadcasts events from one client to all the other clients using custom event names. I'll just show you one here, the "rbbt" event, but the others are all very similar.


<script type="text/javascript">

sckt.on("rbbt", function(fdata) {
    for (var f = 0; f < otherfrogs.length; f++) {
        if (otherfrogs[f].uid == fdata.id) {
            otherfrogs[f].ribbit();
        }
    }
});

</script>

If you look at the server code and the first part of the client setup, you'll see that when a client's frog ribbits the event is published over the socket as the "ribbit" event. The server gets that and republishes to all other clients as a "rbbt" event. I removed the vowels. Servers don't like to broadcast vowels. No, that statement is completely false. Really I just wanted a slightly different event name so I could tell the client and server events apart. Anyway, this is an example of the client listening for the "rbbt" custom event. If this client gets such an event it means that some other client's frog is making a sound and so we should show that frog opening his mouth and play the sound as well. The data that comes in will have the other frog's id in it so we just look for which frog it should be and then call the "ribbit" method on that frog.

Couldn't be easier, right?

And there's even better news! What if you wanted to hook up your OUYA game to a Node.js server using Socket.io? I know I do! There is a Java client available for Socket.io that works on Android. So technically you could hook up web browsers, mobile phones, game consoles, and possibly even refrigerators all to the same server to share data in real-time. Yes, Node.js most assuredly rocks.