Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon
14. March 2012

 

 

In part 1 we look at a very basic example of sending information across the wire from SFML to NodeJS.  In part 2 we looked at JSON encoding high score data, that is then sent via UDP socket to a Node server.  Now we complete the process, by having Node return high score data back to your C++ SFML app.  By the end of this section, you will have all the code you need for a functioning ( if somewhat fragile ) high score client and server.

 

 

First we look at the C++ code.  Things are very similar to part 2, except code has been refactored a bit for re-use.  Lets look now.

 

 

#include "SFML/Network.hpp" #include "JSON.h" #include <iostream> void MakePacket(const wchar_t* action, const wchar_t * name, const float score, sf::Packet &packet) { JSONObject data; data[L"action"] = new JSONValue(action); data[L"name"] = new JSONValue(name); data[L"score"] = new JSONValue(score); JSONValue * val = new JSONValue(data); data.clear(); std::wstring dataString = val->Stringify(); delete val; std::string notSoWide; notSoWide.assign(dataString.begin(),dataString.end()); packet.Append(notSoWide.c_str(),notSoWide.length()); } int main(int argc, char* argv[]) { sf::IPAddress ip("127.0.0.1"); sf::SocketUDP socket; sf::Packet packet; unsigned short port = 1000; unsigned short respPort = 1001; if(argc == 1) { //No arguments means program should retrieve scores and print them MakePacket(L"GetScores",L"",0.0f,packet); socket.Bind(respPort); socket.Send(packet,ip,port); char buffer[512]; // The buffer to store raw response data in sf::IPAddress respIP; // The ip address where the response came from size_t respSize; // The amount of data actually written to buffer // Now receive a response. This is a blocking call, meaning your program // will hang until a response is received. socket.Receive(buffer,512,respSize,respIP,port); socket.Close(); std::string respString(buffer,respSize); // Now lets turn the string back into JSON JSONValue * jsonHighScores = JSON::Parse(respString.c_str()); if(!jsonHighScores->IsObject()) { std::cout << "Something went wrong, not good."; return -1; } JSONObject root = jsonHighScores->AsObject(); if(root.find(L"Scores") != root.end() && root[L"Scores"]->IsArray()) { JSONArray scores = root[L"Scores"]->AsArray(); std::cout << "Current high scores:" << std::endl; for(int i = 0; i < scores.size();i++) { JSONObject curObj = scores[i]->AsObject(); std::wcout << "Name:" << curObj[L"Name"]->AsString(); std::cout << " High Score:" << curObj[L"Score"]->AsNumber() << std::endl; } } delete jsonHighScores; } else if(argc == 3) { MakePacket(L"AddScore", std::wstring(argv[1],argv[1] + strlen(argv[1])).c_str(), atof(argv[2]), packet); if(socket.Send(packet,ip,port) != sf::Socket::Done) { std::cout << "An error ocurred sending packet" << std::endl; } socket.Close(); } else { std::cout << "Invalid usage, proper format is player name then score, for example:" << std::endl; std::cout << "Or run with no arguments to get a list of scores returned" << std::endl; std::cout << "Scoreboard \"Player Name\" 42" << std::endl; return -1; } return 0; }

Click here to download Scoreboard.cpp

 

 

We have reorganized slightly to use a set of if/else’s based on the number of parameters passed in.  The common code between the two handled conditions has been moved to the method MakePacket(), which contains nothing new from last part.  It’s the section where argc == 1 ( which means there were no parameters specified, as argc at 1 represents the executables name ) that we are interested in.  If the user runs the application from the command line with no parameters, we want to fetch the high scores from the server.

 

 

The request process is the same, although we are passing the action GetScores instead.  One key difference is we Bind our port.  Think of this action as say “Yo!  This port is ours!”.  Only one application per computer can have access to a given port.  This is why we run our server on 1000, but then bind our response port on 1001, since client and server on running on the same machine.  Unlike when we add a score, for GetScores we want to listen for a response, which is what we do with socket.Receive().  Keep in mind, this action blocks, so your program wont be able to continue until this is done.  There are alternatives ( like Selector ) if this behavior is undesirable.

 

 

Now assuming Receive() worked correctly ( which in the Real World™ you shouldn’t!), buffer will be full of our JSON encoded string, while respSize will represent the amount of buffer that was actually used.  Using these two pieces of information, lets create a string from only the meaningful bits ( the rest of the buffer will be full of gibberish ).  We now turn our string back into JSON ( in production code, I would extend the JSON library to work with standard strings ), and check to see if it is a valid JSON object, error out if it’s not.  Now it’s a matter of parsing out the JSON into meaningful form.  Remember that a JSONObject is actually just a STL map of key/value pairs, so we find our array of type JSONArray named Scores.  Each item in that array is in turn another map, so we loop through them all find the value for “Name” and “Score”, turn them back into their native type and display them out to the console.  And that’s about it.

 

 

Now lets take a look at the server side of things.  Here we made much less invasive changes, so lets just look at what has changed.  To see the fully modified server.js click here.  I no doubt forgot a small change here or there, I always do!

 

 

First we add another condition to the switch statement as follows:

 

case "GetScores": console.log("Get Scores called"); // Turn highscores back into a JSON encoded string var highScoreAsString = JSON.stringify(highScores); // Create a buffer to hold that string var responseBuffer = new Buffer(highScoreAsString.length); // Write the string to the buffer responseBuffer.write(highScoreAsString); // Send it back to the client, using the addressing information // passed in via rinfo server.send(responseBuffer,0,responseBuffer.length, rinfo.port,rinfo.address, function(err, sent){ if(err) console.log("Error sending response"); else console.log("Responded to client at " + rinfo.address + ":" + rinfo.port ); }); break;

 

 

The code is pretty much documented, the nutshell version is, we turn our high score information into a JSON encoded string, make a buffer large enough for the string, copy the string to the buffer, then call send to the address and port the request came from, as defined in rinfo.

 

 

Then, mostly because we can, we use Node to create an extremely simple high score web server, so if you visit this site with your browser you can get a current list of high scores.  Look how laughably easy that task is!

 

 

// Now, lets show off a bit, with the worlds simplest web server. // Dynamically creates webpage showing current high scores. var webServer = require('http'); webServer.createServer(function(req,res){ res.writeHead(200, {'Content-Type': 'text/html'}); res.write("<html><body><h1>High Scores</h1><ul>"); for(i=0;i < highScores.Scores.length;++i) { res.write(highScores.Scores[i].Name + "&nbsp;&nbsp;" + highScores.Scores[i].Score + "<br />"); } res.write("</ul></body></html>"); res.end(); }).listen(80);

 

 

 

And… that’s it!  So now, lets take a look at things in action.  Open a command prompt and launch your server.js in node.  Then in another command prompt run scoreboard.exe, like this:

 

 

Here is using Scoreboard from the command line:

image

 

 

And here is the server handling these requests:

 

image

 

 

Finally, fire up a web browser and hit 127.0.0.1 ( assuming you don’t have another webserver running on your machine ):

 

image

 

 

It ain’t pretty, but it is a fully functioning high score server.  All you need to do is add a layer of security, harden things a bit with an iota of fault tolerance, pretty things up a bit and you are set.

 

 

As always, you can download the complete project zip here.

Programming


28. February 2012

 

 

Picking up where our last section left off, by the end of this chapter you will be able to send a high score across the wire and process it in node.  In order to do this, we need to create a common format both our client and server understand.  In this particular case we are going to use JSON.  If you have done any recent web program, you are probably familiar with JSON already.  Basically JSON is a micro-format designed for transferring data on the web with a lighter footprint than XML.  All told it is a pretty simple format, here is the JSON we are going to use for storing high scores:

 

 

{ "Scores" : [ {"Name" : "Mike", "Score" : 2}, {"Name" :"Bob", "Score" : 14}, {"Name" :"Steve", "Score" : 12}, {"Name" :"John", "Score" : 10}, {"Name" :"Henry", "Score" : 8} ] }

Click here to download Highscores.txt

 

This JSON represents an object named “Scores” composed of an array of 5 objects that in turn are made up of a string field “Name” and a integer field “Score”.  Javascript and JSON go together like peanut butter and jam, but what about C++?  Well, you could encode your data into a string with very little effort ( one of the big advantageous of JSON ), but “little effort” is still effort, and I’m a lazy guy!  Therefore we are going to use an existing library.  I wanted a light weight and extremely simple JSON library, so I went with the aptly named SimpleJSON.  Installation really couldn’t be simple, just add the 4 cpp files ( 2 headers, 2 source ) to your project and you are done.

 

 

Now lets take a look at our SFML client.  It is going to be a simple command line utility for now, from a dos prompt simply pass in the name and high score as parameters, and it will send them across to the node server.  Lets take a look at Scoreboard.cpp:

 

#include "SFML/Network.hpp" #include "JSON.h" #include <iostream> int main(int argc, char* argv[]) { if(argc != 3) { std::cout << "Invalid usage, proper format is player name then score, for example:" << std::endl; std::cout << "Scoreboard \"Player Name\" 42" << std::endl; return -1; } sf::IPAddress ip("127.0.0.1"); sf::SocketUDP socket; sf::Packet packet; JSONObject data; data[L"action"] = new JSONValue(L"AddScore"); data[L"name"] = new JSONValue(std::wstring(argv[1],argv[1] + strlen(argv[1]))); data[L"score"] = new JSONValue(atof(argv[2])); JSONValue * val = new JSONValue(data); data.clear(); std::wstring dataString = val->Stringify(); delete val; std::string notSoWide; notSoWide.assign(dataString.begin(),dataString.end()); packet.Append(notSoWide.c_str(),notSoWide.length()); unsigned short port = 1000; if(socket.Send(packet,ip,port) != sf::Socket::Done) { std::cout << "An error ocurred sending packet" << std::endl; } socket.Close(); return 0; }

Click here to download scoreboard.cpp

 

 

One annoyance of the library I chose for JSON is it works with UTF-8 wide strings, but the string that we send we want encoded as standard ascii, so there is a bit of gunk as we create the JSON object using wide character strings, then after turning it into a JSON string, we encode it back to ascii.  Otherwise the code is quite straight forward.

 

 

First we verify we got the proper number of command line arguments, declare our various SFML and JSON related variables.  We are setting the ip address to 127.0.0.1, which is the loopback address, or the equivalent of saying “this machine”.  Next we build up our JSON string.  If you have worked with XML before, the process will be very familiar. We create a JSONObject named data, which is essentially a map of key value pairs of other JSONValues.  When then populate it with our data, then in turn use it as the parameter in creating a new JSONValue.  All the heavy lifting is done in JSONValue’s constructor.  Stringify() is the method that does the actual re-encoding returning a std::wstring.  Of course, we actually want a std:string, so we create one.  Obviously in time sensitive code, we would alter SimpleJSON to use std::string instead.  Our end result is a JSON string that looks like this:

 

{"action":"AddScore","name":"Bob Dole","score":23}

 

 

Now that we have our data in JSON encoded string format, it’s time to send it.  We simply append our string data to our packet and send it using our Socket.  If an error occurs, report it.  Otherwise, we are done, close our Socket and exit.  If you strip away all the wide character string annoyances, the process is actually quite straight forward.

 

 

Now lets take a look at the Node side of things.  The code is fairly long, so instead of walking through it I have simply commented it.  If you have any questions not covered by the comments, fire away.  So here is the contents of Server.js

 

var dgram = require('dgram'), fileSystem = require('fs'), highScores, server; //Load high scores from file fileSystem.readFile("HighScores.txt", 'utf8', function(err,data){ if(err){ //Error occurred loading file, spit out error message then die console.log("Error occurred loading file"); process.exit(); } console.log("Loading high scores from file"); try{ // use JSON to turn file contents back into a Javascript array object highScores = JSON.parse(data); }catch(e) { // Exception occurred parsing file contents as JSON, error out and die. console.log("Exception occured parsing data"); process.exit(); } // Now sort the high scores by score, high to low highScores.Scores.sort(function(a,b){ return b.Score - a.Score; }); // Display the sorted high scores to the console console.log(highScores); }); //Alternative way to read file in NodeJS //file.on("error",function(exception){ // process.exit(); // } //); //file.on("data",function(data){ // fileData = data; // } //); //file.on("close",function(){ // highScores = JSON.parse(fileData); //}); //Create a UDP socket server = dgram.createSocket('udp4'); console.log("Socket created"); // Add a handler for incoming traffic on the socket. This will be called each time something connects to the socket server.on("message",function (msg,rinfo) { //console.log(parseInt(msg).toString()); console.log(rinfo); // SFML sends two packets, one with the size of the following packet ( as a 4 byte number ) // We don't need it, so as a crude-hack, we ignore any 4 byte packets if(rinfo.size != 4) { console.log("Received message:" + msg.toString()); // Socket data comes in as a JSON encoded array of objects, turn back into a JS object var jsonData,i; try{ jsonData = JSON.parse(msg); } catch( exception ) { console.log("Invalid JSON request received"); return; // Non lethal error, just stop processing packet } // The action parameter determines what you should do with this packet switch(jsonData.action) { // action==AddScore, add the score to the highscore array if it's higher than an existing score case "AddScore": console.log("AddScore called\n"); // Make sure highscore has been initialize... order can be a weird thing in node if(highScores != undefined){ // Loop through current highScores ( which should be sorted ) // and insert score if a lower match found for(i=0;i < highScores.Scores.length;++i) { if(highScores.Scores[i].Score < jsonData.score){ highScores.Scores.splice(i,0,{"Name" : jsonData.name, "Score" : jsonData.score}); console.log("Inserted highscore by: " + jsonData.name); break; // match found, stop looping } } } // Display newly created highscore array console.log(highScores.Scores); break; } } // // }); // Called when socket starts listening for packets. besides logging, currently serves no purpose server.on("listening", function () { var address = server.address(); console.log("server listening " + address.address + ":" + address.port); }); // Finally, bind the server to port 1000. 1000 was randomly chosen. Think of this as saying GO! // Now we are listening for UDP connections on port 1000 server.bind(1000);

Click here to download server.js

 

 

Now start the server at the command line ( node server.js ) and run the client from a different command line.  It should look like this:

 

image

 

 

As you can see, we are successfully sending data from SFML over a socket to our node based server.  In the next part, we will look at sending data the other way.

 

 

You can download the complete project right here.  The scripts are in a sub-folder named NodeJS.

 

 

Click for part 3

Programming


8. February 2012

 

The last week or so I’ve been looking at using Node for server side game programming and I have been quite impressed.  I have found a few things quite frustrating ( the documentation is extremely sparse and tooling support is… iffy ) but code wise everything has worked about exactly how I expect it.

 

That said, I ran into my first glaring bug and it is an annoying one.  In my current code, I want to write out some memory to file when my code exits, I imagine a fairly common event.  At first it seems like a remarkably simple thing to do.  Every node application has a process object, and process has an exit event.  Therefore handling on exit should be as simple as:

 

process.on('exit', function () { //handle your on exit code console.log("Exiting, have a nice day"); });

 

*Should* being the operative word.  Reality is, this doesn’t work.  The event simply does not fire on CTRL+C.   Hmmm.  So I decide to throw process.exit() at the end of my script and BAM, the on exit event is fired.  So it appears that contrary to the documentation, the CTRL+C event doesn’t raise an exit event, or so I thought.

 

Alright, no big deal, I’ll just trap the SIGINT event raised from CTRL+C and handle exit then.  Ohh… that didn’t work out well.

 



C:\Users\Mike\workspace\NodeDev\src>node server.js

node.js:218
        throw e; // process.nextTick error, or 'error' event on first tick
              ^
Error: No such module
    at EventEmitter.<anonymous> (node.js:403:27)
    at Object.<anonymous> (C:\Users\Mike\workspace\NodeDev\src\server.js:5:9)
    at Module._compile (module.js:434:26)
    at Object..js (module.js:452:10)
    at Module.load (module.js:353:32)
    at Function._load (module.js:310:12)
    at Array.0 (module.js:472:10)
    at EventEmitter._tickCallback (node.js:209:41)



Hmmm…

 

So off to Google I go!  After a few minutes I discover this tidbit.  Basically it’s a known issue, SIGINT isn’t raised on Windows and they claim it isn’t possible, which considering Python handles it just fine, I find a bit difficult to believe.  So, it is a currently unsolved issue, but hopefully it will be resolved in the future.

 

Considering that CTRL+C is the defacto way to shut down Node, I really hope this is resolved soon.  In the meanwhile, does anyone out there know an effective workaround?

 

 

Update

 

Stack Overflow came to my rescue on this issue.  User pimvdb posted this work around:

 

var tty = require("tty"); process.openStdin().on("keypress", function(chunk, key) { if(key && key.name === "c" && key.ctrl) { console.log("bye bye"); process.exit(); } }); tty.setRawMode(true);

 

It feels like a bit of a hack, but then, I suppose it is!  Hopefully this issue gets fixed in node eventually, but for now, here is an effective workaround.

Programming


31. January 2012

 

 

As I mentioned earlier, I have recently discovered Node.JS.  Quick version, Node is a server side Javascript engine, allowing you to easily create applications on the server.  Obviously you could use SFML on both the client and the server but many people find Javascript a much faster language to work with.  The libraries bundled with Node make this task extremely easy.

 

We are going to create a very simple “High score” system, allowing users to push their score to the server as well as get a list of the current high scores.

 

 

 

You may notice Node referred to as Node, Node.js and NodeJS, which is correct?  Beats the hell out of me!  I frankly just use them all, so if you notice me switching terms, remember they are all referring to the same thing.  I am assuming you are using Windows 7 and Visual Studio for this tutorial, but there are no reasons you can’t use different OSes or IDEs.

 

 

First off, lets get Node installed, which you can download here.  Run the installer and node will be installed to your machine.  Really that’s about it.  Optionally you can add the install directory to your PATH environment variable, in my case it was C:\Program Files(X86)\nodejs, this will save you a ton of key strokes later and for the following instructions I will assume you have performed this step.

 

 

Now that you have installed node, we can create our server; you are about to see how laughably easy this is using Node.  Create a file server.js in your editor of choice, in my case I am using JetBrain WebStorm ( on a 30 day trial ) and have been impressed so far, but you can use any text editor including Notepad.  In server.js add the following code:

 

var dgram = require('dgram'); var server = dgram.createSocket('udp4'); console.log("Socket created\n"); server.on("message",function (msg,rinfo) { console.log("Received message"+ msg.toString()); }); server.bind(9000);

 

 

And… that’s it.  A perfectly functioning UDP server, not that it does much yet.  To run the code, in a command prompt, change to the directory you saved server.js to and simply type “node server.js”.  To stop node from running, press CTRL + C twice.

 

dgram is a built in Node library for creating datagram sockets.  When we create the socket with tell it to use udp4, that is to say UDP connection for IP v 4.  Then we add an on “message” handler that for now simply echos whatever data it receives.

 

 

Now lets make a C++ client to talk to our server.  If you are unfamiliar with how to configure SFML, take a look at part 1 of my C++ with SFML tutorial.  In this case we aren’t going to need to link the graphics or audio libraries, which greatly cuts down the size of our project.  If you have trouble setting up a project or simply don’t want to, you can download a pre-configured project here.  Simply open the solution and run.

 

Once you have created and configured a new project, lets add our code.  In my case I created a file called Scoreboard.cpp and added the following code.

 

#include "SFML/Network.hpp" int main(int argc, char* argv[]) { if(argc <2) return-1; sf::IPAddress ip("127.0.0.1"); sf::SocketUDP socket; sf::Packet packet; packet.Append(argv[1],strlen(argv[1])); socket.Send(packet,ip,9000); return0; }

 

 

Extremely simple code again.  The app is simply going to send a string you specify on the command line over a UDP socket, so first we check to make sure you specified a string. Then you create the IP address that represents your server’s location.  In this case you are using 127.0.0.1 which is a special reserved IP address called a loopback, which basically means “this machine”.  In an actual production environment, you would use an external IP address or DNS entry like “gameserver.mydomain.com”.

 

Next we declare our Socket and  Packet.  Think of a Packet as the “stuff” we are sending across the wire.  Next we fill that picket with the string the user passed in on the command line.  The Socket represents the connection to the remote server.  When we say socket.Send() we are saying send packet, on ip IpAddress using port 9000.  Finally we simply exit.

 

You can now run this from the command line using ScoreBoard.exe “The text you want to send”  <—The quotes are critical, or it will end up looking like multiple parameters instead of a single string.  Additionally you could specify the string in your debug settings, then you can simply press F5 to run your code.  To set this value, right click your solution and select Properties, then on the left hand side choose “Command Arguments” and specify your string (quoted again ) there, like this:

 

image

 

 

Now your primitive client and server are up and running.  Start your server first.  Open a command prompt, CD to the directory you created server.js in and type “node server.js” ( no quotes ).   Note, if you didn’t set your PATH to include the path to nodejs, this wont work and instead you will have to fully resolve the path to node.  Now run your SFML application, either using F5 if you specified a Command Argument, or from the command line.

 

Now you should see:

 

image

 

 

Congratulations!  You just created a fully function client and server!  You may have noticed we actually received two socket connections there, an empty one, then our string.  What’s going on here?  Well, SFML sends Packets in two parts, the first contains only the size, the second contains the actually data.  Why?  Beats me.  Not really a big deal, as you will soon see.

 

 

Now, in the next part we will actually make them do something!

 

 

Click here to download the pre-configured Visual Studio project.

Click here to download Server.js.

 

 

Click for part 2

Programming


GFS On YouTube

See More Tutorials on DevGa.me!

Month List