Visar inlägg med etikett client-server. Visa alla inlägg
Visar inlägg med etikett client-server. Visa alla inlägg

lördag 27 juli 2013

Realtime communication using SocketIO

This is a short introduction to SocketIO.

SocketIO is a Javascript library for realtime communication over Internet. Using SocketIO you can very easily write client-server apps where data is exchanged between a client and a server, or between multiple clients connected to the server at the same time. Some applications that spring to mind are chat applications and simple multiplayer games.

SocketIO tries to use WebSockets for its communication, but can fallback to other techniques in situations where WebSockets can not be used. So you actually don't have to mind about the communication medium when you write your app.

For the server-side of your app, you write your app in Javascript and run it using the Node.js execution environment.

Here below I have written a small tutorial to show how easy it is to create a simple, browser-based, realtime chat application.

What will the application do?
  • The application will let a client, running in a browser, connect to a server and send messages and get responses from the server in realtime.
  • If multiple clients, running in separate browsers and potentially on separate computers, are connected to the server at the same time, the clients will be able to send text messages to each other.
  • The client-side app will have two text boxes, one for writing text messages to send to the server, and one for writing text messages that will be broadcasted to all connected clients.
  • The client-side app will also have a text label. Whenever the client receives a text message (either from the server, or from another client) the message will be shown in the text label.

First some pre-requisites before we start with the implementation:
  • First of all, this requires that you have some server where you can upload and run your server-side app. See for example my earlier posting about Linode on how you can rent a private virtual server: http://mattiaserlo.blogspot.com/2013/06/virtual-private-server.html (Or I guess you could run the server also on your computer using "localhost" for test purposes.)
  • You'll also need to have Node.js installed on your server. See my earlier posting about how to install Node.js: http://mattiaserlo.blogspot.com/2013/06/nodejs.html
  • You also need to install the SocketIO package on your server. On your server, write: npm install socketio

Now we can get started writing some code! As explained above, the application will consist of two parts: one server-side app that will handle incoming connections and messages from clients, and one client-side app that will run on a client in a browser.
Let's start with the server-side app. Create a file called serverapp.js with the following contents:


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

io.sockets.on('connection', function (socket) {
 console.log("Someone connected");
 
 socket.on('messageToServer', function (data) {
  console.log("Got messageToServer message");
  
  if (data.type != undefined) {
   console.log("data.type = " + data.type);
  }
  
  // Do some work on the server...
  // ...
  
  // Then send a reply to the client
  socket.emit('messageFromServer', { type: 'someOtherType',
       text: 'Hello!' });
 });
 
 socket.on('broadcast', function (data) {
  console.log("Got broadcast message");
  
  // Broadcast the message to all connected clients
  io.sockets.emit('broadcast', data);
 });
});

The first line creates a socketIO object and tells socketIO to listen to incoming connections on port 8083. I chose this port number arbitrarily - you can use any number you want as long as it is not already used by some other app or service on your server.

The second line, io.sockets.on('connection', function (socket) {, declares an anonymous function that will be called whenever some client connects to the app on the port we are listening to. As you see there is a function argument, socket. This socket object is used for communicating with the client from now on. If another clients connects to the server later, the anonymous function will be called again with a new socket used for that new client.
Inside the anonymous function you can see socket.on('messageToServer', function (data) { ... Here we are telling socketIO that whenever a message of type messageToServer comes to the server, execute this function. By the way, message names are just text string and you can decide what to name your messages on your own.

So what we do when "messageToServer" is received, is we do a debug print and then we call socket.emit. Socket.emit is used for sending data via the socket. In our example we are sending a message of another type, messageFromServer, back to the client, with some additional data. Again, you are free to name your messages whatever you want, and you can basically pass any number of parameters in your message. Message data should be JSON formatted.

You can also see that we are listening to another message type, broadcast. I came up with this name for messages that I want to broadcast to all connected clients. So if a client wants to send some text that should be received by all clients, the client will send this message to the server. When the server receives a message of this type, it calls io.sockets.emit('broadcast', data);

io.sockets.emit means that a message will be sent to all the sockets currently open on the server app. And we are reusing the name "broadcast" for this message type. We don't need to modify the data, we just pass on whatever data the client wishes to broadcast.

And this is actually all the code we need for the server-side.

To run this code on your server, upload the code to your node folder on your server, open a shell on your server and write: node serverapp.js

Now we need to write the client-side of the application.

Create a file named index.html, containing the following:


<!DOCTYPE html>
<html>
<head>
 <title>SocketIO test</title>
</head>
<body>
 <script src="http://106.187.52.148:8083/socket.io/socket.io.js"></script>
 <h1>SocketIO test</h1>
 Message to send to server: <input type="text" id="toServerTextBox" onchange="toServerTextChanged()">
 <br>
 Message to broadcast to clients: <input type="text" id="broadcastTextBox" onchange="broadcastTextChanged()">
 <br>
 <label id="textLabel"></label>
 <br>
 <script>
  
  var socket = null;
  
  function toServerTextChanged() {
   var textBox = document.getElementById("toServerTextBox");
   if (socket) {
    socket.emit('messageToServer', { text: textBox.value });
   }
  }
  
  function broadcastTextChanged() {
   var textBox = document.getElementById("broadcastTextBox");
   if (socket) {
    socket.emit('broadcast', { text: textBox.value });
   }
  }

  socket = io.connect('http://106.187.52.148:8083');

  if (socket != null) {
   socket.on('messageFromServer', function (data) {
    console.log("Got messageFromServer");
    document.getElementById('textLabel').innerHTML =
    "Got message from server: " + data.text;
   });

   socket.on('broadcast', function (data) {
    console.log("Got broadcast message");
    document.getElementById('textLabel').innerHTML =
    "Got broadcast message: " + data.text;
   });


   console.log("Now send a message to the server");
   
   socket.emit('messageToServer', { type: 'someType' ,
       moreData: 'someOtherData' });
  }
 </script> 
</body>
</html>


Looking at this code, you can see a line that fetches a script, script src="http://...

You should exchange the IP number with the IP number to your server, and use the same port number as you chose for your server-app to listen to. The socket.io.js script will be magically served by your server when the clients need the script.

Further below in the code you can see that we are calling io.connect:
socket = io.connect('http://106.187.52.148:8083');
Again, make sure here to use your server's IP number and port number.
When this line is executed, the client will connect to the server, and your server-app's line io.sockets.on('connection'... will be called with a socket to use for communicating with this client.

Further below in the code you can see similar code as we wrote for the server-side - we tell which messages we want to listen to and associate each message with an anonymous function to be called. In our example implementation here we simply update the text label with the message contents.

Now that you are running your server-side app on your server, you are ready to receive connections from clients. So on your local computer, start a browser, and open the index.html file you just wrote. Hopefully you'll see the text label being updated with a "hello" from the server, as a response from the client's connection request.

Now, either in another browser tab, or on another computer or smartphone, open up the index.html file. Now both clients should be connected to the server. Writing a message in the broadcast text box (and pressing Enter) should make the message pop up on all clients' screens.

Neat huh? :-)

If you want to download the code from GitHub, here is the link: https://github.com/mattiaserlo/socketiotest

måndag 17 juni 2013

Node.js


Admittedly, besides from a little dabbling with PHP a long time ago, I don't have much experience in writing server side applications in other frameworks than Node.js. So I don't have much to compare with. But I have to say I'm surprised by how quick and easy it is to write server side apps using Node.js.

For those who don't already know, Node.js is a software platform for server side apps, and it is built upon the same Javascript engine (called V8) as used in Google Chrome. Thus Node.js makes it possible to execute Javascript code on the server. Before Node.js, Javascript was regarded solely as a client-side language.

Perhaps the first benefit that comes to mind with this is that we can now share code between the client and server implementations. Let's say you are making a simple multiplayer game in Javascript, and you want the server to be in charge of the game logic, but you also want clients to run game logic to be able to predict the game state while waiting for updates from the server. Without running the same language on server and client this could be quite a hazzle, and even small differences between the client's and the server's engine state calculations could lead to large confusing differences in state. Thus it's a really good advantage to be able to run the exact same logic on both sides. (Besides, obviously you wouldn't have to implement the game engine twice.)

This leads us to another aspect of Node.js: Node.js apps are typically single-threaded, meaning one single thread on the server handles all the requests made from multiple clients. Client-server work should be designed to be asynchronous, i.e. typically the client sends a request to the server and the server will call the client back shortly later using a callback function. As a benefit of being single-threaded, the overhead needed to serve a new connecting client is very low: the server does not need to create any new process or allocate any new memory, and it does not have to do any context switching to serve multiple clients.

As a result, one server app can typically handle a large number of clients without bogging down. On the other hand, this means that it's bad to spend a long execution time serving one client as work will not be going on in parallell. Going back to the multiplayer game example above, it's then actually a bad idea to run game engine code on the server unless the executions can be made quickly. I guess Node.js apps should be designed to do just short operations. (I have heard there are some attempts in using something like webworkers to spawn threads for longer running operations, but I have not tried it out.)

Also, serving clients in one process is obviously dangerous if some code is misbehaving: if one operation crashes, the whole server app goes down. As an example, I found out that the file system call to read from a file will throw an error if the file does not exist. Forget to write exception handling for that, and you'll watch your server go down in flames when one single client cannot find the file it is looking for.

Anyway. For my experiments so far Node.js has been nice to work with, and the benefits have greatly outweighed the limitations. Knowing a bit of Javascript it's usually very quick, and does not require much code, to achieve quite a lot. There are tons of open-source packages you can use to speed up your work, and a package manager (aptly called NPM, Node Package Manager) to help you installing things.

So far, using Node.js, I have written:
  • One file server, which I use as a web server on my Linode machine, i.e. to serve files over http at www.mattiaserlo.com
  • One "session server", which uses socket.io to handle communication between clients. I'm using this for simple multiplayer games and chat-type apps.
  • A few RESTful services, that I used for testing to store and retrieve data from the server.
I'm running all of those on my rented Linode server.

To get started using Node.js on a Linux machine, go to http://nodejs.org/ and download an archive, then unpack and install like this, using shell:

tar xzvf node-v0.6.7.tar.gz (or whichever version you have downloaded)
cd node-v0.6.7
./configure
make
make install

You can run Node.js apps on your local computer using localhost, although it's much more fun to host your stuff on a real server that is always accessible by your friends. If you don't already have access to a server, I recommend renting one: see my earlier posting, http://mattiaserlo.blogspot.jp/2013/06/virtual-private-server.html

Finally, as an example, let me show how easy it is to create a file server using Node.js and a few packages:

Assuming you have installed Node.js (see above),

1. Do "npm install node-static"
2. Do "npm install http"
3. Create a file named myfileserver.js, containing this:

var static = require('node-static');
var http = require('http');
var file = new(static.Server)('/var/www'); // The folder which you want to serve files from
var httpServer = http.createServer(function(request, response) {
request.addListener('end', function() {
file.serve(request, response);
}
}).listen(80); // The port number your server will listen to requests on

4. Run the server like this: node myfileserver.js

Now you can connect to your server from another computer and fetch files from /var/www.

To run your fileserver as a service, instead of from a blocking shell call, break the Node.js execution above and create a file named myfileserver.conf, containing this:

description "My node.js server"
author "Me"

start on started mountall
stop on shutdown

# Automatically Respawn:
respawn
respawn limit 99 5

script
export HOME="/root"
exec /usr/local/bin/node /node/myfileserver.js >> /var/myfileserver.log 2>&1
end script

post-start script
# Optionally put a script here that will notifiy you node has (re)started
# /root/bin/hoptoad.sh "node.js has started!"
end script

The exec line above assumes you have stored myfileserver.js in a folder called /node.

Store the conf file in /etc/init on your machine, then you can start and stop your service whenever you want, like this:

start myfileserver
stop myfileserver