Pull to refresh

TornadIO2 = Tornado + Socket.IO

Python *

Что такое 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 — как только, так сразу.

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

Буду благодарен за любую помощь в нагрузочном тестировании, выявлении ошибок и надеюсь что кому-то еще это все пригодится.
Tags: tornado servercometsocket.iotornadioweb socketspython
Hubs: Python
Total votes 45: ↑45 and ↓0 +45
Comments 13
Comments Comments 13

Popular right now