15 Sep 2013 17:09:42

Comet. Apache + php + Nodejs + Socket.io. Realtime fullduplex web application

Классическая модель интернета - запрос - ответ не позволяет строить полноценные приложения, в которых сервер может отсылать данные клиентам в браузер. Однако существует множество технологий позволяющих тем или иным путем симулировать или добиться настоящей обратной связи, к примеру для построения чата с моментальным откликом на каком-либо веб ресурсе. В статье приведен пример как обычный сайт, использующий в качестве сервера - Apache, можно наполнить динамикой используя современные технологии протокола Websocket, библиотеку js socket.io и серверное приложение, написанное на Nodejs исползуемого Apache'ем в связке с Curl.


Краткая теория как это работает:

Известно что начиная с версии прокола http 1.0 браузеры при отправке запросов сохраняют TCP туннель, и используют его снова для отправки нового запроса, поскольку TCP является протоколом  транспортного уровня он предоставляет возможность серверу послать обратный запрос, к сожалению http протокол данной возможности не предоставляет, но сам браузер - способен принять ответ по TCP туннелю и так же как в случае с ajax передать ответь в js функцию которая решит что с этим ответом делать.

Отсюда следует - что для того чтобы обеспечить возможность серверу отсылать в браузер данные нужно воспользоваться API бразуера с помощью js - в этом нам поможет библиотека socket.io, а для того чтобы сервер сумел отправить нам данные - там должен находиться иной сервер (отличный от apache) - способный использовать протокол отличный от http - а именно web socket - для примера был выбран движок Nodejs и написанное на нем серверное приложение, которое будет принимать запросы от браузеров, поддерживать с ними постоянные связи, а при поступлении новых данных на сервер apach'a, отсылать подключенным клиентам в браузер свежую информацию помощью приложения nodejs.

Так же в начале упоминалось про apache - чтобы не переписывать на nodejs весь старый функционал сайта, а просто добавить новую фичу типа чата (пример будет простым, просто описывающим принцип работы) - мы будем использовать apache и когда на сервер будет приходить информация - например новый пост - будет срабатывать Curl - который обычным http запросом с постом будет слать данные на nodejs приложение, которое в свою очередь будет обновлять клиентам данные:

Итак начнем: вначале на сервер apach'а приходит запрос и он возвращает страничку, на которой вставлен js скрипт:

// Подключаем библиотеку для установления
// постоянного конекта с nodejs приложеним.
<script src="socket.io.js"></script>
<script>
  var sw='run';
  // Конектимся на локальный сервер по 8080 порту. 
  var socket = io.connect('http://localhost:8080');

  // Функция которая сработает когда нам по туннелю
  // отправят данные как и в случае с ajax. 
  socket.on('stream', function (data) {
    // Для наглядности вставим ответ внутрь враппера.
    document.getElementById('number').innerHTML = data.n;
  });
 
  function stop_timer() {
    if (sw == 'run') {
      // Шлем данные на nodejs приложение по клику.
      socket.emit('action', {todo: 'stop'});
      sw='stop';
    }
    else {
      // Шлем данные на nodejs приложение по клику.
      socket.emit('action', { todo: 'run'});
    }
  }
</script>
Magic Page
<div style="border:1px solid #ccc" id="number">&nbsp;</div>
<a href="#" onclick="stop_timer();return false;">Action</a>

Вышеописанный скрипт устанавливает постоянный TCP туннель через Websocket протокол со нижеописанным nodejs приложением написанным на javascript с использованием нескольких обычных модулей\расширений, которые можно установить с помощью утилиты npm:

Nodejs приложение биндит сокет по 8080 порту, и когда бразуер устанавливает с ним сообщение, он единожды биндит другой сокет на другой порт - 8300 (поднимает http сервер), и когда на вторичный адрес приходит запрос отдает всем участникам (пользователям которые установили постоянные соединения) какие-то данные.

// Подключаем библиотеку и сразу же поднимаем сервер на 8080 порт.
var io = require('...path/node_modules/socket.io').listen(8080);

// Load the http module to create an http server.
var http = require('http');
var server = undefined;

io.sockets.on('connection', function (socket) {
    // Счетчик для примера.
    t = 1;
    // Создаем HTTP server если он не был создан.
    if (typeof(server) == 'undefined') { 
      server = http.createServer(function (request, response) {
        response.writeHead(200, {"Content-Type": "text/plain"});
        // На запрос вызываем метод библиотеки socket.io
        // чтобы отослать данные подключенным постоянно клиентам.
        socket.broadcast.emit('stream', {n:t = t+1});
        // Выводим сообщение.
        response.end("Hello World\n");
      });
    }

    // Биндим Http server на 8300 порт, IP defaults to 127.0.0.1
    server.listen(8300);
    
    // Отсылаем всем клиентам какие-то данные при
    // подключении нового клиента.
    socket.broadcast.emit('stream', {n:'c'});
    socket.on('action', function (data) {
        if (data.todo=='stop') {
          // Что-нибудь сделать на action stop
        }
       else if (data.todo = 'run') {
          // Что-нибудь сделать на action run
        }
    });
});

Собственно все уже работает, остается из apacha на php вызвать к примеру с помощью curl http запрос на такой-то порт (8300) такой-то адрес (указанный в nodejs приложении) и тогда nodejs примет его и передаст данные на клиентов.

Comments:

ilyin.eugene
Продаю веселые грибы, недорого
30 май 2013 в 10:16

add comment