Categories

(110)
(56)
(94)
(16)

Implementing Websockets using php (Ratchet library) or Tornado web server

05.06.2015
Implementing Websockets using php (Ratchet library) or Tornado web server
Author:

Websockets is an important protocol in web development which provides a two-way communication channel between the browser and the web server. Its difference from the traditional way of data transfer is in the fact that the connection is not closed after receiving the answer, but is kept open. Thus, there is an exchange of information in real time, in particular, you can receive notifications from the server without additional requests.

When and where it is worth to use websockets:

  • Social media, including chats and notifications about other users’ online actions (their likes, comments, posts etc.)
  • Financial notifications or other types of alerts where it is important to keep the information up-to-date.
  • Shared work on documents, when it is important to know what changes are made by another person at the moment.
  • Applications for which data on actual location is important.
  • Online education, where an important criterion is the online contact between the student and the teacher, as well as between the students.
  • Online games where the online interaction between the players is important.

Websockets are usually implemented using Node.js and Tornado web server. This is due to the fact that php has been developed as a programming language for web pages which take the request-response format.

However, the Ratchet library allows you to bypass these restrictions and implement server support of websockets using php.

So, in this article we will discuss both ways to implement websockets:

  • using Node.js and Tornado web server;
  • using php (Ratchet library) and js.

You can compare them and choose the one that suits you best. In this article we will discuss the implementation of a simple chat (without authentication). Let’s start!

Websockets via Tornado and js

Tornado is a web server and a framework written in Python, which easily expands and is not blocked during requests. It was created for use in the FriendFeed project. This company was bought by Facebook in 2009, which resulted in opening the source code of Tornado [1].

Application structure:

.

├── app.py - server part

├── index.html

├── requirements.txt

── static

── main.js - front-end

back-end (Tornado):

1. Let’s create a new directory to place the project in:

mkdir tornado-websocket-example && cd tornado-websocket-example

2. Let’s create the requirements.txt file with the following contents:

tornado==4.0.2

3. Let’s install tornado using pip:

pip install -r requirements.txt

4. Let’s create app.py file in the project root:

from tornado import websocket, web, ioloop
import json

cl = []

class IndexHandler(web.RequestHandler):
  def get(self):
      self.render("index.html")

class SocketHandler(websocket.WebSocketHandler):
  def check_origin(self, origin):
      return True

  def open(self):
      if self not in cl:
          cl.append(self)

  def on_close(self):
      if self in cl:
          cl.remove(self)

class ApiHandler(web.RequestHandler):

  @web.asynchronous
  def get(self, *args):
      self.finish()

  @web.asynchronous
  def post(self, *args):
      self.finish()
      message = self.get_argument("m")
      data = {"message": message}
      data = json.dumps(data)
      for c in cl:
          c.write_message(data)

app = web.Application([
  (r'/', IndexHandler),
  (r'/ws', SocketHandler),
  (r'/api', ApiHandler),
  (r"/static/(.*)", web.StaticFileHandler, {"path":r"./static/"}),
])

if __name__ == '__main__':
  app.listen(8080)
  ioloop.IOLoop.instance().start()

This file is the heart of our server part.

Brief explanation of the code:

cl = [] - an array of connected users.

app = web.Application([ - doing the routing.

(r'/', IndexHandler), - writing the path to render index.html.

(r'/ws', SocketHandler), - writing the path to connect websockets.

(r'/api', ApiHandler), - writing the path to send messages.

self.render("index.html") - specify to web server which file needs to be rendered.

cl.append(self) - when connecting a new user, adding him to the array of connected users.

cl.remove (self) - when disconnecting a user, removing him from the array of connected users.

def get (self, * args): - writing the processing of get requests here (we ignore them)

def post (self, * args): - writing the processing of post requests here (sending out messages to all users)

app.listen (8080) - writing the port here

front-end (WebSocket):

Creating the index.html file in the project folder root

Chat with websockets<!DOCTYPE html>
<html>
<head>
  <title>Chat with websockets</title>
  <link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.no-icons.min.css" rel="stylesheet">
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
  <script type="text/javascript" src="static/main.js"></script>
</head>
<body>
  <div class="container">
    <h1>Chat with websockets</h1>
    <hr>
      WebSocket status : <span id="message"></span>
    <hr>

    <div id="chat">
      <div class="viewport"></div>
      <form class="row row-chat">
        <div class="input-group">
          <input type="text" class="form-control" placeholder="Type your message" />
          <span class="input-group-btn">
              <button type="submit" class="btn btn-primary">Send</button>
          </span>
        </div>

      </form>
    </div>
</body>
</html>

For more convenience, let’s connect jQuery and Bootstrap.

Let’s create main.js file (to be placed in the static folder) - it’s the heart of our client part:

$( document ).ready(function() {
function wrap_message(msg, $element) {
  var html = '<div class="bubble bubble">'+ msg +'</div>';

  html = $.trim(html);
 $element.html($element.html() + html);
}

var ws = new WebSocket('ws://localhost:8080/ws');
var $message = $('#message');

ws.onopen = function(){
  $message.attr("class", 'label label-success');
  $message.text('open');
};
ws.onmessage = function(ev){
  $message.attr("class", 'label label-info');
  $message.hide();
  $message.fadeIn("slow");
  $message.text('received message');

  var json = JSON.parse(ev.data);

  wrap_message(json.message, $('.viewport'));

};
ws.onclose = function(ev){
  $message.attr("class", 'label label-important');
  $message.text('closed');
};
ws.onerror = function(ev){
  $message.attr("class", 'label label-warning');
  $message.text('error occurred');
};

$('.input-group-btn .btn-primary').click(function(event) {
  $.get("api?m=" + $(this).parent().parent().find('input').val());
  $.post( "/api", { m: $(this).parent().parent().find('input').val() } );
  $(this).parent().parent().find('input').val('');
  return false;
});
});

Brief explanation of the code:

var ws = new WebSocket('ws://localhost:8080') - when creating a websocket sample, please note that that front-end and the back-end ports must match.

ws.onopen = function() - here you write all the actions when opening the channel, in this example we just inform the user that the WebSocket is open.

ws.onmessage = function(ev) - here you write all the actions when closing the channel, in this case we display the message to the user.

ws.onclose = function(ev) - here you write all the actions when closing the channel, in this case we just inform the user the WebSocket is closed.

Conclusion

Starting the server

python app.py

Let’s open http://localhost:8080/ in multiple browsers, if everything is all right - you can chat between different browsers.

We have considered the simplest example of websocket implementation via Tornado web server. The archive with the application code is added. Link to the repository. This example contained a Hello World variation.

Websockets via php (Ratchet library) та js

Application structure:

.

├── chat-server.php - file to start the server

├── composer.json

├── composer.lock

├── index.html

├── src

│ └── MyApp

│ └── Chat.php - server part

└── static

└── main.js - front-end

back-end (Ratchet):

1. Let’s create a new directory to place your project in:

mkdir ratchet-websocket-example && cd ratchet-websocket-example

2. Let’s install Ratchet library via composer:

php ~/composer.phar require cboden/ratchet && php ~/composer.phar install

3. Let’s create chat-server.php file:

run();

This file is responsible for the launch of the the server part of our chat.

4. Let’s create the src folder and the MyApp folder in it:

mkdir -p src/MyApp

5. Let’s create the Chat.php file:

 
clients = new \SplObjectStorage;
  }

  public function onOpen(ConnectionInterface $conn) {
      // Store the new connection to send messages to later
      $this->clients->attach($conn);

      echo "New connection! ({$conn->resourceId})\n";
  }

  public function onMessage(ConnectionInterface $from, $msg) {
      $numRecv = count($this->clients)/* - 1*/;
      echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n"
          , $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's');

      foreach ($this->clients as $client) {
          //if ($from !== $client) { // if you don’t want sent message to sender too - uncomment this line
              // The sender is not the receiver, send to each client connected
              $client->send(json_encode(array('message' => $msg)));
          //} // if you don’t want sent message to sender too - uncomment this line
      }
  }

  public function onClose(ConnectionInterface $conn) {
      // The connection is closed, remove it, as we can no longer send it messages
      $this->clients->detach($conn);

      echo "Connection {$conn->resourceId} has disconnected\n";
  }

  public function onError(ConnectionInterface $conn, \Exception $e) {
      echo "An error has occurred: {$e->getMessage()}\n";

      $conn->close();
  }
}

This file is the heart of our server part.

Brief explanation of the code:

public function onOpen(ConnectionInterface $conn) - here you write all actions when opening a new channel, in this example, we just display a message in the console. Here, for example, you can limit the connection of users.

public function onMessage(ConnectionInterface $from, $msg) - here you write all actions when getting a message, here we show notifications in the console and send this message to all connected users. In this part, you can implement selective sending of messages.

public function onClose(ConnectionInterface $conn) - here you write all actions when closing a channel, in the example we just display a message in the console.

//if ($from !== $client) { - uncomment this line, if you do not want to send this message to its author.

Potential problems:

Try to run the chat-server.php file:

php chat-server.php

if you see an error like “PHP Fatal error: Class 'MyApp\Chat' not found”, the probable error is on the wrong location of the Chat.php file. The chat-server.php file has this line:

require __DIR__ . '/vendor/autoload.php';

which is responsible for the autoloading of classes. In order to make the autoloading successful, the location of the Chat.php file should meet the PSR-0 standards. In our case, it has to be here src/MyApp/Chat.php as related to the application folder.

front-end (WebSocket):

Let’s create the index.html file in the application folder root.

<!DOCTYPE html>
<html>
<head>
<title>Chat with websockets</title>
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.no-icons.min.css" rel="stylesheet">
<link href="/static/main.css" rel="stylesheet">
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script type="text/javascript" src="/static/main.js"></script>
</head>
<body>
<div class="container">
  <h1>Chat with websockets</h1>
  <hr>
    WebSocket status : <span id="message"></span>
  <hr>

  <div id="chat">
    <div class="viewport"></div>
    <form class="row row-chat">
      <div class="input-group">
        <input type="text" class="form-control" placeholder="Type your message" />
        <span class="input-group-btn">
            <button type="submit" class="btn btn-primary">Send</button>
        </span>
      </div>

    </form>
  </div>
</body>
</html>

For convenience, let’s connect jQuery and Bootstrap.

Let’s create main.js file (place in the static folder) - this is the heart of our client part:

$( document ).ready(function() {
function wrap_message(msg, $element) {
  var html = '<div class="bubble bubble">'+ msg +'</div>';

  html = $.trim(html);
 $element.html($element.html() + html);
}

var ws = new WebSocket('ws://localhost:8080/ws');
var $message = $('#message');

ws.onopen = function(){
  $message.attr("class", 'label label-success');
  $message.text('open');
};
ws.onmessage = function(ev){
  $message.attr("class", 'label label-info');
  $message.hide();
  $message.fadeIn("slow");
  $message.text('received message');

  var json = JSON.parse(ev.data);

  wrap_message(json.message, $('.viewport'));

};
ws.onclose = function(ev){
  $message.attr("class", 'label label-important');
  $message.text('closed');
};
ws.onerror = function(ev){
  $message.attr("class", 'label label-warning');
  $message.text('error occurred');
};

$('.input-group-btn .btn-primary').click(function(event) {
  $.get("api?m=" + $(this).parent().parent().find('input').val());
  $.post( "/api", { m: $(this).parent().parent().find('input').val() } );
  $(this).parent().parent().find('input').val('');
  return false;
});
});

Brief explanation of the code

var ws = new WebSocket('ws://localhost:8080') - creating a websocket example, please not that the front-end and back-end ports must match.

ws.onopen = function() - here you write all actions when opening a channel, in this case we just inform the user that WebSocket is open.

ws.onmessage = function(ev) - here you write all actions when receiving a message, in this case we display the message to the user.

ws.onclose = function(ev) - here you write all actions when opening a channel, in this case we just inform the user that WebSocket is closed.

Conclusion

Starting the server

php chat-server.php

We open the index.html file in multiple browsers, if everything is ok, you can chat between different browsers.

We have considered the simplest example of websocket implementation via the Ratchet library. This example was a variation of Hello World.

The Demo is on the socketo.me website.

5 votes, Rating: 5

Read also

1

Installing Drupal base package really seems to be a challenging task. If you are not IT guru, you probably think that it will last for a couple of hours...or maybe even all day. We have written a...

2

Creating CTools popups (modal windows) is not a complicated thing, but it has many important nuances. Therefore, this article is devoted to the various nuances of popup creation.

3

This is a second portion of useful Drupal modules for social networks integration. Hopefully it would be useful for your website!

4

Need to integrate some social networks to your Drupal website? We've made up a list of the most useful modules for this purpose!

5

TOP Drupal modules helping you to configure the Views according to specific website.

Need a quote? Let's discuss the project

Are you looking for someone to help you with your Drupal Web Development needs? Let’s get in touch and discuss the requirements of your project. We would love to hear from you.

Join the people who have already subscribed!

Want to be aware of important and interesting things happening? We will inform you about new blog posts on Drupal development, design, QA testing and more, as well news about Drupal events.

No charge. Unsubscribe anytime