Пользователь
0,0
рейтинг
25 ноября 2011 в 03:21

Разработка → TornadIO2 = Tornado + Socket.IO

Что такое Socket.IO?


Это библиотека предназначенная для организации постоянного соединения между сервером и браузером.

Главное преимущество библиотеки: она автоматически подстраивается под возможности браузера и использует наиболее эффективный транспортный протокол из поддерживаемых.

Браузер умеет веб сокеты? Отлично, будем использовать их. Браузер умеет AJAX? Будем использовать long polling. Это древний Internet Explorer? Будем использовать html file object. Ну и так далее.

О socket.io уже писали на Хабре. «Родной» сервер Socket.IO написан на node.js.

Что такое TornadIO2?


Это библиотека работающая поверх Tornado и поддерживающая текущую версию протокола Socket.IO.

Главное преимущество библиотеки — она предоставляет удобную абстракцию над виртуальным соединением, так что разработчику не прийдется задумываться как будет работать транспорт в разных условиях.

Почему называется TornadIO2? Потому что есть TornadIO, который поддерживает старые версии Socket.IO и более старые версии Tornado (1.2, например). Начиная с Socket.IO 0.7, поменялся транспортный протокол и появилась новая функциональность, которая плохо вписывалась в архитектуру первого TornadIO. Кроме того, TornadIO2 требует Tornado 2.1 или выше.

Домашняя страница проекта находится тут: http://github.com/MrJoes/tornadio2
Документация (на английском) находится тут: http://tornadio2.readthedocs.org/

Ближе к коду


Простейший клиент Socket.IO выглядит так:
var conn = io.connect('http://myserver');

conn.on('connect', function() { 
    alert('connected');
});

conn.on('message', function(msg) {
    alert('Got ' + msg);
});

conn.on('disconnect', function() {
    alert('disconnected');
});

Грубо говоря, мы создаем объект соединения, а потом добавляем реакции на разные события: подключение, получение сообщения и отключение от сервера.

Если нам захочется отправить сообщение на сервер, то делаем:

conn.send('Hello World!');


Socket.IO поддерживает отправку json объектов:

conn.json.send({msg: 'Hello World', a: 10});


Теперь посмотрим как выглядит простейший сервер:

from tornado import web
from tornadio2 import SocketConnection, TornadioRouter, SocketServer

# Declare connection class
class MyConnection(SocketConnection):
    def on_open(self, info):
        print 'Client connected'

    def on_message(self, msg):
        print 'Got', msg
        self.send(msg)

    def on_close(self):
        print 'Client disconnected'

# Create TornadIO2 router
router = TornadioRouter(MyConnection)

# Create Tornado application with urls from router
app = web.Application(router.urls, socket_io_port=8001)

# Start server
if __name__ == '__main__':
    SocketServer(app)


В данном примере основной частью является MyConnection. Когда клиент присоединится к серверу, TornadIO2 создаст экземпляр этого класса и вызовет on_open(). Когда сервер получит сообщение от клиента, он вызовет on_message с отправленным сообщением. И так далее.

TornadioRouter работает с одним классом соединения и реализует всю сетевую логику необходимую для правильной работы клиента socket.io.

Дальше мы создаем приложение Tornado, скармливаем ему ссылки из маршрутизатора и стартуем сервер. SocketServer это просто удобная обертка для старта HTTP сервера Tornado и Flash Policy Server'a для поддержки FlashSocket транспорта.

Можно отправлять как строки, так и обычные объекты, главное что бы они нормально сериализировались в json.

События


В последних версиях Socket.IO появилась концепция событий — вместо отправления «обычных» сообщений, эмулируется вызов функции и получается такой-себе RPC. Естественно, если ошибиться с количеством параметров (или их именами) произойдет исключение и соединение просто закроется.

Событие отправляется так:

var conn = io.connect('http://myserver:1234');

conn.on('connect', function() {
    conn.emit('hello', 'Joes');
});


А на сервере они обрабатываются так:

from tornadio2 import SocketConnection, event

class MyConnection(SocketConnection):
    @event('hello')
    def hello(self, name):
        print 'Got hello from %s' % name


Аналогично работает отправление событий с сервера и обработка их на клиенте:

var conn = io.connect('http://myserver:1234');

conn.on('connect', function() {
    conn.emit('ping', 'Joes');
});

conn.on('pong', function(name) {
    alert(name); // Will show Joes
});


И сервер:

from tornadio2 import SocketConnection, event

class MyConnection(SocketConnection):
    @event('ping')
    def ping(self, name):
        self.emit('pong', name)


Подтверждения


При отправлении сообщения или события, можно запросить подтверждение — когда сообщение будет получено и обработано, автоматически будет отправлен ACK пакет и вызовется переданный callback.
В случае событий, можно вернуть значение из обработчика и оно автоматически отправится на клиент, благодаря чему получаем удобный код для паттерна REQUEST/RESPONSE.

С точки зрения API это все выглядит так:

var conn = io.connect('http://localhost');

conn.on('connect', function() {
    // Emit 'whoareyou' event and provide callback function that will be
    // called once event was handled by the server.
    conn.emit('whoareyou', function(name) {
       alert(name); // Will print 'Joes'
    });
});


И сервер:

class MyConnection(SocketConnection):
    @event('whoareyou')
    def woohoo(self):
        return 'Joes'


Сервер тоже может отправлять сообщения с необходимостью подтверждения. Для этого необходимо передавать callback для self.send() или использовать self.emit_ack() для событий:

class MyConnection(SocketConnection):
    @event('hello')
    def myhello(self):
        self.emit_ack('hello', self.on_ack)

    def on_ack(self, msg, data):       
        print 'Woohoo, got ACK', msg, data


Поддержка tornado.gen


Начиная с версии Tornado 2.1.0, появился удобный интерфейс для написания асинхронного кода без callback'ов.

При использовании tornado.gen, код будет работать асинхронно — если от клиента поступило два сообщения и первое начало обрабатываться асинхронно, второе будет обработано до того как закончится обработка первого. Это не всегда то что ожидается от сервера.

Специально для этих целей и был создан декоратор tornadio2.gen.sync_engine.

Если «обернуть» декоратором обработчик сообщений (или событий), то сообщения будут обрабатываться в порядке их получения, но при этом не будет блокироваться ioloop.

Лучше всего посмотреть пример, который иллюстрирует как это все работает.

Монитор производительности


TornadIO2 включает в себя простой монитор, который подсчитывает различные события: количество активных соединений, количество отправленных и принятых сообщений в секунду и так далее. Список счетчиков пока не очень большой, так что буду рад пожеланиям.

Есть простой пример, который показывает как можно работать с данными — строит график и показывает статистику в реальном времени, которая выглядит как-то так:


Производительность


К сожалению, полноценное нагрузочное тестирование библиотеки еще не проводилось, так что никаких реальных цифр о производительности озвучить не смогу. Если кто желает протестировать, я буду очень признателен.

Код сервера достаточно легковесный и overhead не должен быть сильно заметен.

Примеры


TornadIO2 содержит несколько примеров использования: простой чат, ping через socket.io и так далее.
Все примеры лежат тут.

Статус и будущее


TornadIO2 пока еще не зарегистрирован в PyPI — как только, так сразу.

Сервер готов к использованию (надеюсь) — вполне нормально крутится на моих небольших, закрытых, проектах. Все протоколы работают, так что не должно быть особых сюрпризов.

Буду благодарен за любую помощь в нагрузочном тестировании, выявлении ошибок и надеюсь что кому-то еще это все пригодится.
Сергей @Joes
карма
52,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (13)

  • 0
    а как обстоят дела с масштабируемостью?
    • 0
      У Tornado дела с масштабируемостью обстоят хорошо, он для этого и создавался, а TornadoIO2 — лишь разборщик протокола Socket.IO.
    • –2
      Вот нельзя прям обойтись без этого вопроса ни в одном посте про что-то серверное :(
    • +1
      Почему же, вполне правильный вопрос.

      TornadIO2 достаточно легковесный, там нет никаких сложных вычислений которые могли бы сильно кушать процессор. А сам Tornado — один из самых быстрых асинхронных серверов для питона. Один сервер сможет вытянуть большое количество клиентов.

      Если говорить про горизонтальную масштабируемость — добавляем ZeroMQ или Redis, запускаем ферму, балансируем с помощью haproxy (или dns round robins) и получаем кластер, который вытянет сильно больше чем один сервер.
  • +1
    А есть примеры, когда клиент — не браузер? Насколько сложно написать клиент, скажем, на C# или Java?
    • 0
    • +1
      Основная идея socket.io — организация push на браузер. Т.е. как бы нет смысла его использовать для других целей, так как есть всякие zeromq, thrift, etc.

      Но да, народ написал клиенты для большинства языков. Для питона полноценного клиента нет, но есть пример как можно на коленке сделать свой: stackoverflow.com/q/7586302
      • 0
        смотрел и трогал еще первый tornadio, отказался по нескольким причинам

        все круто, но мне не нужны другие транспорты кроме flash & websocket

        а tornadio.SocketConnection != tornado.websocket.WebSocketHandler

        я правда не тестировал еще flash прокладку, не было необходимости, но в WebSoketHandler мне приятно было делать self.get_secure_cookie, да и вообщем все остальное было как в обычном хендлере понятно и прозрачно

        скажите просто, мне стоит смотреть tornadio2 или проще реализовать протокол самому поверх WebSocketHandler?
        • 0
          WebSocketHandler более низкоуровневый чем соединение tornadio. Из за поддержки всех транспортных протоколов нельзя рассчитывать что всегда будут доступны куки и так далее. FlashSocket нормально работает с WebSocketHandler, но он не умеет передавать куки вообще.

          К слову, в TornadIO2 немного улучшилась ситуация с обработкой входящего запроса (куки и строка запроса передаются в on_open вне зависимости от протокола) и, причем, точно знаю одного человека у которого взлетели secure cookie в TornadIO2 (chegivara, он отметился ниже).

          Но если нет желания использовать ничего кроме веб сокетов и обратная совместимость не сильно заботит, то можно и не смотреть :-)
  • 0
    Написал чат на tornadio2 — все отлично работает. Большое спасибо за библиотеку.
    • 0
      Здорово! Очень интересует, удалось ли читать secure cookie? При каких транспортаах? Как храните инстансы соединений?

      Разработчики Socket.IO сами пишут что существует такая проблема как отсутствие cookies при соединении через WebSockets, и поэтому они реализовали принудительный xhr запрос после удачного соединения. В TornadIO2 есть подобый функционал? Или как можно самому его реализовать?
  • 0
    Удивительная вещь — статьи на хабре в 90% случаев появляются вовремя. Автору + в карму и благодарность за полезную статью с примерами!
  • 0
    Как организовать check_origin в Torndio2?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.