Содержание


Conexus – универсальная библиотека ввода/вывода для ЯП C++

Comments

Серия контента:

Этот контент является частью # из серии # статей:

Следите за выходом новых статей этой серии.

Этот контент является частью серии:

Следите за выходом новых статей этой серии.

1. Какие задачи решает библиотека Conexus

Conexus – это универсальная библиотека ввода/вывода, предназначенная для языка программирования C++ и обеспечивающая поддержку работы с BSD-сокетами (IPv4, IPv6, TCP, UDP), защищёнными сокетами OpenSSL, сокетами NSPR (Netscape Pirtable Runtime), потоками NSS (Network Security Services), очередями сообщений ядра, файлами и программными каналами, терминалами, подключаемыми через последовательные порты, виджетами Gtkmm и др.

Изначально библиотека создавалась для упрощения процесса разработки языка визуального программирования Isaac, используемого для управления роботами, двигающимися в автономном режиме. Разработка велась в университете штата Нью-Мексико [New Mexico State University]. Первоначальный вариант библиотеки предоставлял платформе робота возможность передавать и принимать последовательные потоки данных. В дальнейшем библиотека постепенно обрастала новыми функциональными возможностями и к настоящему моменту превратилась в универсальный инструмент ввода/вывода.

2. Ключевые принципы функционирования библиотеки

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

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

Все объекты "конечная точка" создаются наследованием от базового класса Conexus::Endpoint, в том числе и конечные точки последовательной передачи данных, такие как IPv4/TCP, IPv4/UDP, очереди сообщений ядра, программные каналы (pipes) и т. д.

Другим важным понятием в контексте библиотеки являются "данные". Класс Conexus::Data, по существу, представляет собой инкапсулированный буфер данных (упрощённо говоря, указатель на блок байтов) с обязательной характеристикой – размером этого буфера в байтах.

Кроме атрибутов "буфер" и "размер", Conexus::Data предоставляет механизм хранения и применения значения приоритета, который может потребоваться при создании очередей с приоритетами, а также отдельный атрибут "время".

2.1. Обработка событий

Для перехода к модели, управляемой событиями, вводятся понятия конечной точки пуска (starting endpoint) и конечной точки останова (stopping endpoint). В некоторых случаях данные можно просто считывать через Conexus::Endpoint, используя метод read(), аналогичный POSIX-функции с тем же именем. Тем не менее Conexus::Endpoint предоставляет ещё и методы start() и stop(). При инициализации объект типа Conexus::Endpoint создаёт отдельный поток обслуживания, который отслеживает ввод данных и генерирует событие signal_data при поступлении данных в эту конечную точку.

Чтобы принять данные, пришедшие в конечную точку, необходимо установить связь события signal_data с функцией или методом класса, которые будут осуществлять дальнейшую обработку этих данных. Эта задача решается с помощью механизма "сигнал-слот", предоставляемого библиотекой sigc++.

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

void имя_функции( const Conexus::Data )

Следует обратить особое внимание на то, что первым параметром для этой функции обязательно должна быть константа типа Conexus::Data. Если необходимо передавать другие параметры, то следует воспользоваться объектом типа sigc::bind (подробности см. в документации к библиотеке sigc++).

Второй шаг – создание слота sigc++. Этот слот будет принимать данные из конечной точки. В большинстве случаев для этого используются объекты типа sigc::mem_fun или sigc::ptr_fun. Для слота требуется такая сигнатура:

sigc::slot<void, Conexus::Data>

Заключительным шагом является установление связи между слотом и конечной точкой с использованием метода connect() события signal_data.

3. Пример обработки исключения EOF при чтении файла

После изрядной доли теоретических изысканий перейдём к практическим примерам. Начнём с самого простого – с обработки признака конца файла – для того чтобы лучше понять, как работать с библиотекой Conexus. Приведённый ниже исходный код размещается в файле con_eof.cpp. В файле con_test.txt можно разместить произвольный текст для тестирования.

01: #include <conexus/conexus.h>
02: #include <iostream>

03: int main( int argc, char *argv[] )
04: {
05:   Conexus::Data dbuf;
  
06:   Conexus::init();
07:   Conexus::File::pointer fp = 
               Conexus::File::create( "con_test.txt", Conexus::FILE_READ );
08:   while( true )
09:   {
10:     try
11:     {
12:       dbuf = fp->read(1);
13:       std::cout << dbuf[0];
14:     }
15:     catch( Conexus::exception::read::eof )
16:     {
17:       break;
18:     }
19:   }
20:   fp->close();
  
21:   fp->open();
22:   fp->set_throw_eof( false );
23:   while( !fp->eof() )
24:   {
25:     dbuf = fp->read(1);
26:     if( dbuf )
27:       std::cout << dbuf[0];
28:   }
29:   fp->close();
30:   return 0;
31: }

В целом исходный код программы почти не отличается от обычного кода на C++. Отметим только строку 05 – создание объекта буфера данных типа Conexus::Data, строку 06 – инициализация контекста Conexus и строку 07 – создание указателя на файл, открываемый в режиме "только для чтения".

В этой программе демонстрируются два способа обработки ситуации, в которой встречается признак конца файла. В первом варианте (строки 08–20) выполняется явный перехват исключения Conexus::exception::read::eof, обработка которого состоит в том, что цикл чтения прерывается (строка 17), после чего файл закрывается (строка 20).

Следует обратить внимание на то, что после закрытия файла, указатель на него остаётся активным, и это позволяет вновь открыть данный файл с помощью того же указателя fp (строка 21).

В строках 21–29 реализован второй вариант обработки признака конца файла. Здесь генерация исключения отменяется (строка 22), а проверка появления признака конца файла включена в условие цикла (строка 23). Результаты выполнения обоих вариантов идентичны.

Теперь, когда основной принцип практического применения библиотеки Conexus понятен, можно перейти к более сложному примеру.

4. Пример реализации простой системы "клиент-сервер"

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

4.1. Программа-сервер

Начнём с реализации сервера. Исходный код содержится в файле con_fserver.cpp, а тестовый файл используем тот же, что и в предыдущем примере – con_test.txt.

01: #include <conexus/conexus.h>
02: #include <sys/types.h>
03: #include <unistd.h>
04: #include <iostream>
05: #include <vector>

06: void on_new_connection( Conexus::Endpoint::pointer endpoint );

07: int main( void )
08: {
09:   Conexus::init();     //инициализация библиотеки
10:   Conexus::IPv4::TCPServer::pointer tcp_srv =
                 Conexus::IPv4::TCPServer::create( 21177 );
11:   tcp_srv->signal_new_endpoint().connect( sigc::ptr_fun(&on_new_connection));
12:   tcp_srv->start();
13:   std::cout << "Идентификатор основного потока: " << pthread_self() << std::endl;
14:   std::cout << "Запуск..." << std::endl;
15:   for( int i=1; i <= 60; i++ )
16:   {
17:     if( i%10 == 0 )
18:       std::cout << "Время, сек.: " << i << std::endl;
19:     sleep(1);
20:   }
21:   tcp_srv->stop();
22:   return 0;
23: }

24: void on_new_connection( Conexus::Endpoint::pointer endpoint )
25: {
26:   Conexus::File::pointer fp = 
               Conexus::File::create( "con_test.txt", Conexus::FILE_READ );
27:   Conexus::Data dbuf = fp->read(10240);
28:   std::cout << "Считано из файла: " << (const char *)(dbuf.data()) << std::endl;
29:   endpoint->write( dbuf );
30:   sleep(1);
31: }

Здесь в строке 06 объявляется функция-обработчик события "создание новой конечной точки", которое возникает в момент приёма запроса от клиента.

Создаваемый сервер является многопоточным, поэтому во время инициализации библиотеки (строка 09) выполняется инициализация механизма pthreads, но этот процесс скрыт от пользователя для того, чтобы предоставить возможность сосредоточиться на прикладной задаче.

В строке 10 создаётся и инициализируется объект "TCP-сервер" с указанием номера прослушиваемого им порта 21177.

Далее, в строке 11 выполняется связывание события, соответствующего входящему запросу от клиента, со слотом, предоставляемым библиотекой sigc++, который будет вызван для обработки этого события. В нашем случае это ранее объявленная функция on_new_connection().

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

В строках 15–20 организован цикл ожидания, продолжающийся в течение 60 секунд и через каждые 10 секунд выдающий контрольные "засечки" времени. Поскольку, как уже было отмечено ранее, сервер является многопоточным, системный вызов sleep(1) не будет оказывать никакого влияния на работу потока, обслуживающего запрос клиента.

Строка 21 – останов сервера и подготовка к завершению программы.

Функция on_new_connection() (строки 24–31) занимается непосредственно обработкой запроса, т. е. открывает требуемый файл, считывает из него данные (процедура чтения преднамеренно упрощена), даёт контрольный вывод считанных данных на консоль и выполняет процедуру записи этих данных через конечную точку endpoint, указатель на которую передаётся в функцию, как аргумент. В итоге клиент получает затребованные данные.

4.2. Программа-клиент

Исходный код клиента располагается в файле con_fclient.cpp. Клиент обращается к серверу, определяемому комбинацией "адрес_хоста-порт", при этом само это обращение фактически является запросом. Данные, предоставленные сервером, клиент сохраняет в собственном файле.

01: #include <conexus/conexus.h>
02: #include <iostream>

03: void save2file( const Conexus::Data dbuf, Conexus::File::pointer fp );

04: int main( int argc, char *argv[] )
05: {
06:   Conexus::init();
07:   Conexus::IPv4::TCP::pointer tcp_clnt = Conexus::IPv4::TCP::create();
08:   Conexus::IPv4::Address host_addr;
09:   Conexus::File::pointer fp_sv = 
         Conexus::File::create( "./savefile.txt", 
                              Conexus::FILE_CREATE | Conexus::FILE_WRITE );
10:   char default_host[] = "127.0.0.1";
11:   char *host = default_host;
12:   int port = 21177;

13:   if( argc > 1 )
14:     host = argv[1];
15:   if( argc > 2 )
16:     port = atoi( argv[2] );

17:   host_addr.set_host( host );
18:   host_addr.set_port( port );

19:   tcp_clnt->signal_data().connect(sigc::bind(sigc::ptr_fun(&save2file),fp_sv));

20:   tcp_clnt->connect( host_addr );
21:   tcp_clnt->start();
22:   sleep(1);

23:   fp_sv->close();
24:   tcp_clnt->close();
25:   sleep(1);
26:   return 0;
27: }

28: void save2file( const Conexus::Data dbuf, Conexus::File::pointer fp )
29: {
30:   std::cout << "Полученные данные: " << std::endl;
31:   std::cout << (const char *)(dbuf.data()) << std::endl;
32:   fp->write( dbuf );
33: }

В приведённом выше исходном коде следует обратить внимание в первую очередь на строки 07–12 в начальном блоке функции main. В строке 07 инициализируется указатель на объект, отвечающий за установление TCP-соединения с сервером. В строке 08 объявляется объект типа Address, используемый для определения характеристик соединения; в данном примере это адрес и порт сервера. Файл для сохранения получаемых данных задан в строке 09. Адрес хоста по умолчанию и номер порта по умолчанию присваиваются в строках 10–12.

Строки 13–16 – проверка наличия параметров, переданных в программу извне. Если пользователь при вызове программы клиента ввёл адрес и номер порта, то значения этих параметров заменяют соответствующие значения, принятые по умолчанию внутри программы.

Строка 19 – процедура связывания сигнала о поступлении данных от сервера с функцией обработки данных, реагирующей на этот сигнал. Имя функции save2file() сообщает о том, какую операцию она выполняет.

Далее устанавливается соединение с сервером (строка 20), и клиент ожидает ответа (строки 21–22). В момент приёма данных вызывается функция обработки (строки 28–33), в которой выполняется контрольный вывод данных на консоль и запись в заранее заготовленный файл. Буфер с принятыми данными и указатель на целевой файл передаются в функцию обработки, как параметры.

После того как функция обработки save2file завершила свою работу, управление возвращается в основную часть программы. Здесь выполняются операции закрытия целевого файла с сохранёнными данными (строка 23), завершение соединения с сервером (строка 24) и после секундной паузы выход из программы-клиента.

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

5. Заключение. Оценка функциональных возможностей Conexus

В универсальной библиотеке Conexus собраны практически все инструменты для решения разнообразных задач ввода и вывода данных. Средства библиотеки позволяют существенно снизить трудозатраты при создании программ. Только представьте себе, какой объём исходного кода потребовался бы для написания многопоточного файлового сервера на "чистом C++". Conexus избавляет разработчика от большинства проблем, возникающих при программировании на относительно низком, системном уровне. Возможно, в простых примерах, приведённых в данной статье, конструкции Conexus могут показаться излишне громоздкими и неудобными. Но с повышением сложности программ (что неизбежно в "промышленном" программировании) всё ярче будут проявляться главные преимущества Conexus – ее гибкость и универсальность.


Ресурсы для скачивания


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux
ArticleID=482916
ArticleTitle=Conexus – универсальная библиотека ввода/вывода для ЯП C++
publish-date=04152010