Do you have a question that doesn't require you to open an issue? Join the
gitter channel.
If you use uvw
and you want to say thanks or support the project, please
consider becoming a
sponsor.
You can help me make the difference.
Many thanks to those who supported me
and still support me today.
uvw
started as a header-only, event based, tiny and easy to use wrapper for
libuv
written in modern C++.
Now it's finally available also as a compilable library, either static or
shared.
The basic idea is to hide completely the C-ish interface of libuv
behind a
graceful C++ API. Currently, no uv_*_t
data structure is actually exposed by
the library.
Note that uvw
stays true to the API of libuv
and it doesn't add anything to
its interface. For the same reasons, users of the library must follow the same
rules which are used with libuv
.
As an example, a handle should be initialized before any other operation and
closed once it is no longer in use.
#include <uvw.hpp>
#include <memory>
void listen(uvw::Loop &loop) {
std::shared_ptr<uvw::TCPHandle> tcp = loop.resource<uvw::TCPHandle>();
tcp->once<uvw::ListenEvent>([](const uvw::ListenEvent &, uvw::TCPHandle &srv) {
std::shared_ptr<uvw::TCPHandle> client = srv.loop().resource<uvw::TCPHandle>();
client->on<uvw::CloseEvent>([ptr = srv.shared_from_this()](const uvw::CloseEvent &, uvw::TCPHandle &) { ptr->close(); });
client->on<uvw::EndEvent>([](const uvw::EndEvent &, uvw::TCPHandle &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::TCPHandle>();
tcp->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::TCPHandle &) { /* handle errors */ });
tcp->once<uvw::ConnectEvent>([](const uvw::ConnectEvent &, uvw::TCPHandle &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::getDefault();
listen(*loop);
conn(*loop);
loop->run();
}
The main reason for which uvw
has been written is the fact that there does not
exist a valid libuv
wrapper in C++. That's all.
To be able to use uvw
, 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).
The requirements below are mandatory to compile the tests and to extract the documentation:
- CMake version 3.15 or later.
- Doxygen version 1.8 or later.
Note that libuv
is part of the dependencies of the project and may be cloned
by CMake
in some cases (see below for further details).
Because of that, users don't have to install it to run the tests or when uvw
libraries are compiled through CMake
.
uvw
is a dual-mode library. It can be used in its header-only form or as a
compiled library, either shared or static.
The following sections describe what to do in both cases to get uvw
up and
runningin your own project.
To use uvw
as a header-only library, all is needed is to include the uvw.hpp
header or one of the other uvw/*.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 the src
directory to
the include paths.
Note that users are required to correctly setup the include directories and
libraries search paths for libuv
in this case.
When used through CMake
, the uvw::uvw
target is exported for convenience.
To use uvw
as a compiled library, set the BUILD_UVW_LIBS
options in cmake
before including the project.
This option triggers the generation of two targets named respectively
uvw::uvw-static
and uvw::uvw-shared
. The matching version of libuv
is also
compiled and exported as uv::uv-static
and uv::uv-shared
for convenience.
In case you don't use or don't want to use CMake
, you can still compile all
.cpp
files and include all .h
files to get the job done. In this case, users
are required to correctly setup the include directories and libraries search
paths for libuv
.
Starting with tag v1.12.0 of libuv
, uvw
follows the
semantic versioning scheme.
The problem is that any version of uvw
also requires to track explicitly the
version of libuv
to which it is bound.
Because of that, the latter wil be appended to the version of uvw
. As an
example:
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 is valid).
In other terms, tags will look like this from now on:
v1.0.0_libuv-v1.12
Branch master
of uvw
will be a work in progress branch that follows branch
v1.x of libuv
(at least as long as it remains their master branch).
The documentation is based on
doxygen
. To build it:
$ cd build
$ cmake ..
$ make docs
The API reference will be created in HTML format within the directory
build/docs/html
.
To navigate it with your favorite browser:
$ cd build
$ your_favorite_browser docs/html/index.html
The API reference is also available online for the latest version.
The documentation is mostly inspired by the official libuv API documentation for obvious reasons.
To compile and run the tests, uvw
requires libuv
and googletest
.
CMake
will download and compile both the libraries before compiling anything
else.
To build the tests:
$ cd build
$ cmake .. -DBUILD_TESTING=ON
$ make
$ ctest -j4 -R uvw
Omit -R uvw
if you also want to test libuv
and other dependencies.
There is only one rule when using uvw
: always initialize the resources and
terminate them.
Resources belong mainly to two families: handles and requests.
Handles represent long-lived objects capable of performing certain operations
while active.
Requests represent (typically) short-lived operations performed either over a
handle or standalone.
The following sections will explain in short what it means to initialize and
terminate these kinds of resources.
For more details, please refer to the
online documentation.
Initialization is usually performed under the hood and can be even passed over,
as far as handles are created using the Loop::resource
member function.
On the other side, handles keep themselves alive until one explicitly closes
them. Because of that, memory usage will grow if users simply forget about a
handle.
Therefore the rule quickly becomes always close your handles. It's as simple
as calling the close
member function on them.
Usually initializing a request object is not required. Anyway, the recommended
way to create a request is still through the Loop::resource
member
function.
Requests will keep themselves alive as long as they are bound to unfinished
underlying activities. This means that users don't have to discard a
request explicitly .
Therefore the rule quickly becomes feel free to make a request and forget about
it. It's as simple as calling a member function on them.
The first thing to do to use uvw
is to create a loop. In case the default one
is enough, it's easy as doing this:
auto loop = uvw::Loop::getDefault();
Note that loop objects don't require being closed explicitly, even if they offer
the close
member function in case a user wants to do that.
Loops can be started using the run
member function. The two calls below are
equivalent:
loop->run();
loop->run<uvw::Loop::Mode::DEFAULT>();
Available modes are: DEFAULT
, ONCE
, NOWAIT
. Please refer to the
documentation of libuv
for further details.
In order to create a resource and to bind it to the given loop, just do the following:
auto tcp = loop->resource<uvw::TCPHandle>();
The line above will create and initialize a tcp handle, then a shared pointer to
that resource will be returned.
Users should check if pointers have been correctly initialized: in case of
errors, they won't be.
Another way to create a resource is:
auto tcp = TCPHandle::create(loop);
tcp->init();
Pretty annoying indeed. Using a loop is the recommended approach.
The resources also accept arbitrary user-data that won't be touched in any
case.
Users can set and get them through the data
member function as it follows:
resource->data(std::make_shared<int>(42));
std::shared_ptr<void> data = resource->data();
Resources expect a std::shared_pointer<void>
and return it, therefore any kind
of data is welcome.
Users can explicitly specify a type other than void
when calling the data
member function:
std::shared_ptr<int> data = resource->data<int>();
Remember from the previous section that a handle will keep itself alive until
one invokes the close
member function on it.
To know what are the handles that are still alive and bound to a given loop,
just do the following:
loop->walk([](uvw::BaseHandle &){ /* application code here */ });
BaseHandle
exposes a few methods and cannot be promoted to the original type
of the handle (even though type
and category
member functions fill the gap
somehow).
Anyway, it can be used to close the handle that originated from it. As an
example, all the pending handles can be closed easily as it follows:
loop->walk([](uvw::BaseHandle &h){ h.close(); });
No need to keep track of them.
To know what are the available resources' types, please refer to the API reference.
uvw
offers an event-based approach, so resources are small event emitters
to which listeners can be attached.
Attaching a listener to a resource is the recommended way to be notified about
changes.
Listeners must be callable objects of type void(EventType &, ResourceType &)
,
where:
EventType
is the type of the event for which they have been designed.ResourceType
is the type of the resource that has originated the event.
It means that the following function types are all valid:
void(EventType &, ResourceType &)
void(const EventType &, ResourceType &)
void(EventType &, const ResourceType &)
void(const EventType &, const ResourceType &)
Once more, please note that there is no need to keep around references to the resources: they will pass themselves as an argument whenever an event is published.
There exist two methods to attach a listener to a resource:
resource.once<EventType>(listener)
: the listener will be automatically removed after the first event of the given type.resource.on<EventType>(listener)
: to be used for long-running listeners.
Both of them return an object of type ResourceType::Connection
(as an example,
TCPHandle::Connection
).
A connection object can be used later as an argument to the erase
member
function of the resource to remove the listener.
There exists also the clear
member function to drop all the listeners at once.
Almost all the resources emit ErrorEvent
in case of errors.
All the other events are specific for the given resource and documented in the
API reference.
The code below shows how to create a simple tcp server using uvw
:
auto loop = uvw::Loop::getDefault();
auto tcp = loop->resource<uvw::TCPHandle>();
tcp->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::TCPHandle &) { /* something went wrong */ });
tcp->on<uvw::ListenEvent>([](const uvw::ListenEvent &, uvw::TCPHandle &srv) {
std::shared_ptr<uvw::TCPHandle> client = srv.loop().resource<uvw::TCPHandle>();
client->once<uvw::EndEvent>([](const uvw::EndEvent &, uvw::TCPHandle &client) { client.close(); });
client->on<uvw::DataEvent>([](const uvw::DataEvent &, uvw::TCPHandle &) { /* data received */ });
srv.accept(*client);
client->read();
});
tcp->bind("127.0.0.1", 4242);
tcp->listen();
Note also that uvw::TCPHandle
already supports IPv6 out-of-the-box. The
statement above is equivalent to tcp->bind<uvw::IPv4>("127.0.0.1", 4242)
.
It's sufficient to explicitly specify uvw::IPv6
as the underlying protocol to
use it.
The API reference is the recommended documentation for further details about resources and their methods.
In case users need to use functionalities not wrapped yet by uvw
or if they
want to get the underlying data structures as defined by libuv
for some other
reasons, almost all the classes in uvw
give direct access to them.
Please, note that this functions should not be used directly unless users know
exactly 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 is
completely controlled by the library and working around it could quickly break
things.
That being said, going raw is a matter of using the raw
member functions:
auto loop = uvw::Loop::getDefault();
auto tcp = loop->resource<uvw::TCPHandle>();
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.
If you want to contribute, please send patches as pull requests against the
branch master.
Check the
contributors list to see
who has partecipated so far.
Code and documentation Copyright (c) 2016-2020 Michele Caini.
Logo Copyright (c) 2018-2020 Richard Caseres.
Code released under
the MIT license.
Documentation released under
CC BY 4.0.
Logo released under
CC BY-SA 4.0.
If you want to support this project, you can
offer me an espresso.
If you find that it's not enough, feel free to
help me the way you prefer.