- Notifications
You must be signed in to change notification settings - Fork220
Header-only, event based, tiny and easy to use libuv wrapper in modern C++ - now available as also shared/static library!
License
skypjack/uvw
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Do you have aquestion that doesn't require you to open an issue? Join thegitter channel.
If you useuvw
and you want to say thanks or support the project, pleaseconsider becoming asponsor.
You can help me make the difference.Many thanks to those who supported meand still support me today.
uvw
started as a header-only, event based, tiny and easy to use wrapper forlibuv
written in modern C++.
Now it's finally available also as a compilable static library.
The basic idea is to wrap theC-ish interface oflibuv
behind a graceful C++API.
Note thatuvw
stays true to the API oflibuv
and it doesn't add anything toits interface. For the same reasons, users of the library must follow the samerules which are used withlibuv
.
As an example, ahandle should be initialized before any other operation andclosed once it is no longer in use.
#include<uvw.hpp>#include<memory>voidlisten(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();}voidconn(uvw::loop &loop) {auto tcp = loop.resource<uvw::tcp_handle>(); tcp->on<uvw::error_event>([](const uvw::error_event &, uvw::tcp_handle &) {/* handle errors*/ }); tcp->on<uvw::connect_event>([](const uvw::connect_event &, uvw::tcp_handle &tcp) {auto dataWrite = std::unique_ptr<char[]>(newchar[2]{'b','c' }); tcp.write(std::move(dataWrite),2); tcp.close(); }); tcp->connect(std::string{"127.0.0.1"},4242);}intmain() {auto loop =uvw::loop::get_default();listen(*loop);conn(*loop); loop->run();}
The main reason for whichuvw
has been written is the fact that there does notexist a validlibuv
wrapper in C++. That's all.
To be able to useuvw
, users must provide the following system-wide tools:
- A full-featured compiler that supports at least C++17.
libuv
(which version depends on the tag ofuvw
in use)- If you use
meson
, libuv will be downloaded for you
- If you use
The requirements below are mandatory to compile the tests and to extract thedocumentation:
- CMake version 3.13 or later.
- Doxygen version 1.8 or later.
Note thatlibuv
is part of the dependencies of the project and may be clonedbyCMake
in some cases (see below for further details).
Because of that, users don't have to install it to run the tests or whenuvw
libraries are compiled throughCMake
.
You can useuvw
withmeson by simply adding it toyoursubprojects
directory in your project.
To compileuvw
from source without using it as a subproject, in theuvw
source directory, run:
$ meson setup build
- If you want a static library, add
--default-library=static
- If you want a static library, add
$ cd build
$ meson compile
uvw
is a dual-mode library. It can be used in its header-only form or as acompiled static library.
The following sections describe what to do in both cases to getuvw
up andrunningin your own project.
To useuvw
as a header-only library, all is needed is to include theuvw.hpp
header or one of the otheruvw/*.hpp
files.
It's a matter of adding the following line at the top of a file:
#include<uvw.hpp>
Then pass the proper-I
argument to the compiler to add thesrc
directory tothe include paths.
Note that users are required to correctly setup the include directories andlibraries search paths forlibuv
in this case.
When used throughCMake
, theuvw::uvw
target is exported for convenience.
To useuvw
as a compiled library, set theUVW_BUILD_LIBS
options in cmakebefore including the project.
This option triggers the generation of a targets nameduvw::uvw-static
. The matching version oflibuv
is alsocompiled and exported asuv::uv-static
for convenience.
In case you don't use or don't want to useCMake
, you can still compile all.cpp
files and include all.h
files to get the job done. In this case, usersare required to correctly setup the include directories and libraries searchpaths forlibuv
.
Starting with tagv1.12.0 oflibuv
,uvw
follows thesemantic versioning scheme.
The problem is that any version ofuvw
also requires to track explicitly theversion oflibuv
to which it is bound.
Because of that, the latter wil be appended to the version ofuvw
. As anexample:
vU.V.W_libuv-vX.Y
In particular, the following applies:
- U.V.W are major, minor and patch versions of
uvw
. - X.Y is the version of
libuv
to which to refer (where any patch version isvalid).
In other terms, tags will look like this from now on:
v1.0.0_libuv-v1.12
Branchmaster
ofuvw
will be a work in progress branch that follows branchv1.x oflibuv
(at least as long as it remains theirmaster branch).
The documentation is based ondoxygen
. To build it:
$ cd build
$ cmake ..
$ make docs
The API reference will be created in HTML format within the directorybuild/docs/html
.
To navigate it with your favorite browser:
$ cd build
$ your_favorite_browser docs/html/index.html
The same version is also availableonlinefor the latest release, that is the last stable tag.
The documentation is mostly inspired by the officiallibuv API documentation for obviousreasons.
To compile and run the tests,uvw
requireslibuv
andgoogletest
.CMake
will download and compile both the libraries before compiling anythingelse.
To build the tests:
$ cd build
$ cmake .. -DUVW_BUILD_TESTING=ON
$ make
$ ctest -j4 -R uvw
Omit-R uvw
if you also want to testlibuv
and other dependencies.
There is only one rule when usinguvw
: always initialize the resources andterminate them.
Resources belong mainly to two families:handles andrequests.
Handles represent long-lived objects capable of performing certain operationswhile active.
Requests represent (typically) short-lived operations performed either over ahandle or standalone.
The following sections will explain in short what it means to initialize andterminate these kinds of resources.
For more details, please refer to theonline documentation.
Initialization is usually performed under the hood and can be even passed over,as far as handles are created using theloop::resource
member function.
On the other side, handles keep themselves alive until one explicitly closesthem. Because of that, memory usage will grow if users simply forget about ahandle.
Therefore the rule quickly becomesalways close your handles. It's as simpleas calling theclose
member function on them.
Usually initializing a request object is not required. Anyway, the recommendedway to create a request is still through theloop::resource
memberfunction.
Requests will keep themselves alive as long as they are bound to unfinishedunderlying activities. This means that users don't have to discard arequest explicitly .
Therefore the rule quickly becomesfeel free to make a request and forget aboutit. It's as simple as calling a member function on them.
The first thing to do to useuvw
is to create a loop. In case the default oneis enough, it's easy as doing this:
auto loop = uvw::loop::get_default();
Note that loop objects don't require being closed explicitly, even if they offertheclose
member function in case a user wants to do that.
Loops can be started using therun
member function. The two calls below areequivalent:
loop->run();loop->run(uvw::loop::run_mode::DEFAULT);
Available modes are:DEFAULT
,ONCE
,NOWAIT
. Please refer to thedocumentation oflibuv
for further details.
In order to create a resource and to bind it to the given loop, just do thefollowing:
auto tcp = loop->resource<uvw::tcp_handle>();
The line above creates and initializes a tcp handle, then a shared pointer tothat resource is returned.
Users should check if pointers have been correctly initialized: in case oferrors, they won't be.
It also is possible to create uninitialized resources to init later on as:
auto tcp = loop->uninitialized_resource<uvw::tcp_handle>();tcp->init();
All resources also accept arbitrary user-data that won't be touched in anycase.
Users can set and get them through thedata
member function as it follows:
resource->data(std::make_shared<int>(42));std::shared_ptr<void> data = resource->data();
Resources expect astd::shared_pointer<void>
and return it, therefore any kindof data is welcome.
Users can explicitly specify a type other thanvoid
when calling thedata
member function:
std::shared_ptr<int> data = resource->data<int>();
Remember from the previous section that a handle will keep itself alive untilone invokes theclose
member function on it.
To know what are the handles that are still alive and bound to a given loop,there exists thewalk
member function. It returns handles with their types.Therefore, the use ofoverloaded
is recommended to be able to intercept alltypes of interest:
handle.parent().walk(uvw::overloaded{ [](uvw::timer_handle &h){/* application code for timers here*/ }, [](auto &&){/* ignore all other types*/ }});
This function can also be used for a completely generic approach. For example,all the pending handles can be closed easily as it follows:
loop->walk([](auto &&h){ h.close(); });
No need to keep track of them.
uvw
offers an event-based approach where resources are small event emitters towhich listeners are attached.
Attaching listeners to resources is the recommended way to receive notificationsabout their operations.
Listeners are callable objects of typevoid(event_type &, resource_type &)
,where:
event_type
is the type of the event for which they have been designed.resource_type
is the type of the resource that has originated the event.
It means that the following function types are all valid:
void(event_type &, resource_type &)
void(const event_type &, resource_type &)
void(event_type &, const resource_type &)
void(const event_type &, const resource_type &)
Please note that there is no need to keep around references to the resources,since they pass themselves as an argument whenever an event is published.
Theon
member function is the way to go to register long-running listeners:
resource.on<event_type>(listener)
To know if a listener exists for a given type, the class offers ahas
functiontemplate. Similarly, thereset
function template is be used to reset and thusdisconnect listeners, if any. A non-template version ofreset
also exists toclear an emitter as a whole.
Almost all the resources emiterror_event
in case of errors.
All the other events are specific for the given resource and documented in theAPI reference.
The code below shows how to create a simple tcp server usinguvw
:
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 &) {/* something went wrong*/ });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 &) {/* data received*/ }); srv.accept(*client); client->read();});tcp->bind("127.0.0.1",4242);tcp->listen();
Note also thatuvw::tcp_handle
already supportsIPv6 out-of-the-box.
The API reference is the recommended documentation for further details aboutresources and their methods.
In case users need to use functionalities not wrapped yet byuvw
or if theywant to get the underlying data structures as defined bylibuv
for some otherreasons, almost all the classes inuvw
give direct access to them.
Please, note that this functions should not be used directly unless users knowexactly what they are doing and what are the risks. Going raw is dangerous,mainly because the lifetime management of a loop, a handle or a request iscompletely controlled by the library and working around it could quickly breakthings.
That being said,going raw is a matter of using theraw
member functions:
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();
Go the raw way at your own risk, but do not expect any support in case of bugs.
Interested in additional tools and libraries that build uponuvw
? You mightfind the following useful then:
uvw_net
: a networking library with acollection of clients (HTTP/Modbus/SunSpec) that also includes discoveryimpementations like dns-sd/mdns.
Feel free to add your tool to the list if you like.
If you want to contribute, please send patches as pull requests against thebranch master.
Check thecontributors list to seewho has partecipated so far.
Code and documentation Copyright (c) 2016-2024 Michele Caini.
Logo Copyright (c) 2018-2021 Richard Caseres.
Code and documentation released underthe MIT license.
Logo released underCC BY-SA 4.0.
If you want to support this project, you canoffer me an espresso.
If you find that it's not enough, feel free tohelp me the way you prefer.
About
Header-only, event based, tiny and easy to use libuv wrapper in modern C++ - now available as also shared/static library!