понедельник, 27 апреля 2009 г.

Обновление библиотеки libevent

17 апреля вышла новая версия libevent 2.0, в которой существенно переработано и улучшено API для
работы с асинхронными событиями.

Судя по тому, что указано на официальном сайте, практически весь код, который работал
на версии 1.4, будет работать на libevent 2.0, но настоятельно рекомендуется для нового кода
использовать новую версию библиотеки. Так же необходимо перелинковать старые приложения.

Что изменилось:

  • стала более удобной структура заголовочных файлов;
  • улучшился потоко-безопасный API
  • появились перекрываемые функции работающие с памятью;
  • создание event_base стало конфигурируемое: в рантайме можно выбирать любой из доступных бекендов;
  • сокеты стали абстрактным типом: теперь нет необходимости приводить дескрипторы к int;
  • стало возможным выставлять таймауты для постоянных событий (раньше это не имело смысла);
  • теперь возможно выставлять множество одинаковых событий на один дескриптор;
  • многие вещи стали действительно thread-safe
  • появилась поддержка edge-triggered событий (если поддерживается бекендом);
  • улучшения в evbuffers (множество исправлений в работе с памятью);
  • переработан механизм работы с внутренней памятью (раньше это были цельные блоки,которые приходилось двигать и изменять в размере, что не есть хорошо. Теперь это связный список страниц); 
  • более гибкий механизм чтения из буфера
  • поддержка zero-copy в буферах evbuffers;
  • множественные колбеки на один буфер evbuffer;
  • новый интерфейс колбеков;
  • фильтрация буферизированного ввода-вывода;
  • и много чего другого.

четверг, 23 апреля 2009 г.

Работа с PostgreSQL. Использование libpqxx

Существует, как минимум, два официальных клиентских C++ API для работы с PostgreSQL:
libpq++ [1] и libpqxx [2]. Обе библиотеки распространяются свободно, с открытым
исходным кодом и хорошо документированы.
Для себя я выбрал libpqxx: являясь очень гибкой, она остается так же достаточно простой в использовании.

Хотя, наверное, это и не самая простая библиотека, но однообразный интерфейс к данным
и привычные для C++ разработчика итераторы вместо "get_next_row" для доступа к результатам
запроса, делают ее использование интуитивно понятным. Так же поддерживаются потоки,
где это имеет смысл, а поля могут быть считаны в переменные шаблонными методами.
В общем, все, как в нормальных C++ библиотеках.

Вот несколько причин, по которым можно выбрать именно libpqxx:

  1. Согласованность с языком. В разумной мере используются фичи современного
    C++ такие, как шаблоны (templates), исключения (exceptions), различного типы итераторов.
    В библиотеке, по возможности, используются стандартные вещи из STL.
  2. Структура, позволяющая писать "хороший" код. Многие проблемы обнаруживаются
    на этапе компиляции или тестирования, а не во время работы.
  3. Мощность. Такие встроенные фичи, как автоматическое восстановление соединения и
    управление транзакциями освобождают разработчика от низкоуровневой рутинной работы.
  4. Гибкость. С учетом описанного выше, можно сделать вывод, что это не фреймворк.
    Ничто не заставляет втискивать свой код в чужие обработчики. В ней нет доморощенных
    строк и классов исключений. Код библиотеки живет в своем пространстве имен (pqxx)
    и полностью прячет нативный C API.
Остальные достопримечательности библиотеки можно почитать на официальном сайте [2].

Итак, что же необходимо для работы?

Во-первых, необходима нативная libpq.so/libpq.dll.
Во-вторых, сама libpqxx.so/libpqxx.dll.

Что касается подхода к работе с базой данных, тут стоит обратить внимание, что
доступ к базе данных выполняются через объект транзакции pqxx::transaction.
Поэтому, знание интерфейсов этих классов является фундаментальным [3].

Для работы с libpqxx™ нам необходимо, как минимум следующие классы:
  • pqxx::connection – представляет собой обвертку к соединению в программе к back-end'у PostgreSQL™.
    Можно открывать множество соединений ко многим базам данных.
  • pqxx::work – тип для шаблона pqxx::transaction, представляющий собой саму транзакцию,
    которая выполняется в контексте подключения (pqxx::connection). Если произойдет
    какая-нибудь проблема в рамках этой транзакции – выполнится полный ее откат к исходному
    состоянию.
  • pqxx::result – контейнер, содержащий результат работы запроса или команды выполненных
    в транзакции.
В качестве примера, приведу программу, которая выполняет запрос и выводит результат
на экран в виде field='value'.

#include <pqxx/connection>
#include <pqxx/transaction>
#include <iostream>
#include <string>
#include <sstream>

void help()
{
std::cout << "Usage: <host> <user> <password> <database>" << std::endl;
}

int main(int argc, char **argv)
{
// Check arguments
if (argc < 5) {
help();
return 1;
}

// Prepare connection string
std::ostringstream conn_string("");
conn_string << "host=" << argv[1]
<< " user=" << argv[2]
<< " password=" << argv[3]
<< " dbname=" << argv[4];

do {
// Create connection
try {
pqxx::connection conn(conn_string.str());
pqxx::work xact(conn, "SampleSelect");

std::string query("SELECT * from news limit 10");
// Execute query
try {
pqxx::result res = xact.exec(query);
if (!res.size()) {
std::cout << "Empty result set." << std::endl;
break;
}
// Show results
for (pqxx::result::const_iterator i = res.begin(), r_end = res.end(); i != r_end; ++i) {
// Iterate fields
for (pqxx::result::const_fielditerator f = i->begin(), f_end = i->end(); f != f_end; ++f) {
std::cout << f->name() << " = '" << f->c_str() << "'" << std::endl;
}
std::cout << std::endl;
}
} catch (...) {
std::cout << "Failed to execute query: " << query << std::endl;
break;
}
} catch (pqxx::broken_connection) {
std::cout << "Failed to establish connection." << std::endl;
break;
}
std::cout << "Query successfully executed." << std::endl;

return 0;
} while (false);

return 1;
}


Makefile выглядит следующим образом:

SRC=pqxx_sample.cpp
PROG=pqxx_sample

all:
c++ ${SRC} -o ${PROG} `pkg-config --libs --cflags libpqxx`
clean:
rm -f *.o ${PROG}

Как видно из примера, строкой подключения является стандартное перечисление ключ-значение,
которое можно собрать, например, в std::ostringstream [4].

Далее, подключение заворачивается в try-catch(pqxx::broken_connection).
После выполнения запроса выполняется двойной цикл: первый по записям, второй по полям.

Конструкция do {} while (false) здесь используется для уменьшения цикломатической сложности.

На этом возможности библиотеки libpqxx, естественно, не заканчиваются. О них можно
почитать в справочнике [3].

  1. libpq++ – http://www.postgresql.org/docs/7.2/static/libpqplusplus.html
  2. libpqxx – http://pqxx.org/development/libpqxx/
  3. Справочная информация – http://pqxx.org/devprojects/libpqxx/doc/3.0/html/Reference/
  4. std::ostringstream – http://www.cplusplus.com/reference/iostream/ostringstream/

среда, 8 апреля 2009 г.

Использование libevent в сетевых приложениях

API библиотеки libevent предоставляет удобный механизм callback-функций на появление соответствующего события, связанного с файловым дескриптором или по истечению таймаута, а также callback'и на сигналы или обычные временные интервалы.

На текущий момент, libevent поддерживает механизмы /dev/poll, kqueue, event ports, select, poll, epoll. За счет прозрачного API библиотека может развиваться без необходимости менять что-либо в приложениях, которые используют libevent. Как результат, libevent позволяет разрабатывать кроссплатформенные приложения и использовать наиболее подходящий механизм событий в конкретной операционной системе.
Так же, libevent можно использовать в многопоточных приложениях.
libevent работает на Linux, *BSD, Max OS X, Solaris и Windows.

К примеру, libevent используют такие системы, как Memcached – высокопроизводительная распределенная система кэширования, Tor – анонимайзер, Systrace – песочница для системных вызовов.

Использовать в своем проекте libevent достаточно просто:
Включить заголовочный файл:
#include 
и указать линковщику флаг -levent.
Перед использованием функций из этой библиотеки, необходимо выполнить инициализацию вызовом функции
event_init()
После этого можно регистрировать callback'и на любые дескрипторы или таймауты.
Вот пример простейшего сетевого эхо-сервера:
#include <event.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <iostream>

// Read/write buffer max length
static const size_t MAX_BUF = 512;

typedef struct {
    struct event ev;
    char         buf[MAX_BUF];
    size_t       offset;
    size_t       size;
} ConnectionData;

void on_connect(int fd, short event, void *arg);
void client_read(int fd, short event, void *arg);
void client_write(int fd, short event, void *arg);

int main(int argc, char **argv)
{
    // Check arguments
    if (argc < 3) {
        std::cout << "Run with options: <ip address> <port>" << std::endl;
        return 1;
    }
    // Create server socket
    int server_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (server_sock == -1) {
        std::cerr << "Failed to create socket" << std::endl;
        return 1;
    }

    sockaddr_in sa;
    int         on      = 1;
    char      * ip_addr = argv[1];
    short       port    = atoi(argv[2]);
 
    sa.sin_family       = AF_INET;
    sa.sin_port         = htons(port);
    sa.sin_addr.s_addr  = inet_addr(ip_addr);

    // Set option SO_REUSEADDR to reuse same host:port in a short time
    if (setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
        std::cerr << "Failed to set option SO_REUSEADDR" << std::endl;
        return 1;
    }

    // Bind server socket to ip:port
    if (bind(server_sock, reinterpret_cast<const sockaddr*>(&sa), sizeof(sa)) == -1) {
        std::cerr << "Failed to bind server socket" << std::endl;
        return 1;
    }

    // Make server to listen
    if (listen(server_sock, 10) == -1) {
        std::cerr << "Failed to make server listen" << std::endl;
        return 1;
    }
 
    // Init events
    struct event evserver_sock;
    // Initialize
    event_init();
    // Set connection callback (on_connect()) to read event on server socket
    event_set(&evserver_sock, server_sock, EV_READ, on_connect, &evserver_sock);
    // Add server event without timeout
    event_add(&evserver_sock, NULL);

    // Dispatch events
    event_dispatch();

    return 0;
}

// Handle new connection {{{
void on_connect(int fd, short event, void *arg) 
{
    sockaddr_in client_addr;
    socklen_t   len = 0;

    // Accept incoming connection
    int sock = accept(fd, reinterpret_cast<sockaddr*>(&client_addr), &len);
    if (sock < 1) { 
        return; 
    }
 
    // Set read callback to client socket
    ConnectionData * data = new ConnectionData;
    event_set(&data->ev, sock, EV_READ, client_read, data);
    // Reschedule server event
    event_add(reinterpret_cast<struct event*>(arg), NULL);
    // Schedule client event
    event_add(&data->ev, NULL);
}
//}}}

// Handle client request {{{
void client_read(int fd, short event, void *arg)
{
    ConnectionData * data = reinterpret_cast<ConnectionData*>(arg);
    if (!data) {
        close(fd);
        return;
    }
    int len = read(fd, data->buf, MAX_BUF - 1);
    if (len < 1) {
        close(fd);
        delete data;
        return;
    }
    data->buf[len] = 0;
    data->size     = len;
    data->offset   = 0;
    // Set write callback to client socket
    event_set(&data->ev, fd, EV_WRITE, client_write, data);
    // Schedule client event
    event_add(&data->ev, NULL);
}
//}}}

// Handle client responce {{{
void client_write(int fd, short event, void *arg)
{
    ConnectionData * data = reinterpret_cast<ConnectionData*>(arg);
    if (!data) {
        close(fd);
        return;
    }
    // Send data to client
    int len = write(fd, data->buf + data->offset, data->size - data->offset);
    if (len < data->size - data->offset) {
        // Failed to send rest data, need to reschedule
        data->offset += len;
        event_set(&data->ev, fd, EV_WRITE, client_write, data);
        // Schedule client event
        event_add(&data->ev, NULL);
    }
    close(fd);
    delete data;
}
//}}}

В libevent есть простой асинхронный HTTP сервер, который можно встраивать в свое
приложение, а так же фреймворк для написания приложений, использующие RPC.