Avez-vous une question qui ne nécessite pas d'ouvrir un ticket ? Rejoignez la chaîne gitter.
Si vous utilisez uvw
et que vous souhaitez remercier ou soutenir le projet, pensez à devenir sponsor .
Vous pouvez m'aider à faire la différence. Un grand merci à ceux qui m'ont soutenu et qui me soutiennent encore aujourd'hui.
uvw
a commencé comme un wrapper d'en-tête uniquement, basé sur des événements, petit et facile à utiliser pour libuv
écrit en C++ moderne.
Maintenant, il est enfin disponible également sous forme de bibliothèque statique compilable.
L'idée de base est d'envelopper l'interface C-ish de libuv
derrière une API C++ gracieuse.
Notez que uvw
reste fidèle à l'API de libuv
et n'ajoute rien à son interface. Pour les mêmes raisons, les utilisateurs de la bibliothèque doivent suivre les mêmes règles que celles utilisées avec libuv
.
A titre d'exemple, un handle doit être initialisé avant toute autre opération et fermé une fois qu'il n'est plus utilisé.
#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.accepter(*client); client->lire(); }); tcp->bind("127.0.0.1", 4242); tcp->écouter(); }void conn(uvw::loop &loop) {auto tcp = loop.resource<uvw::tcp_handle>(); tcp->on<uvw::error_event>([](const uvw::error_event &, uvw::tcp_handle &) { /* gérer les erreurs */ }); tcp->on<uvw::connect_event>([](const uvw::connect_event &, uvw::tcp_handle &tcp) {auto dataWrite = std::unique_ptr<char[]>(new char[2]{ 'b' , 'c' }); tcp.write(std::move(dataWrite), 2); tcp.close(); }); tcp->connect(std::string{"127.0.0.1"}, 4242); }int main() {auto loop = uvw::loop::get_default();listen(*loop);conn(*loop); boucle->run(); }
La principale raison pour laquelle uvw
a été écrit est le fait qu'il n'existe pas de wrapper libuv
valide en C++. C'est tout.
Pour pouvoir utiliser uvw
, les utilisateurs doivent fournir les outils système suivants :
Un compilateur complet prenant en charge au moins C++17.
libuv
(quelle version dépend de la balise uvw
utilisée)
Si vous utilisez meson
, libuv sera téléchargé pour vous
Les exigences ci-dessous sont obligatoires pour compiler les tests et extraire la documentation :
CMake version 3.13 ou ultérieure.
Doxygen version 1.8 ou ultérieure.
Notez que libuv
fait partie des dépendances du projet et peut être cloné par CMake
dans certains cas (voir ci-dessous pour plus de détails).
Pour cette raison, les utilisateurs n'ont pas besoin de l'installer pour exécuter les tests ou lorsque les bibliothèques uvw
sont compilées via CMake
.
Vous pouvez utiliser uvw
avec meson en l'ajoutant simplement au répertoire de vos subprojects
dans votre projet.
Pour compiler uvw
à partir des sources sans l'utiliser comme sous-projet, dans le répertoire source uvw
, exécutez :
$ meson setup build
Si vous voulez une bibliothèque statique, ajoutez --default-library=static
$ cd build
$ meson compile
uvw
est une bibliothèque bimode. Il peut être utilisé sous sa forme d'en-tête uniquement ou en tant que bibliothèque statique compilée.
Les sections suivantes décrivent ce qu'il faut faire dans les deux cas pour que uvw
soit opérationnel dans votre propre projet.
Pour utiliser uvw
comme bibliothèque d'en-tête uniquement, il suffit d'inclure l'en-tête uvw.hpp
ou l'un des autres fichiers uvw/*.hpp
.
Il s'agit d'ajouter la ligne suivante en haut d'un fichier :
#include <uvw.hpp>
Passez ensuite l'argument -I
approprié au compilateur pour ajouter le répertoire src
aux chemins d'inclusion.
Notez que les utilisateurs doivent configurer correctement les chemins de recherche des répertoires et des bibliothèques d'inclusion pour libuv
dans ce cas.
Lorsqu'elle est utilisée via CMake
, la cible uvw::uvw
est exportée pour plus de commodité.
Pour utiliser uvw
comme bibliothèque compilée, définissez les options UVW_BUILD_LIBS
dans cmake avant d'inclure le projet.
Cette option déclenche la génération d'une cible nommée uvw::uvw-static
. La version correspondante de libuv
est également compilée et exportée sous uv::uv-static
pour plus de commodité.
Si vous n'utilisez pas ou ne souhaitez pas utiliser CMake
, vous pouvez toujours compiler tous les fichiers .cpp
et inclure tous les fichiers .h
pour faire le travail. Dans ce cas, les utilisateurs doivent configurer correctement les chemins de recherche des répertoires d'inclusion et des bibliothèques pour libuv
.
À partir de la balise v1.12.0 de libuv
, uvw
suit le schéma de version sémantique.
Le problème est que toute version de uvw
nécessite également de suivre explicitement la version de libuv
à laquelle elle est liée.
De ce fait, ce dernier sera ajouté à la version de uvw
. A titre d'exemple :
vU.V.W_libuv-vX.Y
En particulier, ce qui suit s'applique :
UVW sont des versions majeures, mineures et correctives de uvw
.
XY est la version de libuv
à laquelle se référer (où toute version de correctif est valide).
En d’autres termes, les balises ressembleront désormais à ceci :
v1.0.0_libuv-v1.12
La branche master
de uvw
sera une branche en cours de réalisation qui suivra la branche v1.x de libuv
(au moins tant qu'elle restera leur branche principale ).
La documentation est basée sur doxygen
. Pour le construire :
$ cd build
$ cmake ..
$ make docs
La référence API sera créée au format HTML dans le répertoire build/docs/html
.
Pour y naviguer avec votre navigateur préféré :
$ cd build
$ your_favorite_browser docs/html/index.html
La même version est également disponible en ligne pour la dernière version, c'est-à-dire la dernière balise stable.
La documentation est principalement inspirée de la documentation officielle de l'API libuv pour des raisons évidentes.
Pour compiler et exécuter les tests, uvw
nécessite libuv
et googletest
.
CMake
téléchargera et compilera les deux bibliothèques avant de compiler autre chose.
Pour construire les tests :
$ cd build
$ cmake .. -DUVW_BUILD_TESTING=ON
$ make
$ ctest -j4 -R uvw
Omettez -R uvw
si vous souhaitez également tester libuv
et d'autres dépendances.
Il n'y a qu'une seule règle lors de l'utilisation uvw
: toujours initialiser les ressources et y mettre fin.
Les ressources appartiennent principalement à deux familles : les handles et les requêtes .
Les poignées représentent des objets de longue durée capables d’effectuer certaines opérations lorsqu’ils sont actifs.
Les requêtes représentent (généralement) des opérations de courte durée effectuées soit via un handle, soit de manière autonome.
Les sections suivantes expliqueront en bref ce que signifie initialiser et mettre fin à ces types de ressources.
Pour plus de détails, veuillez vous référer à la documentation en ligne.
L'initialisation est généralement effectuée sous le capot et peut même être ignorée, dans la mesure où les handles sont créés à l'aide de la fonction membre loop::resource
.
De l’autre côté, les poignées se maintiennent en vie jusqu’à ce qu’on les ferme explicitement. Pour cette raison, l’utilisation de la mémoire augmentera si les utilisateurs oublient simplement un handle.
La règle devient donc vite de toujours fermer les poignées . C'est aussi simple que d'appeler la fonction membre close
sur eux.
En général, l'initialisation d'un objet de requête n'est pas requise. Quoi qu’il en soit, la méthode recommandée pour créer une requête consiste toujours à utiliser la fonction membre loop::resource
.
Les requêtes resteront actives tant qu'elles seront liées à des activités sous-jacentes inachevées. Cela signifie que les utilisateurs n'ont pas à rejeter explicitement une demande.
La règle devient donc vite libre de faire une demande et de l'oublier . C'est aussi simple que d'appeler une fonction membre sur eux.
La première chose à faire pour utiliser uvw
est de créer une boucle. Si celui par défaut est suffisant, c'est aussi simple que de procéder comme suit :
boucle automatique = uvw::loop::get_default();
Notez que les objets de boucle ne nécessitent pas d'être fermés explicitement, même s'ils offrent la fonction membre close
au cas où un utilisateur souhaite le faire.
Les boucles peuvent être démarrées à l’aide de la fonction membre run
. Les deux appels ci-dessous sont équivalents :
boucle->run(); boucle->run(uvw::loop::run_mode::DEFAULT);
Les modes disponibles sont : DEFAULT
, ONCE
, NOWAIT
. Veuillez vous référer à la documentation de libuv
pour plus de détails.
Afin de créer une ressource et de la lier à la boucle donnée, procédez simplement comme suit :
auto tcp = loop->resource<uvw::tcp_handle>();
La ligne ci-dessus crée et initialise un handle TCP, puis un pointeur partagé vers cette ressource est renvoyé.
Les utilisateurs doivent vérifier si les pointeurs ont été correctement initialisés : en cas d'erreurs, ils ne le seront pas.
Il est également possible de créer des ressources non initialisées pour une initialisation ultérieure sous la forme :
auto tcp = loop->uninitialized_resource<uvw::tcp_handle>(); tcp->init();
Toutes les ressources acceptent également des données utilisateur arbitraires qui ne seront en aucun cas touchées.
Les utilisateurs peuvent les définir et les obtenir via la fonction membre data
comme suit :
ressource->données(std::make_shared<int>(42)); std::shared_ptr<void> data = ressource->data();
Les ressources attendent un std::shared_pointer<void>
et le renvoient, donc tout type de données est le bienvenu.
Les utilisateurs peuvent spécifier explicitement un type autre que void
lors de l'appel de la fonction membre data
:
std::shared_ptr<int> data = ressource->data<int>();
Rappelez-vous de la section précédente qu'un handle se maintiendra en vie jusqu'à ce que l'on invoque la fonction membre close
dessus.
Pour savoir quels sont les handles encore vivants et liés à une boucle donnée, il existe la fonction membre walk
. Il renvoie les handles avec leurs types. Par conséquent, l’utilisation de overloaded
est recommandée pour pouvoir intercepter tous les types d’intérêt :
handle.parent().walk(uvw::overloaded{ [](uvw::timer_handle &h){ /* code d'application pour les minuteries ici */ }, [](auto &&){ /* ignorer tous les autres types */ } });
Cette fonction peut également être utilisée pour une approche complètement générique. Par exemple, toutes les poignées en attente peuvent être fermées facilement comme suit :
boucle->walk([](auto &&h){ h.close(); });
Pas besoin de les suivre.
uvw
propose une approche basée sur les événements dans laquelle les ressources sont de petits émetteurs d'événements auxquels les auditeurs sont attachés.
Attacher des écouteurs aux ressources est la méthode recommandée pour recevoir des notifications sur leurs opérations.
Les écouteurs sont des objets appelables de type void(event_type &, resource_type &)
, où :
event_type
est le type d'événement pour lequel ils ont été conçus.
resource_type
est le type de ressource à l'origine de l'événement.
Cela signifie que les types de fonctions suivants sont tous valides :
void(event_type &, resource_type &)
void(const event_type &, resource_type &)
void(event_type &, const resource_type &)
void(const event_type &, const resource_type &)
Attention, il n'est pas nécessaire de conserver des références aux ressources, puisqu'elles se font passer en argument à chaque fois qu'un événement est publié.
La fonction membre on
est la voie à suivre pour enregistrer les auditeurs de longue durée :
ressource.on<event_type>(auditeur)
Pour savoir si un écouteur existe pour un type donné, la classe propose un modèle de fonction has
. De même, le modèle de fonction reset
est utilisé pour réinitialiser et donc déconnecter les écouteurs, le cas échéant. Une version sans modèle de reset
existe également pour effacer un émetteur dans son ensemble.
Presque toutes les ressources émettent error_event
en cas d'erreurs.
Tous les autres événements sont spécifiques à la ressource donnée et documentés dans la référence API.
Le code ci-dessous montre comment créer un serveur TCP simple en utilisant uvw
:
auto loop = uvw::loop::get_default();auto tcp = loop->resource<uvw::tcp_handle>(); tcp->on<uvw::error_event>([](const uvw::error_event &, uvw::tcp_handle &) { /* quelque chose s'est mal passé */ }); 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 &) { /* données reçues */ }); srv.accepter(*client); client->lire(); }); tcp->bind("127.0.0.1", 4242); tcp->écouter();
Notez également que uvw::tcp_handle
prend déjà en charge IPv6 prêt à l'emploi.
La référence API est la documentation recommandée pour plus de détails sur les ressources et leurs méthodes.
Dans le cas où les utilisateurs ont besoin d'utiliser des fonctionnalités non encore enveloppées par uvw
ou s'ils souhaitent obtenir les structures de données sous-jacentes telles que définies par libuv
pour d'autres raisons, presque toutes les classes d' uvw
y donnent un accès direct.
Veuillez noter que ces fonctions ne doivent pas être utilisées directement à moins que les utilisateurs sachent exactement ce qu'ils font et quels sont les risques. Passer au brut est dangereux, principalement parce que la gestion de la durée de vie d'une boucle, d'un handle ou d'une requête est entièrement contrôlée par la bibliothèque et contourner cela pourrait rapidement casser les choses.
Cela étant dit, passer au brut consiste à utiliser les fonctions membres raw
:
auto loop = uvw::loop::get_default();auto tcp = loop->resource<uvw::tcp_handle>();uv_loop_t *raw = loop->raw();uv_tcp_t *handle = tcp->raw() ;
Suivez la voie brute à vos propres risques, mais n'attendez aucune assistance en cas de bugs.
Intéressé par des outils et des bibliothèques supplémentaires basés sur uvw
? Les éléments suivants pourraient alors vous être utiles :
uvw_net
: une bibliothèque réseau avec une collection de clients (HTTP/Modbus/SunSpec) qui inclut également des implémentations de découverte comme dns-sd/mdns.
N'hésitez pas à ajouter votre outil à la liste si vous le souhaitez.
Si vous souhaitez contribuer, veuillez envoyer des correctifs sous forme de demandes d'extraction au maître de branche.
Consultez la liste des contributeurs pour voir qui a participé jusqu'à présent.
Code et documentation Copyright (c) 2016-2024 Michele Caini.
Logo Copyright (c) 2018-2021 Richard Caseres.
Code et documentation publiés sous licence MIT.
Logo publié sous CC BY-SA 4.0.
Si vous souhaitez soutenir ce projet, vous pouvez m'offrir un expresso.
Si vous trouvez que cela ne suffit pas, n'hésitez pas à m'aider comme vous le souhaitez.