среда, 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.

2 комментария:

T-Rex комментирует...

О, нормальный такой пост. Я даже все понял. Даешь более сложные примеры жизненные?
Было бы замечательно посмотреть как такое будет работать на Windows Mobile в виде сервиса. Хотя наверное там WSA* функции рулят.

Андрей Булавинов комментирует...

В Windows есть мощная система Windows I/O Completion Port, так что использование libevent оправдано только для переносимого кода. Под Windows Mobile - MSMQ