Haben Sie eine Frage , für die Sie kein Problem eröffnen müssen? Treten Sie dem Gitter-Kanal bei.
Wenn Sie uvw
nutzen und sich bedanken oder das Projekt unterstützen möchten, denken Sie bitte darüber nach, Sponsor zu werden .
Sie können mir helfen, den Unterschied zu machen. Vielen Dank an alle, die mich unterstützt haben und mich auch heute noch unterstützen.
uvw
begann als reiner Header-basierter, ereignisbasierter, kleiner und benutzerfreundlicher Wrapper für libuv
der in modernem C++ geschrieben wurde.
Jetzt ist sie endlich auch als kompilierbare statische Bibliothek verfügbar.
Die Grundidee besteht darin, die C-artige Schnittstelle von libuv
hinter eine elegante C++-API zu packen.
Beachten Sie, dass uvw
der API von libuv
treu bleibt und seiner Schnittstelle nichts hinzufügt. Aus den gleichen Gründen müssen Benutzer der Bibliothek dieselben Regeln befolgen, die für libuv
verwendet werden.
Beispielsweise sollte ein Handle vor jeder anderen Operation initialisiert und geschlossen werden, sobald es nicht mehr verwendet wird.
#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(*client); client->read(); }); tcp->bind("127.0.0.1", 4242); tcp->listen(); }void conn(uvw::loop &loop) {auto tcp = loop.resource<uvw::tcp_handle>(); tcp->on<uvw::error_event>([](const uvw::error_event &, uvw::tcp_handle &) { /* Fehler behandeln */ }); 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); Schleife->run(); }
Der Hauptgrund, warum uvw
geschrieben wurde, ist die Tatsache, dass es in C++ keinen gültigen libuv
Wrapper gibt. Das ist alles.
Um uvw
nutzen zu können, müssen Benutzer die folgenden systemweiten Tools bereitstellen:
Ein Compiler mit vollem Funktionsumfang, der mindestens C++17 unterstützt.
libuv
(welche Version hängt vom verwendeten Tag von uvw
ab)
Wenn Sie meson
verwenden, wird libuv für Sie heruntergeladen
Die folgenden Anforderungen sind zwingend erforderlich, um die Tests zusammenzustellen und die Dokumentation zu extrahieren:
CMake-Version 3.13 oder höher.
Doxygen-Version 1.8 oder höher.
Beachten Sie, dass libuv
Teil der Abhängigkeiten des Projekts ist und in einigen Fällen von CMake
geklont werden kann (weitere Einzelheiten siehe unten).
Aus diesem Grund müssen Benutzer es nicht installieren, um die Tests auszuführen oder wenn uvw
-Bibliotheken über CMake
kompiliert werden.
Sie können uvw
mit meson verwenden, indem Sie es einfach zu Ihrem subprojects
in Ihrem Projekt hinzufügen.
Um uvw
aus dem Quellcode zu kompilieren, ohne es als Unterprojekt zu verwenden, führen Sie im uvw
-Quellverzeichnis Folgendes aus:
$ meson setup build
Wenn Sie eine statische Bibliothek wünschen, fügen Sie --default-library=static
hinzu
$ cd build
$ meson compile
uvw
ist eine Dual-Mode-Bibliothek. Es kann in seiner reinen Header-Form oder als kompilierte statische Bibliothek verwendet werden.
In den folgenden Abschnitten wird beschrieben, was in beiden Fällen zu tun ist, um uvw
in Ihrem eigenen Projekt zum Laufen zu bringen.
Um uvw
als reine Header-Bibliothek zu verwenden, müssen Sie lediglich den Header uvw.hpp
oder eine der anderen uvw/*.hpp
Dateien einbinden.
Es geht darum, die folgende Zeile am Anfang einer Datei hinzuzufügen:
#include <uvw.hpp>
Übergeben Sie dann das richtige Argument -I
an den Compiler, um das Verzeichnis src
zu den Include-Pfaden hinzuzufügen.
Beachten Sie, dass Benutzer in diesem Fall die Include-Verzeichnisse und Bibliothekssuchpfade für libuv
korrekt einrichten müssen.
Bei Verwendung über CMake
wird das Ziel uvw::uvw
der Einfachheit halber exportiert.
Um uvw
als kompilierte Bibliothek zu verwenden, legen Sie die UVW_BUILD_LIBS
-Optionen in cmake fest, bevor Sie das Projekt einbinden.
Diese Option löst die Generierung eines Ziels mit dem Namen uvw::uvw-static
aus. Die passende Version von libuv
wird der Einfachheit halber auch als uv::uv-static
kompiliert und exportiert.
Falls Sie CMake
nicht verwenden oder nicht verwenden möchten, können Sie trotzdem alle .cpp
-Dateien kompilieren und alle .h
Dateien einbinden, um die Aufgabe zu erledigen. In diesem Fall müssen Benutzer die Suchpfade für Include-Verzeichnisse und Bibliotheken für libuv
korrekt einrichten.
Ab Tag v1.12.0 von libuv
folgt uvw
dem semantischen Versionierungsschema.
Das Problem besteht darin, dass jede Version von uvw
auch explizit die Version von libuv
verfolgen muss, an die sie gebunden ist.
Aus diesem Grund wird letzteres an die Version von uvw
angehängt. Als Beispiel:
vU.V.W_libuv-vX.Y
Insbesondere gilt:
UVW sind Haupt-, Neben- und Patch-Versionen von uvw
.
XY ist die Version von libuv
auf die verwiesen werden soll (wobei jede Patch-Version gültig ist).
Mit anderen Worten, Tags sehen von nun an so aus:
v1.0.0_libuv-v1.12
Der Branch- master
von uvw
wird ein in Arbeit befindlicher Branch sein, der dem Branch v1.x von libuv
folgt (zumindest solange er ihr Master -Branch bleibt).
Die Dokumentation basiert auf doxygen
. Um es zu bauen:
$ cd build
$ cmake ..
$ make docs
Die API-Referenz wird im HTML-Format im Verzeichnis build/docs/html
erstellt.
So navigieren Sie mit Ihrem Lieblingsbrowser:
$ cd build
$ your_favorite_browser docs/html/index.html
Die gleiche Version ist auch online für die neueste Version verfügbar, also den letzten Stable-Tag.
Die Dokumentation ist aus offensichtlichen Gründen größtenteils von der offiziellen libuv-API-Dokumentation inspiriert.
Um die Tests zu kompilieren und auszuführen, benötigt uvw
libuv
und googletest
.
CMake
lädt beide Bibliotheken herunter und kompiliert sie, bevor etwas anderes kompiliert wird.
So erstellen Sie die Tests:
$ cd build
$ cmake .. -DUVW_BUILD_TESTING=ON
$ make
$ ctest -j4 -R uvw
Lassen Sie -R uvw
weg, wenn Sie auch libuv
und andere Abhängigkeiten testen möchten.
Bei der Verwendung uvw
gibt es nur eine Regel: Initialisieren Sie die Ressourcen immer und beenden Sie sie.
Ressourcen gehören hauptsächlich zu zwei Familien: Handles und Requests .
Handles stellen langlebige Objekte dar, die im aktiven Zustand bestimmte Vorgänge ausführen können.
Anforderungen stellen (normalerweise) kurzlebige Vorgänge dar, die entweder über ein Handle oder eigenständig ausgeführt werden.
In den folgenden Abschnitten wird kurz erklärt, was es bedeutet, diese Art von Ressourcen zu initialisieren und zu beenden.
Weitere Einzelheiten finden Sie in der Online-Dokumentation.
Die Initialisierung wird normalerweise im Verborgenen durchgeführt und kann sogar übergangen werden, sofern Handles mithilfe der Memberfunktion loop::resource
erstellt werden.
Auf der anderen Seite bleiben Handles so lange am Leben, bis man sie explizit schließt. Aus diesem Grund steigt die Speichernutzung, wenn Benutzer ein Handle einfach vergessen.
Daher gilt schnell die Regel , die Griffe immer zu schließen . Es ist so einfach wie das Aufrufen der close
-Member-Funktion für sie.
Normalerweise ist die Initialisierung eines Anforderungsobjekts nicht erforderlich. Wie auch immer, die empfohlene Methode zum Erstellen einer Anfrage ist immer noch die Verwendung der Mitgliedsfunktion loop::resource
.
Anfragen bleiben bestehen, solange sie an unerledigte zugrunde liegende Aktivitäten gebunden sind. Dies bedeutet, dass Benutzer eine Anfrage nicht explizit verwerfen müssen.
Daher gilt schnell die Regel , dass man einfach eine Anfrage stellen und es dann vergessen kann . Es ist so einfach wie das Aufrufen einer Mitgliedsfunktion für sie.
Um uvw
zu verwenden, müssen Sie zunächst eine Schleife erstellen. Falls die Standardeinstellung ausreicht, ist das ganz einfach:
auto loop = uvw::loop::get_default();
Beachten Sie, dass Schleifenobjekte nicht explizit geschlossen werden müssen, auch wenn sie die close
Member-Funktion anbieten, falls ein Benutzer dies tun möchte.
Schleifen können mit der run
Member-Funktion gestartet werden. Die beiden folgenden Aufrufe sind äquivalent:
Schleife->run(); loop->run(uvw::loop::run_mode::DEFAULT);
Verfügbare Modi sind: DEFAULT
, ONCE
, NOWAIT
. Weitere Einzelheiten finden Sie in der Dokumentation von libuv
.
Um eine Ressource zu erstellen und an die angegebene Schleife zu binden, gehen Sie einfach wie folgt vor:
auto tcp = loop->resource<uvw::tcp_handle>();
Die obige Zeile erstellt und initialisiert ein TCP-Handle, dann wird ein gemeinsamer Zeiger auf diese Ressource zurückgegeben.
Benutzer sollten überprüfen, ob Zeiger korrekt initialisiert wurden. Im Fehlerfall ist dies nicht der Fall.
Es ist auch möglich, nicht initialisierte Ressourcen zu erstellen, um sie später zu initiieren:
auto tcp = loop->uninitialized_resource<uvw::tcp_handle>(); tcp->init();
Alle Ressourcen akzeptieren auch beliebige Benutzerdaten, die in keinem Fall berührt werden.
Benutzer können sie wie folgt über die data
festlegen und abrufen:
resources->data(std::make_shared<int>(42)); std::shared_ptr<void> data = resources->data();
Ressourcen erwarten einen std::shared_pointer<void>
und geben ihn zurück, daher ist jede Art von Daten willkommen.
Benutzer können beim Aufruf der data
explizit einen anderen Typ als void
angeben:
std::shared_ptr<int> data = resources->data<int>();
Erinnern Sie sich aus dem vorherigen Abschnitt daran, dass ein Handle so lange am Leben bleibt, bis man die close
-Member-Funktion darauf aufruft.
Um herauszufinden, welche Handles noch aktiv und an eine bestimmte Schleife gebunden sind, gibt es die Memberfunktion walk
. Es gibt Handles mit ihren Typen zurück. Daher wird die Verwendung von overloaded
empfohlen, um alle Arten von Interessen abfangen zu können:
handle.parent().walk(uvw::overloaded{ [](uvw::timer_handle &h){ /* Anwendungscode für Timer hier */ }, [](auto &&){ /* alle anderen Typen ignorieren */ } });
Diese Funktion kann auch für einen völlig generischen Ansatz verwendet werden. Alle ausstehenden Handles können beispielsweise wie folgt einfach geschlossen werden:
loop->walk([](auto &&h){ h.close(); });
Es besteht keine Notwendigkeit, sie im Auge zu behalten.
uvw
bietet einen ereignisbasierten Ansatz, bei dem es sich bei den Ressourcen um kleine Ereignisemitter handelt, denen Listener zugeordnet sind.
Das Anhängen von Listenern an Ressourcen ist die empfohlene Methode, um Benachrichtigungen über deren Vorgänge zu erhalten.
Listener sind aufrufbare Objekte vom Typ void(event_type &, resource_type &)
, wobei:
event_type
ist der Typ des Ereignisses, für das sie entworfen wurden.
resource_type
ist der Typ der Ressource, die das Ereignis ausgelöst hat.
Das bedeutet, dass die folgenden Funktionstypen alle gültig sind:
void(event_type &, resource_type &)
void(const event_type &, resource_type &)
void(event_type &, const resource_type &)
void(const event_type &, const resource_type &)
Bitte beachten Sie, dass es nicht notwendig ist, Verweise auf die Ressourcen beizubehalten, da diese sich bei jeder Veröffentlichung eines Ereignisses als Argument übergeben.
Die on
Member-Funktion ist der richtige Weg, um Langzeit-Listener zu registrieren:
resources.on<event_type>(listener)
Um zu wissen, ob ein Listener für einen bestimmten Typ vorhanden ist, bietet die Klasse eine has
-Funktionsvorlage an. In ähnlicher Weise wird die reset
-Funktionsvorlage verwendet, um etwaige Listener zurückzusetzen und somit zu trennen. Es gibt auch eine Nicht-Vorlagenversion von reset
mit der ein Emitter als Ganzes gelöscht werden kann.
Fast alle Ressourcen geben im Fehlerfall error_event
aus.
Alle anderen Ereignisse sind spezifisch für die jeweilige Ressource und in der API-Referenz dokumentiert.
Der folgende Code zeigt, wie man mit uvw
einen einfachen TCP-Server erstellt:
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 &) { /* etwas ist schiefgegangen */ }); 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 &) { /* Daten empfangen */ }); srv.accept(*client); client->read(); }); tcp->bind("127.0.0.1", 4242); tcp->listen();
Beachten Sie auch, dass uvw::tcp_handle
IPv6 bereits standardmäßig unterstützt.
Die API-Referenz ist die empfohlene Dokumentation für weitere Details zu Ressourcen und ihren Methoden.
Für den Fall, dass Benutzer Funktionalitäten nutzen müssen, die noch nicht von uvw
umschlossen sind, oder wenn sie aus anderen Gründen die zugrunde liegenden Datenstrukturen erhalten möchten, wie sie von libuv
definiert sind, bieten fast alle Klassen in uvw
direkten Zugriff darauf.
Bitte beachten Sie, dass diese Funktionen nicht direkt verwendet werden sollten, es sei denn, Benutzer wissen genau, was sie tun und welche Risiken bestehen. Rohes Arbeiten ist gefährlich, vor allem weil die lebenslange Verwaltung einer Schleife, eines Handles oder einer Anfrage vollständig von der Bibliothek kontrolliert wird und eine Umgehung schnell zu Problemen führen kann.
Davon abgesehen ist es bei der Rohverarbeitung eine Frage der Verwendung der 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() ;
Gehen Sie den einfachen Weg auf eigene Gefahr, erwarten Sie jedoch keine Unterstützung im Falle von Fehlern.
Interessiert an weiteren Tools und Bibliotheken, die auf uvw
aufbauen? Dann könnten Sie Folgendes nützlich finden:
uvw_net
: eine Netzwerkbibliothek mit einer Sammlung von Clients (HTTP/Modbus/SunSpec), die auch Erkennungsimplementierungen wie dns-sd/mdns umfasst.
Wenn Sie möchten, können Sie Ihr Werkzeug gerne zur Liste hinzufügen.
Wenn Sie einen Beitrag leisten möchten, senden Sie bitte Patches als Pull-Requests an den Filialmaster.
Schauen Sie in der Mitwirkendenliste nach, wer bisher teilgenommen hat.
Code und Dokumentation Copyright (c) 2016-2024 Michele Caini.
Logo Copyright (c) 2018-2021 Richard Caseres.
Code und Dokumentation unter der MIT-Lizenz veröffentlicht.
Logo veröffentlicht unter CC BY-SA 4.0.
Wenn Sie dieses Projekt unterstützen möchten, können Sie mir einen Espresso anbieten.
Wenn Sie feststellen, dass dies nicht ausreicht, können Sie mir gerne auf die von Ihnen bevorzugte Weise helfen.