вторник, 1 декабря 2009 г.

Буферизированный ввод/вывод средствами libevent

Разрабатывая сетевые приложения, часто сталкиваешься с необходимостью работать с пакетами данных на уровне,
который находится чуть выше чем потоковый. Удобно, когда при отправке пакета информации на стороне клиента
сервер получает этот пакет за один раз и асинхронно относительно других клиентов.
Техническая реализация сетевого стека и API для работы с ним такова, что в асинхронном режиме работы с сокетами
вызов send(2) и read(2) не обязательно отправляют или принимают полный пакет, лишь только тот объем, который
в текущий момент доступен. В синхронном (блокирующем) режиме возникает другое ограничение — выполнение программы
приостанавливается, пока не будет отправлено или вычитано указанное количество байт, что мешает одновременно работать
сразу со многими подключениями в одном потоке. Решение этой проблемы можно найти, например, в библиотеке libevent.
Библиотека libevent, помимо классических обратных вызовов (callbacks) по событиям, предоставляет абстракцию,
которая буферизирует все вызовы чтения/записи (buffered event) [1]. Такой механизм обеспечивает автоматическое
заполнение и освобождение буферов при операциях чтения и записи.
Для того, чтобы начать работать с буферизированным вводом/выводом необходимо:
— инициализировать структуру bufferevent вызовом bufferevent_new(), указав функции для обратного вызова на события 
чтения (необязательно), записи (необязательно) и ошибки (обязательно) ;
— Привязать это событие к локальной событийной базе (event_base) вызовом bufferevent_base_set();
— Включать или выключать обработчики чтения/записи вызовами bufferevent_enable()/bufferevent_disable();
— Читать и писать вызовами bufferevent_read() и bufferevent_write() соответственно.
Стоит отметить, что обработчик чтения будет вызван после того, как в буфере накопится весь пакет или закроется подключение,
а обработчик записи — когда отправляемый буфер отправлен либо полностью, либо до указанной отметки (watermark).

Пример использования


Для примера напишем эхо-сервер и простой клиент, который будет обслуживать одновременно несколько клиентов.
Начнем с сервера. Для этого нам понадобится слушающий сокет, который может асинхронно принимать подключения,
обработчик подключения и логика работы с клиентом.
Для создания серверного сокета достаточно воспользоваться этой статьей.
Для удобства, вся информация о подключениях хранится в хэш-таблице, которая завернута в класс Connections.
Функция main будет практически идентичной, за исключением инициализации событийной базы:

// Initialize
event_base * base = event_base_new();


Создается новая событийная база для того, чтобы не перекрывать глобальный контекст.

// Init events
struct event ev;
// Set connection callback (on_connect()) to read event on server socket
event_set(&ev, server_sock, EV_READ | EV_PERSIST, on_connect, base);
// Attach event to new event base
event_base_set(base, &ev);
// Add server event without timeout
event_add(&ev, NULL);


Создается событие, которое реагирует на чтение из серверного сокета — это событие подключение нового клиента.
Функция event_base_set — привязывает это событие к событийной базе.
Обработка событий выполняется так:

// Dispatch events
event_base_loop(base, 0);


Обработка подключения заключается в том, чтоб создать новое буферизированное событие, связанное с сокетом клиента.

// Setup connection
ConnectionData & cdata = Connections::Instance().Add(sock);

cdata.evb = bufferevent_new(sock, on_read, on_write, on_error, arg);
bufferevent_base_set(reinterpret_cast(arg), cdata.evb);
// Ready to get data
bufferevent_enable(cdata.evb, EV_READ);


Чтение выполняется в функции on_read примерно следующим образом:

size_t len = EVBUFFER_LENGTH(evb->input);
u_char * data = new u_char[len];
size_t read = bufferevent_read(evb, data, len);


Обработка отключения выполняется в функции on_error: необходимо проверить флаг EVBUFFER_EOF переменной what.

if (what & EVBUFFER_EOF) {
// Disconnected
}


В клиентской части работа с буферизированными событиями — аналогична. После выполнение подключения к серверу, инициализируем буфер:

event_base * base = event_base_new();
bufferevent * evb = bufferevent_new(sfd, on_read, on_write, on_error, base);

bufferevent_base_set(base, evb);
bufferevent_enable(evb, EV_WRITE | EV_READ);


Стоит отметить, что создание буфера и запись в него осуществляется до вызова обработчика очереди событий:

const char * data1 = "Hello from client";
bufferevent_write(evb, data1, strlen(data1) + 1);

event_base_loop(base, 0);


Обработка событий чтения/записи/ошибок аналогична обработке в серверной части.
Работающий пример эхо-сервера и клиента можно взять здесь.

___
1. http://www.monkey.org/~provos/libevent/doxygen-1.4.10/

пятница, 10 июля 2009 г.

Архитектура рекламной системы

Вопрос разработки высоконагруженных систем является очень актуальным. лично для меня, с точки зрения ресурсопотребления.
Можно спроектировать систему так, чтобы она хорошо масштабировалась горизонтально, но она все равно будет расходовать слишком много ресурсов для решения поставленных для нее задач. В качестве примера такой системы могу привести всем известный OpenX (бывший OpenAds) [1].
Реальная ситуация: для открутки 10 баннеров, при нагрузке в чуть более чем 4.5 миллиона показа в сутки, система на 5 серверах (2х процессорные машины с 4GB RAM) задыхается в Load Average 200%. Оптимизации базы данных (LOAD DATA INFILE + кэширование) ситуацию сильно не изменили.
Здесь уже приходится искать узкие места. Первое — система написана на PHP (OpenAds на PHP4), второе — на каждый клик выполняется INSERT запрос к БД.
Как вариант оптимизации — перевод системы на FastCGI, но на долго ли хватит этой оптимизации?
В итоге решили отказаться от этой системы в пользу собственной разработки.

Сегодня на сайте конференции разработчиков высоконагруженных систем highload++ [2] увидел презентацию архитектуры рекламной системы Mail.RU.


Цифры говорят сами за себя: 3 миллиарда показов в сутки на 6 фронтендах и 3х бекендах.

_____
1. Официальный сайт OpenX — http://www.openx.org/en/products
2. Сайт конференции HighLoad++ — http://highload.ru

пятница, 3 июля 2009 г.

Разработка RPC приложений с использованием libevent

Разрабатывая клиент-серверные системы часто возникает вопрос, какой протокол выбрать.
Этому вопросу следует уделить достаточно внимания, ведь протокол, новый или уже существующий,
накладывает ряд ограничений и дополнительных трудозатрат на разработку и поддержку системы в будущем.

При выборе протокола следует, как минимум, ответить на следующие вопросы:
— как распределяются задачи между взаимодействующими узлами; Задачи для клиента, задачи для сервера;
— какой объем данных будет минимальный для передачи, а какой максимальный;
— как часто необходимо обмениваться данными;
— какая топология узлов? Будут ли клиент и сервер находиться в одной подсети;
— сколько потребуется времени/ресурсов для расширения протокола и сколько кода это может затронуть;
— как предполагается организовывать защиту трафика;
— критична ли скорость обмена данными и возможно ли масштабировать систему вертикально, например, за счет повышения пропускной способности канала.

Я хотел бы поделиться опытом использования RPC протокола в системе с трехуровневой логической архитектурой.
Решение использовать RPC с бинарным протоколом было следствием следующих факторов:
— вся вычислительная нагрузка приходится на серверную часть (возможно горизонтальное масштабирование);
— размер ответа небольшой, но сильно отличающийся для разных запросов;
— обмениваться данными необходимо достаточно часто и с многих клиентов;
— сервер будет находиться в одной гигабитной подсети с клиентами, поэтому размер пакета играет меньше роли чем время на установку нового соединения;
— расширять функционал сервера прийдется неоднократно;
— нет необходимости в защите трафика;
— поскольку основная часть сервера написана на С, не хотелось путать туда тяжеловесные XML-RPC решения;
— проверенный временем libevent предоставляет удобную и тонкую обвертку над процессом маршалинга и асинхронными вызовами.

О технологии вызова удаленных процедур можно почитать например на citforum.ru [1], так что я не буду вдаваться в теорию, а покажу на примере, как использовать RPC в приложении на C/C++.
Хочу добавить, что данное решение (libevent + RPC) является кроссплатформенным, проверял на FreeBSD, Mac OS X и Windows.

Вот то, что нам необходимо для работы:
1. Компилятор С/С++.
2. Библиотека libevent (1.4.x/2.x).
3. Python (необходим для скрипта event_rpcgen.py).
4. Сам event_rpcgen.py (устанавливается в систему, либо можно взять из дистрибутива).

Перед тем, как разрабатывать сервер и клиент, необходимо определиться с базовыми структурами запросов и ответов.
В качестве примера я приведу сервер, который возвращает время с момента загрузки системы (uptime(1)) и информацию о системе (uname(1)), причем в ответе будет лишь та информация, которую запросит клиент.

Что характерно для систем реализующих RPC, это процесс упорядочивания структур данных для передачи другому узлу, который может находиться как на локальной системе, так и на удаленной, таким образом, чтоб было возможно восстановить их в полной точности (сохраняя размеры и типы данных). Этот процесс называется маршалингом [2]. Практически любая библиотека реализующая RPC предоставляет средства автоматического маршалинга и демаршалинга. Не исключение и libevent.

Создаем файл, в котором описываются структуры для запросов/ответов, расширение файла должно быть .rpc.

Вот пример моего файла QueryTypes.rpc


struct StatRequest {
optional int uptime = 1;
optional int uname = 2;
}
struct StatReply {
optional string uptime = 1;
optional string uname = 2;
}


Доступны следующие типы данных:
1. struct — структура, аналог struct в C (в конце точка с запятой не ставится).
2. int — аналог uint32_t.
3. string — char *, строка переменной длины.
4. bytes — вектор uint8_t, можно указывать длину, например bytes data[24].
5. array struct[type] — вектор структур type.

Модификатор optional указывает на то, что это не обязательно поле, и его наличие необходимо проверять макросом EVTAG_HAS().

После указания имени поля (поля именуются тегами) указывается его числовой id.

Теперь можно генерировать код, для работы с этими структурами:

$ event_rpcgen.py QueryTypes.rpc

Если все указано без ошибок, можно увидеть примерно следующее:

Reading "QueryTypes.rpc"
Created struct: StatRequest
Added entry: uptime
Added entry: uname
Created struct: StatReply
Added entry: uptime
Added entry: uname
... creating "QueryTypes.gen.h"
... creating "QueryTypes.gen.c"

В результате, мы получили два файла, в котором реализованы все необходимые методы для работы с этими структурами, включая методы маршалинга и демаршалинга.

RPC сервер


1. Заголовочные файлы


Нам понадобятся следующие заголовочные файлы:

#include <event.h>
#include <evhttp.h>
#include <evrpc.h>

а так же заголовочный файл с нашими структурами. Тут есть один нюанс, необходимо включать его с модификатором "C":

#ifdef __cplusplus
extern "C" {
#endif

#include "QueryTypes.gen.h"

#ifdef __cplusplus
}
#endif


2. Регистрация удаленных процедур


Собственно то, ради чего была эта затея — процедуры для удаленного вызова. Их необходимо зарегистрировать и сгенерировать код для автоматизации рутинных вещей [2].
Все вызовы RPC имеют следующий прототип: ИмяПроцедуры(СтруктураЗапроса, СтруктураОтвета).
Я назвал процедуру GetServerStat:

// Регистрируем
EVRPC_HEADER(GetServerStat, StatRequest, StatReply);
// Генерируем автокод
EVRPC_GENERATE(GetServerStat, StatRequest, StatReply);
Также, необходимо объявить функцию, которая будет обрабатывать вызов процедуры:

void GetServerStatCB(EVRPC_STRUCT(GetServerStat)* rpc, void * arg);


3. Создание слушающего RPC сервера


Поскольку выбрана реализация на базе HTTP сервера, необходимо зарегистрировать сокет, и
инициализировать HTTP мини-сервер. Создание сокета — дело тривиальное, это можно посмотреть
в моем примере.

// Инициализируем базу механизма событий
event_base * serv_base = event_init();
// Создаем HTTP сервер (это, на самом деле, легковесная обвертка над сокетом)
evhttp * http_base = evhttp_new(serv_base);
// и указывем сокет, на каком он будет работать
evhttp_accept_socket(http_base, server_sock);
// Создаем базу для RPC протокола
evrpc_base * rpc_base = evrpc_init(http_base);

После этого, необходимо зарегистрировать все обработчики для всех удаленных процедур:

EVRPC_REGISTER(rpc_base, GetServerStat, StatRequest, StatReply, GetServerStatCB, NULL);

Все, можно запускать диспетчер событий, дальше работаем в обработчиках вызовов.

event_dispatch();

При выходе, порядок очистки структур следующий:

EVRPC_UNREGISTER(rpc_base, GetServerStat);
evrpc_free(rpc_base);
evhttp_free(http_base);

4. Обработка запросов


Теперь самое интересное — обработка запросов и формирование ответов.
В реализации обработчика выполняются нехитрые стандартные вещи:

void GetServerStatCB(EVRPC_STRUCT(GetServerStat)* rpc, void *arg)
{
// Связывание запроса
StatRequest * request = rpc->request;
// и ответа
StatReply * reply = rpc->reply;
// Проверка полей
if (EVTAG_HAS(request, uptime)) {
// и присвоение значений в ответной структуре
EVTAG_ASSIGN(reply, uptime, data.c_str());
}
// в конце обработчика, если все ОК, даем об этом знать
EVRPC_REQUEST_DONE(rpc);
}

Есть. А теперь клиент.

RPC клиент


1. Заголовки


Заголовки подключаются такие же, как и в серверной части.

2. Регистрация процедур


Процедуры регистрируем так же: EVRPC_HEADER и EVRPC_GENERATE
А вот обработчик результата другой, а именно:

void GotServerStat(struct evrpc_status *status, struct StatRequest *request, struct StatReply *reply, void *arg);

где

status — результат обработки удаленного вызова. Нас интересует случай EVRPC_STATUS_ERR_NONE — значит все Ok,
request и reply — наши структуры,
arg — произвольный аргумент (указывается при регистрации самого обработчика (см. далее).

3. Инициализация RPC



// Событийная база
event_base * base = event_init();
// HTTP здесь нужен только для обработки заголовка HTTP пакета ( этот вызов лишь инициализирует внутренние буферы).
evhttp * http = evhttp_new(base);
// Аналогично с RPC
evrpc_base * rpc = evrpc_init(http);


А теперь, поехали: создаем подключение к RPC серверу

// IP адрес и порт сервера
const char * host = "127.0.0.1";
u_short port = 8090;
evhttp_connection * evcon = evhttp_connection_new(host, port);

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

evrpc_pool * pool = evrpc_pool_new(base);
// Добавляем подключение в пул
evrpc_pool_add_connection(pool, evcon);
// Создаем и инициализируем структуру запроса. Кстати, вызовы StatRequest_new, StatRequest_free,.. - генерируются
// автоматически скриптом event_rpcgen.py
StatRequest * stat_req = StatRequest_new();
EVTAG_ASSIGN(stat_req, uptime, 1); // запрос на получение uptime (флаг 1)
EVTAG_ASSIGN(stat_req, uname, 1); // запрос на получение uname
// Создаем структуру ответа
StatReply * stat_rep = StatReply_new();
// Делаем запрос, указав, что при получении ответа вызвать обработчик GotServerStat
EVRPC_MAKE_REQUEST(GetServerStat, pool, stat_req, stat_rep, GotServerStat, NULL);
// Обрабатываем запрос и ответ
event_dispatch();

В обработчике ответа вызовем функцию выхода из диспетчера, поэтому далее следует код очистки структур.

StatRequest_free(stat_req);
StatReply_free(stat_rep);
evrpc_pool_free(pool);
evrpc_free(rpc);
evhttp_free(http);


4. Обработчик ответа


Обработчик ответа вызовется после получении ответа и демаршалинга структуры ответа.

void GotServerStat(struct evrpc_status *status, struct StatRequest *request, struct StatReply *reply, void *arg)
{
// Проверяем статус возврата
if (status->error == EVRPC_STATUS_ERR_NONE) {
// и читаем доступные значения
char * value = NULL;
if (EVTAG_HAS(reply, uptime) && (EVTAG_GET(reply, uptime, &value) != -1)) {
printf("[Reply] uptime: %s\n", value);
}
if (EVTAG_HAS(reply, uname) && (EVTAG_GET(reply, uname, &value) != -1)) {
printf("[Reply] uname: %s\n", value);
}
}
// Можно выходить из цикла диспетчера событий
event_loopexit(NULL);
}

Как видно, здесь нет ничего сложного, внутренняя реализация этой технологии достаточно эффективна, о чем можно судить по результатам тестирования средствами Apachebench [3].

$ ab -c 100 -n 1000 -p post.http.txt -T application/octet-stream http://10.1.3.10:8080/.rpc.GetServerStat
Server Hostname: 10.1.3.10
Server Port: 8080

Document Path: /.rpc.GetServerStat
Document Length: 136 bytes

Concurrency Level: 100
Time taken for tests: 0.067 seconds
Complete requests: 1000
Failed requests: 0
Broken pipe errors: 0
Non-2xx responses: 1058
Total transferred: 283544 bytes
Total POSTed: 250930
HTML transferred: 143888 bytes
Requests per second: 14925.37 [#/sec] (mean)
Time per request: 6.70 [ms] (mean)
Time per request: 0.07 [ms] (mean, across all concurrent requests)
Transfer rate: 4232.00 [Kbytes/sec] received
3745.22 kb/s sent
7977.22 kb/s total

Connnection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 0.4 1 4
Processing: 3 4 0.8 4 8
Waiting: 1 4 0.7 4 7
Total: 3 6 0.9 6 9

Percentage of the requests served within a certain time (ms)
50% 6
66% 6
75% 6
80% 7
90% 7
95% 8
98% 8
99% 9
100% 9 (last request)

Клиент и сервер находятся в одной сети, т.е. основное время тратится на соединение и обработку запросов.
На этом, конечно же, все возможности RPC не заканчиваются, но для того, чтобы переступить не самый низкий порог входжения этого вполне достаточно.

Скачать работающий пример RPC сервера и клинета (Makefile для UNIX/Max OS) можно отсюда.

Спасибо за внимание и успешной разработки.

___
1. Вызов удаленных процедур (RPC) — http://www.citforum.ru/operating_systems/sos/glava_12.shtml
2. Теоретические основы маршалинга — http://ru.wikipedia.org/wiki/Маршалинг
3. Документация по работе с инструментом Apachebench — http://httpd.apache.org/docs/2.2/programs/ab.html

среда, 20 мая 2009 г.

Вебинар от QNX SS "Exactly When Do You Need Realtime?"


21 мая, компания QNX Software Systems проведет бесплатный онлайн-семинар, который должен помочь при выборе операционной системы для встраиваемых систем.

Продолжительность: 1 час, включая вопросы.

Все ли встраиваемые проекты нуждаются в ОСРВ?.. Завтра узнаем.



пятница, 8 мая 2009 г.

К проекту прилагается отличная документация в формате С

Отличная статья про то, как хорошо и полезно уметь читать и читать исходный код:
http://gaperton.livejournal.com/32772.html

Когда я только начинал программировать, у меня совсем не было интернета, а в моем городе не было вменяемой литературы, поэтому я привык пользоваться справкой.
Читая сопроводительные тексты к среде разработки и к библиотекам, наткнулся на следующее высказывание: "Чтобы научиться программировать – нужно читать очень много кода, а писать еще больше".

Документация-документацией, но все же, не помешает (а часто и поможет) знание того, как это работает "под капотом".

четверг, 7 мая 2009 г.

Написание своего HTTP сервера с использованием libevent

Библиотека libevent содержит в себе простейший асинхронный HTTP сервер, который можно
без особого труда встроить в собственное приложение для обслуживания HTTP запросов.

Для использования этой возможности достаточно добавить в код следующее:

1. Подключить заголовочный файл <evhttp.h>:
#include <evhttp.h>

2. Инициализировать базу событийного движка:
event_base * serv_base = (event_base *)event_init();

3. Инициализировать HTTP сервер:
evhttp * http_server = evhttp_new(serv_base);

4. Указать, на каком сокете слушать подключения:
evhttp_accept_socket(http_server, server_sock);

5. Выставить callback'и на запросы. Можно добавлять на каждый URI свой обработчик:
evhttp_set_cb(http_server, "/news", on_request_news, NULL);

6. Выставить обработчик на остальные запросы:
evhttp_set_gencb(http_server, on_request, NULL);

7. Запустить цикл обработки запросов:
event_base_dispatch(serv_base);


Реализация HTTP сервера является потоко-безопасной (thread safe).

Есть еще один нюанс: настоятельно рекомендуется игнорировать сигнал SIGPIPE.
Делается это следующим вызовом:
signal(SIGPIPE, SIG_IGN);


Описание всех функция для работы с HTTP протоколом
можно найти в документации.

Вот работающий пример простого HTTP сервера, который на все запросы отдает динамическую
страничку с некоторой информацией о клиенте.

/*
* \file: http_server.cpp
* \description: Simple HTTP server
*
*/
#include <errno.h>
#include <event.h>
#include <evhttp.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <signal.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <iostream>

const short  SERVER_BACKLOG    = 128;
const short  BUF_LEN           = 26;
const char   RESPONCE[BUF_LEN] = "<H1>Hello there</H1><BR/>";
const char * SERVER_NAME       = "Simple HTTP Server";

void on_request(struct evhttp_request *, void *);

int main(int argc, char **argv)
{
if (argc < 3) {
std::cout << "Start as:" << std::endl 
<< argv[0] << " host_address port" << std::endl;
return 1;
}

int server_sock = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock == -1) {
std::cout << "Error socket(): " << strerror(errno) << std::endl;
return 1;
}

u_short      port  = atol(argv[2]);
const char * host  = argv[1];
sockaddr_in  sa;
int          on    = 1;
sa.sin_family      = AF_INET;
sa.sin_port        = htons(port);
sa.sin_addr.s_addr = inet_addr(host);

if (setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
std::cout << "Error setsockopt(): " << strerror(errno) << std::endl;
return 1;
}

// Bind server socket to ip:port
if (bind(server_sock, (const sockaddr*)&sa, sizeof(sa)) == -1) {
std::cout << "Error bind(): " << strerror(errno) << " on: " << host << ":" << port << std::endl;
return 1;
}
// Make server to listen
if (listen(server_sock, SERVER_BACKLOG) == -1) {
std::cout << "Error listen(): " << strerror(errno) << std::endl;
return 1;
}
// Init events
event_base * serv_base   = (event_base *)event_init();
evhttp     * http_server = evhttp_new(serv_base);

// Ignore SIGPIPE
signal(SIGPIPE, SIG_IGN);

if (evhttp_accept_socket(http_server, server_sock) == -1) {
std::cout << "Error evhttp_accept_socket(): " << strerror(errno) << std::endl;
return 1;
}
// Set HTTP request callback                               
evhttp_set_gencb(http_server, on_request, NULL);
// Dispatch events
event_base_dispatch(serv_base);

return 0;
}

void on_request(struct evhttp_request * req, void * arg)
{
// Create responce buffer
struct evbuffer *evb = evbuffer_new();
if (!evb) { return; }

// Add heading text
evbuffer_add_printf(evb, "<HTML><HEAD><TITLE>%s Page</TITLE></HEAD><BODY>\n", SERVER_NAME);
// Add buffer
evbuffer_add(evb, RESPONCE, BUF_LEN);
// Add formatted text
evbuffer_add_printf(evb, "Your request is <B>%s</B> from <B>%s</B>.<BR/>Your user agent is '%s'\n", 
req->uri, req->remote_host, evhttp_find_header(req->input_headers, "User-Agent"));
// Add footer
evbuffer_add_printf(evb, "</BODY></HTML>");

// Set HTTP headers
evhttp_add_header(req->output_headers, "Server", SERVER_NAME);
evhttp_add_header(req->output_headers, "Connection", "close");

// Send reply
evhttp_send_reply(req, HTTP_OK, "OK", evb);

// Free memory
evbuffer_free(evb);
}


Makefile для сборки:
SRCS=http_server.cpp
LDFLAGS=-levent
CXXFLAGS=-I/usr/local/include -L/usr/local/lib
PROG=http_server

all:
c++ ${SRCS} -o ${PROG} ${LDFLAGS} ${CXXFLAGS}

clean:
rm -f ${PROG}


А вот результаты тестирования Apache Benchmark:
ab -c 100 -n 1000 "http://192.168.1.110:10000/test"

Server Software:        SimpleHTTPServer
Server Hostname:        192.168.1.110
Server Port:            10000

Document Path:          /test
Document Length:        199 bytes

Concurrency Level:      100
Time taken for tests:   0.185 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      308000 bytes
HTML transferred:       199000 bytes
Requests per second:    5393.74 [#/sec] (mean)
Time per request:       18.540 [ms] (mean)
Time per request:       0.185 [ms] (mean, across all concurrent requests)
Transfer rate:          1622.34 [Kbytes/sec] received

Connection Times (ms)
min  mean[+/-sd] median   max
Connect:        0    5   2.8      5      16
Processing:     5   13   3.9     13      23
Waiting:        2   10   3.9      9      22
Total:         11   18   3.4     18      27

Percentage of the requests served within a certain time (ms)
50%     18
66%     19
75%     20
80%     21
90%     22
95%     24
98%     25
99%     26
100%     27 (longest request)


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

понедельник, 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.

вторник, 24 марта 2009 г.

Асинхронный ввод/вывод с libevent

Один из проектов, которым я сейчас занимаюсь, для работы с сокетами использует механизм kqueue. Мне необходимо модифицировать бизнес-логику, часть которой захватывает отправку отправку ответа, но поскольку некоторые данные приходится модифицировать непосредственно перед отправкой, код обработки события EVFILT_WRITE сильно размазался.
Я решил перенести сетевой асинхронный ввод/вывод на библиотеку libevent. На официальном сайте есть неплохая документация в Doxygen и скудненький пример, который оказался не совсем рабочим: событие EV_READ срабатывает постоянно.
Собрал на FreeBSD 6.2, 6.3, 7.0 – баг повторяется. Чтоб событие отрабатывалось корректно и вовремя, очередь надо открывать с правами O_RDWR, а не с O_RDONLY.

вторник, 10 марта 2009 г.

CodeCamp 2009...

...или еще одна возможность встретиться и пообщаться вживую.
28-29 марта 2009 года в городе Киев пройдет всеукраинская конференция, посвященная разработке программного обеспечения. Программа конференции многопоточна, так что заранее желательно определиться, куда идти.
Помимо программирования, будут доклады по управлению и работе в команде.

Зарегистрироваться можно
здесь
Думаю, будет интересно.

суббота, 7 марта 2009 г.

Прозрачное журналирование с wxLog

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

Если необходимо выводить сообщения в файл не только в ANSI, а и в Unicode,
то очень удобно использовать логгер wxLogStream.
Для его использования, потребуется собрать (если еще не собрано) wxWidgets с выставленной поддержкой std потоков. Для этого необходимо выставить соответствующую
директиву в файле setup.h:

#define wxUSE_STD_IOSTREAM 1


После этого, в приложении потребуется установить целевой логгер:

//wxTestApp.h
// в классе приложения добавить мембер потока
...
#include
...

class wxTestApp: public wxApp
{
...
private:
std::ofstream m_logStream;
...
};

//wxTestApp.cpp
// в OnInit() проинициализировать целевой логгер

bool wxCDCreatorApp::OnInit() {
// задаем имя лога (в каталоге приложения, файл <имя файла приложения>.log
wxString logPath = wxPathOnly(wxGetApp().argv[0])
+ wxFileName::GetPathSeparator()
+ GetAppName()
+ wxT(".log");
m_logStream.open(logPath.GetData());

// не забываем удалить предыдущий логгер
delete wxLog::SetActiveTarget(new wxLogStream(reinterpret_cast<std::ostream*>(&m_logStream)));

// можно работать
wxLogMessage(wxT("Запуск приложения..."));
...
// инициализация приложения
}

// и в OnExit() закрываем поток
int wxCDCreatorApp::OnExit()
{
wxLogMessage(wxT("Выход..."));
m_logStream.close();
return wxApp::OnExit();
}


После этого, в любой части приложения возмножно с помощью функций wxLogMessage, wxLogDebug (только в отладочной версии),
wxLogTrace вести журналирование. В результате работы, в лог пишутся сообщения в примерно таком формате:


09:55:05: Запуск приложения...

четверг, 5 марта 2009 г.

Сборка библиотеки avcap для Windows

Итак, поэкспериментировав с различными библиотеками захвата видео, я остановился
на кроссплатформенной библиотеке avcap. Сайт проекта находится здесь.
Отличная документация к библиотеке идет в пакете с проектом. Также, есть
простенький, но понятный пример использования.

Почему я выбрал именно avcap:
1. Кроссплатформенность.
Реализация под Windows использует DirectShow, под Linux - V4L, под Mac OS X - QuickTime.
2. Написана на C++.
3. Работает достаточно стабильно.
4. Легко интегрируется с С++ проектом.

Из минусов стоит отметить проблемную сборку под Windows.
Под Linux и Mac OS X сборка и установка не вызвала никаких проблем, стандартная процедура:
./configure
make
sudo make install

Для успешной сборки avcap под Windows (XP, Vista) необходимо подготовить следующее:
1. Установить Microsoft Visual Studio (2008).
2. Установить Microsoft DirectX SDK (2008 November).
3. Установить Microsoft Windows SDK 7.
4. Скопировать каталог
C:\Program Files\Microsoft SDKs\Windows\v7.0\Samples\Multimedia\DirectShow\BaseClasses\

например в C:\Temp.
5. Снять атрибут "Только чтение с BaseClasses".
6. Открыть baseclasses.sln, согласиться на конвертацию в новый формат.
7. Выбрать цель Debug.
8. В зависимости от проекта, возможно прийдется изменить Runtime Library на Multi-threaded Debug.
9. Собрать.
10. В настройках путей Visual Studio убедиться, что пути к DirectX SDK находяться ниже,
чем пути Windows SDK.


11. С предыдущего Platform SDK взять qedit.h и закоментировать в нем упоминания
dxtrans.h и IDXEffect. Этот файл можно положить в каталог baseclasses.


Настройка проекта avcap.
1. Открыть avcap.sln, согласиться сконвертировать.
2. В свойствах проекта указать:
Character Set: Use Unicode Character Set
Additional Include Directories: C:\Temp\BaseClasses;C:\Program Files\Microsoft SDKs\Windows\v7.0\Include";..\include\avcap\windows;..\include\avcap;
Runtime Library: Multi-threaded Debug (/MTd)
4. Открываем файл avcap\avcap\windows\ds_devicedescriptor.cpp
находим строчку (477) и правим ее:
wsprintf((LPWSTR)ws, L"FilterGraph %08x pid %08x\0", (DWORD_PTR)pUnkGraph, GetCurrentProcessId());

5.Собираем библиотеку.

В настройки приложения, которое будет использовать avcap, нужно добавить следующее:
в C/C++:
Preprocessor Definitions: AVCAP_WINDOWS
и в Linker:
Additional Dependencies: avcapd.lib strmbasd.lib uuid.lib ole32.lib winmm.lib strmiids.lib oleaut32.lib


Сборка была протестирована на Windows XP SP2 и на Windows Vista SP1.
Основной источник проблем заключался в порядке путей к заголовочным файлам,
так что будьте внимательнее.

вторник, 3 марта 2009 г.

Сборка Windows SDK 7

После установки, первое, что удивило - это замена makefile в Samples на sln+vcproj.
Хоть что-то изменилось.

Скопировал весь каталог BaseClasses в рабочий каталог и открыл в студии проект.
Студия предложила сконвертировать в новый формат. Сконвертировал, собираю...

>amfilter.cpp
1>c:\program files\microsoft sdks\windows\v7.0\include\objidl.h(11265) :
error C2061: syntax error : identifier '__RPC__out_xcount_part'
1>c:\program files\microsoft sdks\windows\v7.0\include\objidl.h(11266) :
error C2059: syntax error : ')'
1>c:\program files\microsoft sdks\windows\v7.0\include\objidl.h(11266) :
fatal error C1903: unable to recover from previous error(s); stopping compilation

Проблема оказалась в путях к заголовочным файлам, вернее в их порядке.
Переместив C:\Program Files (x86)\Microsoft DirectX SDK\Include в самый конец все
собралось на отлично.

Сборка avcap.

В 7 SDK удалили файл qedit.h!
Взял этот файл из SDK 6.0A.
Вернулась проблема dxtrans.h, которого все-таки больше не существует.

Отредактировал qedit.h

//#include "dxtrans.h" -- Line 498
IDxtCompositor //: public IDXEffect -- Line 837
IDxtAlphaSetter //: public IDXEffect -- Line 1151
IDxtJpeg //: public IDXEffect -- Line 1345
IDxtKey //: public IDXEffect -- Line 1735

Пересобираю... опять гора ошибок.
Оказывается, в SDK есть такие же заголовочные файлы, как и в
Samples\Multimedia\DirectShow\BaseClasses. Ладно, переставил порядок,
поставил Samples перед SDK.
Пересобираю...

1>c:\temp\avcap\avcap\windows\ds_devicedescriptor.cpp(477) :
error C2664: 'wsprintfW' : cannot convert parameter 1 from 'char [128]' to 'LPWSTR'
1> Types pointed to are unrelated; conversion requires reinterpret_cast,
C-style cast or function-style cast

"Жопой чую, конец близко" (с) Макс

Быстро поправляю типы данных и преобразования, пересобираю и вуаля!

1>avcap - 0 error(s), 0 warning(s)
2>captest - 0 error(s), 0 warning(s)
========== Build: 2 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

Протестировал, все работает на отлично.

Все просто!

воскресенье, 1 марта 2009 г.

Windows Development Hell

Текущий проект заставил меня вернуться к программированию под Windows.
Прошло два дня активной работы и у меня уже сдают нервы.
Я конечно все понимаю, но не так же все плохо?

Собираю библиотеку libavcap. Она требует Platform SDK, ок, ставлю.
Дальше, BaseClasses захотели DirectX SDK. Ну, скачал поставил.
А оно не работает! Оказывается разработчики или ответственные за
сборку пакета "забыли" добавить файлик dxtrans.h, начал гуглить, действительно.
Причем, эта проблема, как я понял, уже с давних пор. Ждут исправления в Windows 7. Шок.

Порадовал дизайн инсталлятора DirectX.
Почему именно Check Box там, где должен быть Radio Button? Умом их не понять...


Собирая strmbasd.lib, намучался с путями Development Environment Command Prompts.
То они видят cl и не видят link, то не видят вообще ничего...

Во время сборки nmake останавливался раза четыре. Из-за ошибок в коде.

Например, в файле outputq.cpp:635 был код, примерно следующего вида:
for (long iDone = 0;....;) {
}
*nSamplesProcessed = iDone - iLost;


Старый стандарт, это понятно. Но SDK-то не старый!!!
И так еще в 3 файлах и более чем в 10 местах. Поправив все вручную, вроде собралось.

Кстати, при сборке и запуске Register Development Environment Variables у меня пропал файл,
или студия начала ссылаться на несуществующий... mspdb80.dll

Нашел, скопировал, студия перестала вообще что-либо собирать.
Вручную отредактировал пути поиска програм и библиотек,
удалил библиотеку - вроде заработало.

Итого, двое суток мозгосношений и ни строчки собственного кода в попытке сборки чужого.

Становится все интереснее...

пятница, 6 февраля 2009 г.

Сборка без warnings

Довелось работать с модулями системы, которые девелопил не я. Первым делом попробовал собрать.
Не знаю, как кого, но сборка с warnings лично меня сильно напрягает.
Поэтому, я решил причесать их немного.

Одно из проблемыных мест было множественно сообщение компилятора
"deprecated conversion from string constant to 'char *'".
Раньше модули собирались gcc версии 3.4, а сейчас 4.2, поэтому сборка была более тихая.

Код оказался винегредом из С и С++, причем для строк использовался именно char *,
менять на std::string было бы долго и неоправданно. Как оказалось, проблема была в следующем:

void addpack(std::vector<uint8_t>& c, char * formatt,...) {
    // большой switch, проверяющий посимвольно значение ключей
}

void addPack64(std::vector<uint8_t> &c, uint64_t iValue) {                                                            
    addpack(c, "N", (uint32_t)(iValue >> 32) );              
    addpack(c, "N", (uint32_t)(iValue & 0xffffffffUL));
}



Как видно из кода, функции addpack передавался указатель на локальную константную строку с возможностью изменения ее содержимого.

В качестве решения, параметр char * formatt был заменен на const char * formatt.