Portable Components, кроссплатформенная библиотека для C++

в 20:32, , рубрики: boost, c++, Poco, кроссплатформенная разработка, кроссплатформенность, сетевое программирование, метки: , , , , ,

«Система должна быть спроектирована так,
чтобы оставаться как можно проще
после серии внесенных в нее изменений»

Бьёрн Страуструп – программист, автор языка C++

Преамбула

В данной статье мне бы хотелось бы рассказать о довольно популярной, но так редко освещаемой на Хабре библиотеке Portable Components (сокр. POCO). Она будет полезна как разработчикам бизнес-логики программного продукта, так и в решении большинства прикладных задач. При всем изобилии кроссплатформенных библиотек для C++ всё больше людей сталкиваются с POCO лицом к лицу и не знают с чего начать. В данной статье я постараюсь описать технологии, заложенные в библиотеке и дать простейшие примеры решения некоторых задач. Также хотелось бы отметить, что за плечами библиотеки множество успешных как Open Source, так и коммерческих проектов.

Описание

POCO представляет собой коллекцию классов, упрощающих процесс разработки, отладки и тестирования программного продукта. В основе библиотеки лежит принцип модульной системы с малой степенью связности. Как и её коллега Boost, POCO разбита на модули, каждый из которых выполняет свою роль в системе, однако не так усердно использует шаблонную магию парадигму обобщённого программирования как Boost, что позволяет избежать некоторых проблем связанных с поиском ошибок. Еще одним существенным плюсом является размер конечного дистрибутива – размер ядра POCO (Poco Foundation) занимает порядка полутора мегабайт памяти (версия 1.4.3.0, MSVC v100), остальные модули от 50 до 800 КБ, что в современном мире совсем не много даже для встраиваемых систем. Applied Informatics Software Engineering GmbH – разработчик POCO даже заявляют поддержку таких крохотных устройств как Digi Connect ME 9210, выполненных в форм-факторе коннектора RJ-45, имеющего на борту всего 2 или 4 МБ флеш и 8МБ ОЗУ.

POCO ориентирована, в основном, на сетевую разработку и портирована на популярные операционные системы: Windows, Unix, Linux, eLinux, Mac OS X, Solaris, QNX Neutrino, Vxworks, Openvms, Tru64, HP-UX, Android. Данный список довольно быстро расширяется, так как все платформозависимые модули отделены от логики. Фактически, на любую операционную систему, для которой имеется современный компилятор для языка C++, можно портировать POCO.

Библиотека может быть применена как основа к любой разработке, будь то программное обеспечение для сервера или клиента, при этом приложение гарантированно запустится на любой из имеющихся архитектур (признаюсь, иногда не без особой магии). Притом POCO обладает такими мощными средствами как TCP Server Framework, Reactor Framework, которые позволяют очень легко создавать высокопроизводительные WEB-сервера, позволяя нам с вами сэкономить время на разработке. Также в библиотеке присутствует оснастка для создания консольных приложений, демонов Unix, служб Windows. Возможна параллельная работа с такими библиотеками как Qt, wxWidget, GTK+.

Концепции некоторых частей библиотеки позаимствованы из Java Class Library, MS .NET Framework и Apple Cocoa. Это в основном высокоуровневые вещи, такие как управление потоками или таймеры.

Poco написана c строгим соблюдением стандарта языка ANSI/ISO C++ 2003 с помощью стандартной библиотеки C++ и STL. Выпускается под лицензией «Boost Software License», допускающей как коммерческое, так и некоммерческое использование.

Как изучать POCO

Большую часть примеров вы можете найти в исходных кодах, в SDK Reference и презентациях.

Основные модули и примеры использования

Ядро библиотеки

  • Средства динамической типизации данных Any и DynamicAny
    DynamicAny any("99");
    int i = any; // i == 99
    any = 65536;
    string s = any; // s == "65536"
    char c = any; // слишком большое число, вызовется исключение RangeException
    

  • Манипуляции со временем и датой
    const char *date = "05.06.1977"; //Apple 2 birthday
    int Diff;
    DateTime time = DateTimeParser::parse("%d.%m.%Y", date, Diff);
    Timespan diff = DateTime() - time;
    
    cout	<< "Apple 2 существует уже" << diff.days() << " дней с "
    << DateTimeFormatter::format(time, "%e %B %Y, %W", Diff) << endl; // "5 June 1977, Sunday"
    
    cout	<< "Тот год был "
    << (DateTime::isLeapYear(time.year()) ? "високосным" : "не високосным");
    

  • Средства работы с событиями и оповещениями
    #include "Poco/BasicEvent.h"
    #include "Poco/Delegate.h"
    #include <iostream>
    
    using BasicEvent;
    using Delegate;
    
    class Source
    {
    public:
     BasicEvent<int> theEvent;
    
     void fireEvent(int n)
     {
     theEvent(this, n);
     }
    };
    
    class Target
    {
    public:
     void onEvent(const void* pSender, int& arg)
     {
     cout << "onEvent: " << arg << endl;
     }
    };
    
    int main(int argc, char** argv)
    {
     Source source;
     Target target;
    
    //Создаем делегат и добавляем в список Event'ов
     source.theEvent += Delegate<Target, int>(	
     &target, &Target::onEvent);
    
    //Отправляем событие
     source.fireEvent(42);	
    
    //Разделегируем
     source.theEvent -= Delegate<Target, int>(
     &target, &Target::onEvent);
    
     return 0;
    }
    

  • Обработка регулярных выражений
    RegularExpression regular("([0-9]+) ([0-9]+)");
    
    vector<string> arr;
    regular.split("123 456", 0, arr); //arr = {“123 456”, ”123”, ”456”}
    
    string s = "123 456";
    regular.subst(s, "$2 $1"); // s == "456 123"
    

  • Средства работы с динамически-подключаемыми библиотеками.
    SharedLibrary lib("C:\Windows\System32\shell32.dll");
    if (lib.isLoaded() && lib.hasSymbol("Control_RunDLL"))
    {
    auto fun = static_cast<void (*)()>(lib.getSymbol("Control_RunDLL"));
    
    fun(); //Открываем панель управления
    
    lib.unload();
    }
    return 0;
    

  • Средства управления памятью
    //Умные указатели
    class A {};
    class B : public A {} ;
    class C : public A {};
    …
    SharedPtr<A> pA;
    SharedPtr<B> pB(new B);
    pA = pB;	// Возможно, B подкласс A
    pA = new B;	// Капельку полиморфизма
    
    // pB = pA;	 // Нельзя, у SharedPtr запрет прямого копирования
    pB = pA.cast<B>();	// а так можно
    SharedPtr<C> pC(new C);
    pB = pC.cast<B>();	// pB теперь null
    
    //Динамическая фабрика
    DynamicFactory<A> factory;
    
    factory.registerClass<B>("B"); // создаем Instantiator<B, A>
    factory.registerClass<C>("C"); // создаем Instantiator<C, A>
    
    
    SharedPtr<A> pA = factory.createInstance("B");
    SharedPtr<A> pB = factory.createInstance("C");
    
    factory.unregisterClass("C");
    
    bool isA = factory.isClass("B"),			// true
    isB = factory.isClass("C"),			// false (не зарегистрирован)
    isC = factory.isClass("habrahabr");	// false (да вы что?!)
    
    //Буфер
    Buffer<char> buffer(1024);	// Создаем буфер на 1024 элемента
    cin.read(buffer.begin(), buffer.size()); // Читаем эти 1024 элемента из stdin
    streamsize n = cin.gcount();	 
    string s(buffer.begin(), n);	// Формируем строку из принятых данных
    cout << s << endl;	// Профит
    
    //Пул памяти
    MemoryPool pool(1024); // Неограниченное число 1024 байтных блоков
    // MemoryPool pool(1024, 4, 16); // До 16 блоков; 4 выделить заранее
    
    char* buffer = static_cast<char*>(pool.get());	//Получаем буфер из пула
    cin.read(buffer, pool.blockSize());	//Считываем из stdin один блок
    streamsize n = cin.gcount();
    string s(buffer, n); //Составляем строку
    pool.release(buffer); //Отчищаем строку
    
    cout << s << endl; //Профит
    
    
    

  • Синглтоны
    class Singleton
    {
    public:
    int add(int i, int p) { return i+p; }
    static Singleton& instance()
    {
    static SingletonHolder<Singleton> single;
    return *single.get();
    }
    };
    …
    int res = Singleton::instance().add(1,2);
    

  • Средства форматирования строк
    //printf-style formating
    s	tring str = format("In %[0]d was %[1]s of %[2]s %[3]d", 
    DateTime(1977,6,5).year(),	// %[0]d
    string("b-day"),	// %[1]s
    string("Apple"),	// %[2]s 
    2); 				// %[3]d
    //str = “In 1977 was b-day of Apple 2”
    

  • Информация о системе и работа с переменными окружения
    cout	<< "Операционная система: " << Environment::osName()
    << "nВерсия ОС: " << Environment::osVersion()
    << "nАрхитектура: " << Environment::osArchitecture()
    << "nИден. пользователя: " << Environment::nodeName()
    << "nMAC-адрес: " << Environment::nodeId();
    
    if (Environment::has("HOME"))
    cout << "nДомашняя дир.: " << Environment::get("HOME") << endl;
    
    Environment::set("POCO", "foo");	//Устанавливаем переменную окружения FOO=foo
    

Сжатие данных

  • Средства для реализации потокового сжатия данных
  • Средства для создание ZIP файлов
    ofstream zipper("text.gz", ios::binary);
    ofstream text("text.txt", ios::binary);
    
    text << "Hello my little computer world. I can zip this string. It's wonderful.";
    text.close();
    
    Zip::Compress zipc(zipper,true);
    zipc.addFile (Path("text.txt"), "text.txt");
    zipc.close();
    
    ifstream dezipper("text.gz", ios::binary);
    poco_assert (dezipper);
    Zip::Decompress zipd(dezipper,Path().append("\text.dz"));
    zipd.decompressAllFiles();
    

Криптография

  • Генератор псевдослучайных чисел
    Random rnd;
    rnd.seed();
    
    cout	<< "Целочисленное: " << rnd.next()
    << "nЦелочисленное: " << rnd.next(10)
    << "nБайт: " << rnd.nextChar()
    << "nБит: " << rnd.nextBool()
    << "nПлавающее с дв. точностью: " << rnd.nextDouble();
    
    RandomInputStream ri;
    string rs;
    ri >> rs;
    

  • Механизм создания ХЭШ-ей
    string message("Это секретное послание никто не должен разгадать");
    string passphrase("anl!sfsd9!_3g2g?f73");	//Делаем сильный пароль
    
    //HMAC = Hash-based message authentication code ;)
    HMACEngine<SHA1Engine> encoder(passphrase);
    encoder.update(message);	//Хэшируем
    
    //С помощью encoder.digest() получаем вектор байт
    string encodedstr(DigestEngine::digestToHex(encoder.digest()));
    
    //Хэшируем MD5 поток
    MD5Engine md5;
    DigestOutputStream ostr(md5);
    
    ostr << "Это секретное послание никто не должен разгадать";
    ostr.flush();
    
    //Получаем результат в HEXе
    string result = DigestEngine::digestToHex(md5.digest());
    

  • Поддержка сертификатов X509
  • Механизмы шифрования данных
  • Потоковая криптография
  • Поддержка OpenSSL
Базы данных

  • Взаимодействие с различными базами данных (SQLite, MySQL, ODBC, etc)
    Data::SQLite::Connector::registerConnector();
    Data::Session ses("SQLite", "mydb.db");
    
    int count = 0;
    ses << "SELECT COUNT(*) FROM PERSON", into(count), now;
    cout << "People in DB " << count;
    
    string firstName("Петр";
    string lastName("Иванов");
    int age = 0;
    ses << "INSERT INTO PERSON VALUES (:fn, :ln, :age)", use(firstName), use(lastName), use(age), now;
    ses << "SELECT (firstname, lastname, age) FROM Person", into(firstName), into(lastName), into(age, -1), now;
    
    Data::SQLite::Connector::unregisterConnector();
    

  • Пул сессий
  • RecordSet
    Statement select(session);
    select << "SELECT * FROM Person";
    select.execute();
    RecordSet rs(select);
    

  • Кортежи
    struct Person
    {
    string fullname, city;
    size_t age;
    };
    …
    typedef Tuple<string, string, int> Person;
    
    vector<Person> people;
    people.push_back(Person("Bart Simpson", "Springfield", 12));
    people.push_back(Person("Lisa Simpson", "Springfield", 10));
    
    Statement insert(session);
    insert << "INSERT INTO Person VALUES(:name, :address, :age)",
    use(people), now;
    

Файловая система

  • Платформонезависимая работа с файлами и директориями
    Path p_wind("C:\Windows\system32\cmd.exe");
    Path p_unix("/bin/sh");
    
    p_wind = "projects\poco";
    p_unix = "projects/poco";
    
    p_unix.parse("/usr/include/stdio.h", Path::PATH_UNIX);
    
    bool ok = p_unix.tryParse("/usr/*/stdio.h");
    ok = p_unix.tryParse("/usr/include/stdio.h", Path::PATH_UNIX);
    ok = p_unix.tryParse("/usr/include/stdio.h", Path::PATH_WINDOWS);
    ok = p_unix.tryParse("DSK$PROJ:[POCO]BUILD.COM", Path::PATH_GUESS);

Работа с логами

  • Мощное средство управления логами
  • Форматирование логов
  • Каналы передачи логов
    //Простой файловый логгер
    AutoPtr<SimpleFileChannel> pChannel(new SimpleFileChannel);
    pChannel->setProperty("path", "log.log");
    pChannel->setProperty("rotation", "2 K");
    Logger::root().setChannel(pChannel);
    
    Logger& logger = Logger::get("TestLogger"); // Получаем логгер TestLogger
    
    for (int i = 0; i < 13; ++i)
    logger.information("Это сообщение выведется 13 раз");
    
    
    //Расширенный файловый логгер
    AutoPtr<FileChannel> pChannel(new FileChannel);
    pChannel->setProperty("path", " log.log");
    pChannel->setProperty("rotation", "2 K");
    pChannel->setProperty("archive", "timestamp");
    Logger::root().setChannel(pChannel);
    
    Logger& logger = Logger::get("TestLogger"); // Получаем логгер TestLogger
    
    for (int i = 0; i < 13; ++i)
    logger.information("Это сообщение выведется 13 раз");
    
    //Асинхронный консольный логгер
    AutoPtr<ConsoleChannel> pCons(new ConsoleChannel);
    AutoPtr<AsyncChannel> pAsync(new AsyncChannel(pCons));
    
    Logger::root().setChannel(pAsync);
    
    Logger& logger = Logger::get("TestLogger"); // Получаем логгер TestLogger
    
    for (int i = 0; i < 13; ++i)
    logger.information("Это сообщение выведется 13 раз");
    
     
    
    //Консольно-файловый логгер
    AutoPtr<ConsoleChannel> pCons(new ConsoleChannel);
    AutoPtr<SimpleFileChannel> pFile(new SimpleFileChannel("test.log"));
    AutoPtr<SplitterChannel> pSplitter(new SplitterChannel);
    
    pSplitter->addChannel(pCons);
    pSplitter->addChannel(pFile);
    
    Logger::root().setChannel(pSplitter);
    Logger::root().information("Выводим в файл и на консоль");
    
    Logger& logger = Logger::get("TestLogger");
    LogStream lstr(logger);
    lstr << " Выводим в файл и на консоль через поток" << endl;
    
    //Форматированный лог
    AutoPtr<ConsoleChannel> pCons(new ConsoleChannel);
    AutoPtr<PatternFormatter> pPF(new PatternFormatter);
    
    //Формат "Год-месяц-день час:минута:секунда отправитель: текст"
    pPF->setProperty("pattern", "%Y-%m-%d %H:%M:%S %s: %t");
    
    AutoPtr<FormattingChannel> pFC(new FormattingChannel(pPF, pCons));
    
    Logger::root().setChannel(pFC);
    Logger::get("TestChannel").information("Форматированый вывод в консоль");
    

Многопоточность

  • Платформонезависимая поддержка потоков и пулов потоков
    //Наследуемся от Runnable. Hello JAVA.
    class ThreadRunner: public Runnable
    {
    	virtual void run()
    	{
    		cout << "Это сообщение выведено из нового треда" << endl;
    	}
    };
    
    //Ни от чего не наследуюемся
    class ThreadRunnerNoRunnable
    {
    	void norun()
    	{
    		cout << "Это сообщение выведено из нового треда" << endl;
    	}
    };
    
    …
    ThreadRunner runnable;
    Thread thread;
    
    thread.start(runnable); //Запускаем тред
    thread.join(); //Ждем его завершения
    
    ThreadRunnerNoRunnable norunnable;
    Poco::RunnableAdapter< ThreadRunnerNoRunnable > 
    runnable2(norunnable, & ThreadRunnerNoRunnable::norun);
    
    Thread thread2; 
    Thread2.start(norunnable); //Запускаем тред
    Thread2.join(); //Ждем его завершения
    

  • Средства синхронизации потоков
    Пул тредов и мьютексы:
    class ThreadRunner: public Poco::Runnable
    {
    	virtual void run()
    	{
    		static int num;
    		Mutex::ScopedLock lock(_mutex);
    
    		std::cout << "Это сообщение выведено из нового треда " << std::endl;
    		for (int i=0; i<100; ++i)
    			++num;
    	}
    private:
    	Mutex _mutex;
    };
    …
    ThreadRunner runnable1, runnable2, runnable3;
    
    //Запускаем треды
    Poco::ThreadPool::defaultPool().start(runnable1);
    Poco::ThreadPool::defaultPool().start(runnable2);
    Poco::ThreadPool::defaultPool().start(runnable3);
    
    //Ждем их выполнения
    Poco::ThreadPool::defaultPool().joinAll();
    

  • Рабочие очереди
  • Таймеры
  • Менеджер задач
  • Active Objects: Activities и Active Methods

Сетевые технологии

  • Сокеты
    Poco::Net::SocketAddress sa("www.habrahabr.ru", 80);
    Poco::Net::StreamSocket socket(sa);
    Poco::Net::SocketStream str(socket);
    str << "GET / HTTP/1.1rn"
    "Host: habrahabr.rurn"
    "rn";
    
    str.flush();
    Poco::StreamCopier::copyStream(str, std::cout);
    
    //Слушающий сокет
    Poco::Net::ServerSocket srv(8080); // Биндим и начинаем слушать
    while(true)
    {
    	Poco::Net::StreamSocket ss = srv.acceptConnection();
    	{
    		Poco::Net::SocketStream str(ss);
    		str << "HTTP/1.0 200 OKrn"
    		"Content-Type: text/htmlrn"
    		"rn"
    		"<html><head><title>Доброго времени суток</title></head>"
    		"<body><h1><a href="http://habrahabr.ru">Добро пожаловать на Хабр.</a></h1></body></html>"
    		<< std::flush;
    	}
    }
    

  • Многопоточные серверы
  • DNS
    const HostEntry& entry = DNS::hostByName("www.habrahabr.ru");
    std::cout << "Каноническое имя: " << entry.name() << std::endl;
    
    const HostEntry::AliasList& aliases = entry.aliases();
    for (HostEntry::AliasList::const_iterator it1 = aliases.begin(); it1 != aliases.end(); ++it1)
    std::cout << "Хост: " << *it1 << std::endl;
    
    const HostEntry::AddressList& addrs = entry.addresses();
    for (HostEntry::AddressList::const_iterator it2 = addrs.begin(); it2 != addrs.end(); ++it2)
    std::cout << "IP: " << it2->toString() << std::endl;
    

  • Клиенты для некоторых протоколов прикладного уровня (FTP, SMTP, etc.)
  • HTTP(S)-сервер, HTTP аутентификация
  • Компилятор С++ кода — вставок HTML, обработка форм HTML, MIME
  • Поддержка URI, UUID
  • Поддержка RFC 6455
  • Поддержка SSL/TLS

Процессы

  • Манипуляции с процессами (запуск, остановка)
    cout << "Мой PID: " << Process::id() << endl;
    vector<string> args;
    args.push_back("shell32");
    args.push_back("ShellAboutA");
    
    Process::launch("rundll32", args);
    

  • Синхронизация процессов
  • Механизм общей памяти
    File file("Shared.dat");
    
    SharedMemory mem(file, SharedMemory::AM_READ);
    cout << &*mem.begin() << endl;
    

Потоковые операции

  • Потоковое кодирование/декодирование BASE-64, HexBinary
    Base64Encoder encoder(std::cout);
    encoder << "Это сообщение закодируется в Base64 и выведется в stdout";
    

  • Потоки ввода вывода в память
  • Поддержка URI-потоков
    Poco::Net::HTTPStreamFactory::registerFactory();
    std::auto_ptr<std::istream> input(
    Poco::URIStreamOpener::defaultOpener().open("http://habrahabr.ru")
    );
    
    cout << input->rdbuf();
    

Кодирование текста

  • Поддержка кодирование в/из UTF-8 и Unicode
    std::string utf8("Это UTF-8 строка.");
    UTF8Encoding decoder;
    
    TextIterator end(utf8);
    
    for (TextIterator iter(utf8, decoder); iter != end; ++iter)
    int unicode = *iter;
    

  • Поддержка конвертирования кодировок
    std::string latin("Это Latin1 строка.");
    
    Latin1Encoding decoder;
    UTF8Encoding encoder;
    
    OutputStreamConverter converter(std::cout, decoder, encoder);
    converter << latin << std::endl; //Теперь latin - UTF-8 строка
    

Utility

  • Фреймворк для создания консольных и серверных приложений.
  • Поддержка аргументов командной строки

Конфигурирование

  • Создание Unix-daemons и сервисов Windows

XML

  • Поддержка DOM, SAX2
  • Поддержка как режим чтения, так и режим записи

Заключение

В заключение хотелось бы отметить, что описание некоторых модулей было опущено вследствие того, что модули эти заслуживают отдельного внимания и требуют отдельных статей. Я искренне верю, что у данной библиотеки есть светлое будущее. Также хотелось бы видеть в POCO некоторые плюшки из нового стандарта C++11.
Следующей статьей хотелось бы рассказать о специфичных вещах в POCO. Буду рад объективным замечаниям и возражениям.
Спасибо за прочтение.

Автор: nephrael

* - обязательные к заполнению поля