?

Log in

Laphroaig
Куда делись
 
 
Laphroaig
30 January 2007 @ 12:51 am
Опять плять! Первый раз ушел с работы с неисправленным багом, но одно радует, что в последний момент я его нашел таки. Напишу здесь для истории. Короче есть мульти-индекс по некторой структуре (boost::multi_index), который помимо всего прочего содержит поля user_id, meeting_id и title (текстовое описание). meeting_id должны быть уникальны, а для user_id может быть несколько meeting_id. Также существует некоторый индекс, который индексирует эти структуры по полю title (по словам с учетом морфологии)... Сколько раз себе говорил не менять тесты во время рефракторинга!... Короче после определенных изменений он мне начал выдавать какуюто фигню, т.е. вроде ищет правильно, но выделяет слова совсем не те или не все слова, которые были найдены. Три часа ковырял, думал опять в multi_index накосячил, но только когда жена стукнулась в аську с вопросом "ты где пропал?", до меня дошло. Все дело в тестовом скрипте, который я изменил, и теперь он мне стал кидать записи в которых менялось поле user_id, с неизменным meeting_id, т.е. происходит обновление структуры, но она начинает принадлежать другому пользователю, а из индексатора, перед обновлением, у удалял записи для старого юзера, короче удалял не, то что нужно. Вобщем сам не понял, что сейчас написал, спать пора топать.
 
 
Current Location: дома
Current Music: кулер
 
 
Laphroaig
Плять! Всплыл баг конструктора копирования, который генерит VC8, и вся стройная концепция wrapperov и template_constructor's пошла в п... Короче, смотрим пример:

Фишка в том, что в конструкторе копирования класса В мы вызываем нифига не коструктор копирования базового класса, а шаблонный конструктор. У класса C правильная реализация (хотя во всех книгах предлагают именно так, как у B). Как ни странно, я всегда использовал именно второй вариант, не знаю почему, но считал, что так правельнее (ну теперь есть убедительное доказательство моей правоте))).

Теперь про баг. VC8 генерит конструктор копирования, такой как у класса B (условно конечно) и поэтому в лучшем случае, код не скомпилируется (если бы у A был бы базовый класс с простым конструктором с одним параметром), в худшем получим неправильную работу программы, и найти причину непросвещенному пользователю будет тяжело. Если бы в классе A был бы конструктор A(T t), т.е. не константная ссылка, то мы бы получили переполнение стека, т.к. конструктор вызывался бы рекурсивно. Но в этом случае VC8 выдает предупреждение, что возможно переполнение стека (жаль, что он при этом не признается, что сам накосячил)))

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

 
 
Current Location: дома
Current Music: кулер
 
 
Laphroaig
27 January 2007 @ 04:52 pm

В общем случае фильтр – это довольно простая штука, которая получает откуда-то данные, обрабатывает их, и куда-то, затем, обработанные данные отправляет. Любой обработчик сетевого соединения является фильтром. Сразу же можно выделить три концептуальные части любого фильтра. Это приемная часть, которая занимается получением данных откуда-то, собственно обработчик, и передающая часть. Подобные абстракции предоставляют многие сетевые библиотеки, которые реализуют алгоритмы взаимодействия с сетью, оставляя программисту только реализацию обработки данных.

Однако программисту, помимо реализации алгоритма обработки, приходиться заниматься и другими малоприятными вещами, такими как сериализация/десериализация данных и обеспечения целостности приема-передачи.

Чтобы передать куда-либо данные их часто бывает необходимо перевести в последовательную форму (сериализовать). В случае с простыми структурами данных эта процедура может оказаться достаточно простой, достаточно знать расположение этой структуры в памяти и ее размер. Однако если мы передаем эту структуру по сети на компьютер с другой архитектурой, может возникнуть масса проблем, такие как другой порядок байтов в слове или количество байт отведенных под какой-либо тип данных. Аналогичная ситуация и с десерелизацией. Классы пространства имен migashko::filters не предоставляют универсальные алгоритмы сериализация/десериализация, однако с помощью удобных абстракций вы можете один раз реализовать эти механизмы и повторно использовать.

Другой проблемой при работе с потоками данных является обеспечение целостности приема. Чтобы правильно десериализовать данные, необходимо чтобы их было достаточно, для корректного проведения этой операции. Однако при передаче по сети данные разбиваются на пакеты, и могут приходить частями. Транспортные протоколы, типа TCP, гарантируют получение данных в том порядке, в котором они были отправлены, но вот задача по выделению из входного потока целостных структур ложиться на приложение.

Обратная ситуация при передаче данных в сеть. Если мы используем блокируемый ввод/вывод, то проблем особых нет – операция записи блокируется до тех пор, пока все данные не будут отправлены. Проблема в том, что на операции записи поток выполнения программы блокируется, что не всегда хорошо. При неблокируемом вводе/выводе эта проблема не встает, но появляется другая. Не все данные могут быть переданы за одну операцию, поэтому оставшиеся данные необходимо будет передать в следующий раз. В общем, обеспечение целостности приема и передачи не является непосильной задачей, но неприятной и неинтересной это точно. Классы пространства имен migashko::filters имеет в своем составе эффективные реализации алгоритмов приема/передачи данных по неблокируемым сокетам (дескрипторам).

Таким образом, в фильтре можно выделить следующие составные части:

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

Пользователю чаще всего придется реализовывать класс handler, реже read_filter_handler, write_filter_handler, в которых реализуются алгоритмы сериализации/десериализации данных и анализа входного потока, остальные классы также заменяемы по необходимости. Я, например, посчитал ненужным алгоритму передачи данных (класс sender) использовать парсер (как на reciver), однако вы можете считать иначе, просто замените стандартную реализацию sender на свою.

 
 
Current Location: Дома
 
 
Laphroaig
26 January 2007 @ 03:27 pm

Предположим у нас есть класс handler с методом doit() и doit2() и два класса обертки, которые подсчитывают количество вызовов методов doit и doit2 соответственно.

Задача простая: как собрать статистику вызовов со всех объектов не правя существующих классов?. Правильно - паттерн Visitor. Однако этот паттерн требует, чтобы все посещаемые классы имели соотвествующий интерфейс посещения. Мы сделаем проще - сделаем класс обертку:

Теперь разработаем класс visitor

В принципе в методе visit можно было и делать всю обработку, однако мы вынесли логику из класса, чтобы его можно было использовать повторно. Итак, создаем класс подсчитывающий вызовы метода doit и doit2

Проблема в том, что класс doit_statistics требует от посещаемого оъекта наличие двух методов: get_doit_count() и get_doit_count2(). Так что же, теперь надо делать визиторы для всех трех, вариантов. Мы сделаем круче:

Теперь мы можем посещать любые visitable объекты не заботясь о наличии у них необходимого интерфейса. Но мы на этом не остановимся. Создадим список посещения, чтобы один раз заполнить его, а затем по необходимость, повторить сбор статистики.

Ограничение visit_list в том, что он может работать только с одним типом посещаемых объектов. Исправим это досадное недоразумение с помощью списка типов и специализацией класса visit_list:

Уф... Теперь полный пример. Шаблон посетитель реализован в migashko::patterns:

К чему я все это? А ктому, что шаблон visitor используется в migashko::inet, для сбора статистики по подключениям и переданных/прочитанных данных. Кто догадаеться, что будет выведено на экран, тот может считать себя крутым программистом)))

 
 
Current Location: На работе
Current Music: куллер
 
 
 
Laphroaig

Простая задача: есть класс A, который является наследником класса С. Как просто добавить функционал к классу А не меняя эти два класса? Надо сделать обертку B для класса А. Пример:

Усложним задачу. Пусть имеется некоторый класс обработки сообщений handler, и некоторый класс connection отвечающий за прием и отправку этих сообщений. Классическая реализация:

Что мне здесь не нравиться? Слишком много виртуальных вызовов,а я это не люблю. Сделаем на шаблонах:

Теперь сделаем обертку для handler, которая подсчитывает количество вызовов doit.

Теперь предположим, что есть класс handler2, аналогичный handler, но имеет некоторый конструктор:

Теперь объект doit_counter нельзя создать, т.к. у класса doit_counter нет соответствующего конструктора. Что далать? Создать набор шаблонных конструкторов:

Теперь doit_counter не предьявляет требований по контструкторам к базовому классу. По подобному принципу разрабатываються библиотеки migashko::filters и migashko::inet, и предпологаеться что пользователи будут разрабатывать свои классы-обертки для расширения функционала. Проблема в том, что кода конструкторов гораздо больше чем собственно добавленного функционала. Что делать? У меня одна страшная мысль - макросы?!! Но пока мне в голову ничего лучше не пришло. Итак:

Ужоснах! Зато теперь doit_counter выглядит так:

Но что делать, если классу-обертке самому небходимо инициализироваться значения через конструктор? Опять макрос:

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

Кто подскажет как решить проблему с конструкторами не используя макросы, тому спасибо.

P.S. Конструктор копирования шаблонным конструктором не перекрываеться.

 
 
Current Location: На работе
 
 
Laphroaig
26 January 2007 @ 01:54 am

Сначала рассмотрим простой пример TCP эхо-сервера для демонстрации базовых возможностей библиотеки:

В строках 12-13 мы создаем объект сервера и запускаем его. Класс сервера определяется в строке 11. Здесь мы указываем, с каким типом данных будет работать сервер и обработчик этих самых данных, которые сервер получает от клиента. В строке 14 мы ожидаем подключение клиента. Как только клиент подключился, запускается цикл (строка 15) который и обеспечивает прием, обработку и передачу обработанных данных обратно клиенту. Метод rock() обеспечивает «перекачку» одной порции данных пришедшей от клиента. Как вы уже догадались, обработку данных обеспечивает метод doit класса echo_handler.

Класс echo_handler наследуется от handler_base, который предоставляет всего два метода-заглушки, on_init и on_release, которые обязан предоставлять обработчик. Довольно странно выглядит метод doit, который имеет два шаблонных параметра. На самом деле здесь все довольно просто. Первый параметр это ссылка на объект текущего соединения, в данном случае my_server::connection_type, а второй параметр – это тип представления принятых данных, которые, в данном случае, имеют тип std::vector.

От данного примера мало толку, т.к. он обслуживает только одного клиента и завершает работу. Попробуем изменить код сервера таким образом, чтобы он мог обрабатывать запросы множества клиентов одновременно. Для этого заменим строчки 12-15 следующим кодом:

Только что мы создали очень эффективный сервер на неблокируемых сокетах с использованием системного вызова select. Как вы уже догадались, вся работа с этой функцией скрыта в классе selector. В конструкторе, при создании объекта srv, мы передаем указатель на этот объект, и указываем, что будем работать с неблокируемыми сокетами (второй параметр). Далее запускаем сервер, и бесконечный цикл обработки. Здесь нет явных вызовов accept и rock, вся работа с этими и другими методами скрыта в библиотеке.

Селектор может работать с несколькими серверами (в данном случае под сервером мы имеем виду объект класса созданного на базе класса tcp_server или других), для этого при создании объекта просто укажите в конструкторе, с каким объектом селектора будет работать новый сервер. При вызове метода select класса selector процесс переходит в состояние ожидания, до тех пор, пока не произойдет какое-либо событие на сокетах или не истечет время ожидания, указанное в качестве параметра в миллисекундах (-1 – бесконечное время ожидания).

В следующем примере мы добавим код, который демонстрирует описанные выше и некоторые другие возможности:

В этом примере мы перегрузили метод doit, таким образом, что если типом обрабатываемых данных будет string, то каждая порция пришедших данных будет выводиться на экран, в противном случае данные просто будут переданы обратно клиенту. Для демонстрации мы создали два сервера, один из которых работает со строками, а другой с вектором символов. Таким образом, если клиент подключается по порту 3333, то на экран будут выводиться все данные, которые он передал серверу. Кроме того, мы добавили код, который выводит раз в секунду символ ‘.’, если сервер простаивает. Для тестирования можете запустить утилиту telnet и подключиться к нашему серверу по портам 3333 или 3334 и покидать ему данные.

 
 
Current Location: Дома