У вас есть вопрос , который не требует открытия проблемы? Присоединяйтесь к каналу Gitter.
Если вы используете uvw
и хотите сказать спасибо или поддержать проект, рассмотрите возможность стать спонсором .
Вы можете помочь мне изменить ситуацию. Огромное спасибо тем, кто поддерживал меня и поддерживает до сих пор.
uvw
начинался как маленькая и простая в использовании оболочка для libuv
, основанная на событиях и написанная на современном C++.
Теперь она наконец доступна и в виде компилируемой статической библиотеки.
Основная идея состоит в том, чтобы обернуть C- интерфейс libuv
в изящный C++ API.
Обратите внимание, что uvw
остается верным API libuv
и ничего не добавляет к своему интерфейсу. По тем же причинам пользователи библиотеки должны следовать тем же правилам, которые используются с libuv
.
Например, дескриптор должен быть инициализирован перед любой другой операцией и закрыт, когда он больше не используется.
#include <uvw.hpp>#include <memory>void Listen(uvw::loop &loop) { std::shared_ptr<uvw::tcp_handle> tcp = Loop.resource<uvw::tcp_handle>(); tcp->on<uvw::listen_event>([](const uvw::listen_event &, uvw::tcp_handle &srv) { std::shared_ptr<uvw::tcp_handle> client = srv.parent().resource<uvw::tcp_handle>(); client->on<uvw::close_event>([ptr = srv.shared_from_this()](const uvw::close_event &, uvw::tcp_handle &) { ptr->close(); }); client->on<uvw::end_event>([](const uvw::end_event &, uvw::tcp_handle &client) { client.close(); }); srv.accept(*клиент); клиент->читать(); }); tcp->bind("127.0.0.1", 4242); TCP->прослушивать(); }void conn(uvw::loop &loop) {auto tcp =loop.resource<uvw::tcp_handle>(); tcp->on<uvw::error_event>([](const uvw::error_event &, uvw::tcp_handle &) { /* обработка ошибок */ }); tcp->on<uvw::connect_event>([](const uvw::connect_event &, uvw::tcp_handle &tcp) {auto dataWrite = std::unique_ptr<char[]>(new char[2]{ 'b' , 'с' }); tcp.write(std::move(dataWrite), 2); TCP.закрыть(); }); tcp->connect(std::string{"127.0.0.1"}, 4242); }int main() {auto цикл = uvw::loop::get_default();listen(*loop);conn(*loop); цикл-> выполнить(); }
Основная причина написания uvw
заключается в том, что в C++ не существует допустимой оболочки libuv
. Вот и все.
Чтобы иметь возможность использовать uvw
, пользователи должны предоставить следующие общесистемные инструменты:
Полнофункциональный компилятор, поддерживающий как минимум C++17.
libuv
(какая версия зависит от используемого тега uvw
)
Если вы используете meson
, для вас будет загружена libuv.
Приведенные ниже требования являются обязательными для составления тестов и извлечения документации:
CMake версии 3.13 или более поздней.
Doxygen версии 1.8 или новее.
Обратите внимание, что libuv
является частью зависимостей проекта и в некоторых случаях может быть клонирован CMake
(более подробную информацию см. ниже).
Благодаря этому пользователям не нужно устанавливать его для запуска тестов или при компиляции uvw
-библиотек через CMake
.
Вы можете использовать uvw
с мезоном, просто добавив его в каталог subprojects
вашего проекта.
Чтобы скомпилировать uvw
из исходного кода, не используя его в качестве подпроекта, в каталоге исходного кода uvw
выполните:
$ meson setup build
Если вам нужна статическая библиотека, добавьте --default-library=static
$ cd build
$ meson compile
uvw
— двухрежимная библиотека. Его можно использовать в форме только заголовка или как скомпилированную статическую библиотеку.
В следующих разделах описывается, что делать в обоих случаях, чтобы запустить uvw
в вашем собственном проекте.
Чтобы использовать uvw
в качестве библиотеки только для заголовков, достаточно включить заголовок uvw.hpp
или один из других файлов uvw/*.hpp
.
Это вопрос добавления следующей строки в начало файла:
#include <uvw.hpp>
Затем передайте компилятору правильный аргумент -I
, чтобы добавить каталог src
во включаемые пути.
Обратите внимание, что в этом случае пользователям необходимо правильно настроить пути поиска включаемых каталогов и библиотек для libuv
.
При использовании через CMake
для удобства цель uvw::uvw
экспортируется.
Чтобы использовать uvw
в качестве скомпилированной библиотеки, установите параметры UVW_BUILD_LIBS
в cmake перед включением проекта.
Эта опция запускает генерацию цели с именем uvw::uvw-static
. Соответствующая версия libuv
также для удобства компилируется и экспортируется как uv::uv-static
.
Если вы не используете или не хотите использовать CMake
, вы все равно можете скомпилировать все файлы .cpp
и включить все файлы .h
, чтобы выполнить работу. В этом случае пользователям необходимо правильно настроить пути поиска включаемых каталогов и библиотек для libuv
.
Начиная с тега libuv
v1.12.0 , uvw
следует семантической схеме управления версиями.
Проблема в том, что любая версия uvw
также требует явного отслеживания версии libuv
, к которой она привязана.
По этой причине последний будет добавлен к версии uvw
. В качестве примера:
vU.V.W_libuv-vX.Y
В частности, применяется следующее:
UVW — это основные, второстепенные и исправленные версии uvw
.
XY — это версия libuv
, на которую следует ссылаться (где действительна любая версия исправления).
Другими словами, теперь теги будут выглядеть так:
v1.0.0_libuv-v1.12
master
ветки uvw
будет веткой в стадии разработки, которая следует за веткой v1.x libuv
(по крайней мере, пока она остается их основной веткой).
Документация основана на doxygen
. Чтобы построить его:
$ cd build
$ cmake ..
$ make docs
Ссылка на API будет создана в формате HTML в каталоге build/docs/html
.
Чтобы перемещаться по нему с помощью любимого браузера:
$ cd build
$ your_favorite_browser docs/html/index.html
Та же самая версия также доступна в Интернете для последней версии, которая является последней стабильной версией.
Документация в основном основана на официальной документации API libuv по очевидным причинам.
Для компиляции и запуска тестов uvw
требуются libuv
и googletest
.
CMake
загрузит и скомпилирует обе библиотеки, прежде чем компилировать что-либо еще.
Чтобы построить тесты:
$ cd build
$ cmake .. -DUVW_BUILD_TESTING=ON
$ make
$ ctest -j4 -R uvw
Опустите -R uvw
, если вы также хотите протестировать libuv
и другие зависимости.
При использовании uvw
есть только одно правило: всегда инициализировать ресурсы и завершать их.
Ресурсы принадлежат в основном к двум семействам: дескрипторы и запросы .
Дескрипторы представляют собой долгоживущие объекты, способные выполнять определенные операции, пока они активны.
Запросы представляют собой (обычно) кратковременные операции, выполняемые либо через дескриптор, либо автономно.
В следующих разделах будет кратко объяснено, что означает инициализация и завершение ресурсов такого типа.
Более подробную информацию можно найти в онлайн-документации.
Инициализация обычно выполняется скрыто и может быть даже пропущена, поскольку дескрипторы создаются с использованием функции-члена loop::resource
.
С другой стороны, дескрипторы остаются активными до тех пор, пока их явно не закроют. Из-за этого использование памяти будет расти, если пользователи просто забудут об дескрипторе.
Поэтому быстро становится правилом всегда закрывать ручки . Это так же просто, как вызвать для них функцию-член close
.
Обычно инициализация объекта запроса не требуется. В любом случае рекомендуемым способом создания запроса по-прежнему является использование функции-члена loop::resource
.
Запросы будут оставаться активными до тех пор, пока они связаны с незавершенными базовыми действиями. Это означает, что пользователям не нужно явно отклонять запрос.
Поэтому быстро становится правилом : не стесняйтесь сделать запрос и забыть об этом . Это так же просто, как вызвать для них функцию-член.
Первое, что нужно сделать, чтобы использовать uvw
— это создать цикл. Если достаточно значения по умолчанию, это легко сделать:
автоматический цикл = uvw::loop::get_default();
Обратите внимание, что объекты циклов не требуют явного закрытия, даже если они предлагают функцию-член close
на случай, если пользователь захочет это сделать.
Циклы можно запустить с помощью функции-члена run
. Два приведенных ниже вызова эквивалентны:
цикл-> выполнить(); цикл->run(uvw::loop::run_mode::DEFAULT);
Доступные режимы: DEFAULT
, ONCE
, NOWAIT
. Пожалуйста, обратитесь к документации libuv
для получения более подробной информации.
Чтобы создать ресурс и привязать его к заданному циклу, просто сделайте следующее:
auto tcp = цикл->resource<uvw::tcp_handle>();
Строка выше создает и инициализирует дескриптор TCP, затем возвращается общий указатель на этот ресурс.
Пользователям следует проверить, правильно ли инициализированы указатели: в случае ошибок их не будет.
Также возможно создать неинициализированные ресурсы для последующей инициализации следующим образом:
auto tcp = цикл->uninitialized_resource<uvw::tcp_handle>(); TCP-> инициализация ();
Все ресурсы также принимают произвольные пользовательские данные, которые ни в коем случае не будут затронуты.
Пользователи могут устанавливать и получать их с помощью функции-члена data
следующим образом:
ресурс->данные(std::make_shared<int>(42)); std::shared_ptr<void> данные = ресурс->данные();
Ресурсы ожидают std::shared_pointer<void>
и возвращают его, поэтому приветствуются любые данные.
Пользователи могут явно указать тип, отличный от void
при вызове функции-члена data
:
std::shared_ptr<int> data = resources->data<int>();
Помните из предыдущего раздела, что дескриптор будет оставаться активным до тех пор, пока для него не будет вызвана функция-член close
.
Чтобы узнать, какие дескрипторы все еще активны и привязаны к данному циклу, существует функция-член walk
. Он возвращает дескрипторы с их типами. Поэтому рекомендуется использовать overloaded
, чтобы иметь возможность перехватывать все виды интересов:
handle.parent().walk(uvw::overloaded{ [](uvw::timer_handle &h){ /* здесь код приложения для таймеров */ }, [](auto &&){ /* игнорировать все остальные типы */ } });
Эту функцию также можно использовать для совершенно общего подхода. Например, все ожидающие дескрипторы можно легко закрыть следующим образом:
цикл->walk([](auto &&h){ h.close(); });
Нет необходимости за ними следить.
uvw
предлагает подход, основанный на событиях, где ресурсы представляют собой небольшие генераторы событий, к которым подключены слушатели.
Прикрепление прослушивателей к ресурсам — рекомендуемый способ получения уведомлений об их операциях.
Слушатели — это вызываемые объекты типа void(event_type &, resource_type &)
, где:
event_type
— это тип события, для которого они были разработаны.
resource_type
— это тип ресурса, вызвавшего событие.
Это означает, что все следующие типы функций допустимы:
void(event_type &, resource_type &)
void(const event_type &, resource_type &)
void(event_type &, const resource_type &)
void(const event_type &, const resource_type &)
Обратите внимание, что нет необходимости хранить ссылки на ресурсы, поскольку они передаются в качестве аргумента при каждой публикации события.
Функция-член on
— это способ регистрации долго работающих прослушивателей:
ресурс.он<тип_события>(прослушиватель)
Чтобы узнать, существует ли прослушиватель для данного типа, класс предлагает шаблон функции has
. Аналогично, шаблон функции reset
используется для сброса и, таким образом, отключения прослушивателей, если таковые имеются. Также существует нешаблонная версия reset
для очистки эмиттера в целом.
Почти все ресурсы выдают error_event
в случае ошибок.
Все остальные события специфичны для данного ресурса и описаны в справочнике API.
Код ниже показывает, как создать простой tcp-сервер с помощью uvw
:
автоматический цикл = uvw::loop::get_default();auto tcp = цикл->ресурс<uvw::tcp_handle>(); tcp->on<uvw::error_event>([](const uvw::error_event &, uvw::tcp_handle &) { /* что-то пошло не так */ }); tcp->on<uvw::listen_event>([](const uvw::listen_event &, uvw::tcp_handle &srv) { std::shared_ptr<uvw::tcp_handle> client = srv.parent().resource<uvw::tcp_handle>(); client->on<uvw::end_event>([](const uvw::end_event &, uvw::tcp_handle &client) { client.close(); }); client->on<uvw::data_event>([](const uvw::data_event &, uvw::tcp_handle &) { /* данные получены */ }); srv.accept(*клиент); клиент->читать(); }); tcp->bind("127.0.0.1", 4242); TCP->прослушивать();
Также обратите внимание, что uvw::tcp_handle
уже поддерживает IPv6 «из коробки».
Справочник по API — это рекомендуемая документация для получения более подробной информации о ресурсах и их методах.
В случае, если пользователям необходимо использовать функциональные возможности, еще не обернутые uvw
, или если они хотят получить базовые структуры данных, определенные libuv
по каким-либо другим причинам, почти все классы в uvw
предоставляют к ним прямой доступ.
Обратите внимание, что эти функции не следует использовать напрямую, если пользователи точно не знают, что они делают и каковы риски. Работать в сыром виде опасно, главным образом потому, что управление жизненным циклом, дескриптором или запросом полностью контролируется библиотекой, и работа над этим может быстро привести к поломке.
При этом переход к сырому формату — это вопрос использования raw
функций-членов:
auto цикл = uvw::loop::get_default();auto tcp = цикл->ресурс<uvw::tcp_handle>();uv_loop_t *raw = цикл->raw();uv_tcp_t *handle = tcp->raw() ;
Идите сырым путем на свой страх и риск, но не ждите никакой поддержки в случае ошибок.
Заинтересованы в дополнительных инструментах и библиотеках, основанных на uvw
? Тогда вам может пригодиться следующее:
uvw_net
: сетевая библиотека с набором клиентов (HTTP/Modbus/SunSpec), которая также включает реализации обнаружения, такие как dns-sd/mdns.
Если хотите, добавьте свой инструмент в список.
Если вы хотите внести свой вклад, отправьте исправления в виде запросов на включение к мастеру ветки.
Проверьте список участников, чтобы узнать, кто уже принял участие.
Код и документация Copyright (c) 2016–2024, Мишель Каини.
Авторские права на логотип (c) 2018–2021 Ричард Казерес.
Код и документация выпущены по лицензии MIT.
Логотип выпущен под лицензией CC BY-SA 4.0.
Если вы хотите поддержать этот проект, можете предложить мне эспрессо.
Если вы обнаружите, что этого недостаточно, не стесняйтесь помочь мне так, как вам удобнее.