您是否有不需要打开问题的问题?加入 gitter 频道。
如果您使用uvw
并且想要表示感谢或支持该项目,请考虑成为赞助商。
你可以帮助我改变现状。非常感谢那些支持我并且今天仍然支持我的人。
uvw
最初是一个仅包含标头的、基于事件的、小型且易于使用的libuv
包装器,用现代 C++ 编写。
现在它终于可以作为可编译的静态库使用了。
基本思想是将libuv
的C 风格接口包装在优雅的 C++ API 后面。
请注意, uvw
忠实于libuv
的 API,并且没有向其接口添加任何内容。出于同样的原因,该库的用户必须遵循与libuv
相同的规则。
例如,句柄应在任何其他操作之前初始化,并在不再使用时关闭。
#include <uvw.hpp>#include <内存>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>(); 客户端->on<uvw::close_event>([ptr = srv.shared_from_this()](const uvw::close_event &, uvw::tcp_handle &) { ptr->close(); }); 客户端->on<uvw::end_event>([](const uvw::end_event &, uvw::tcp_handle &client) { client.close(); }); srv.accept(*客户端); 客户端->read(); }); tcp->绑定(“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 &) { /* 处理错误 */ }); 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() {自动循环 = uvw::loop::get_default();listen(*loop);conn(*loop); 循环->run(); }
编写uvw
的主要原因是 C++ 中不存在有效的libuv
包装器。就这样。
为了能够使用uvw
,用户必须提供以下系统范围的工具:
至少支持 C++17 的全功能编译器。
libuv
(具体版本取决于使用的uvw
标签)
如果您使用meson
,将为您下载 libuv
编译测试和提取文档必须满足以下要求:
CMake 版本 3.13 或更高版本。
Doxygen 版本 1.8 或更高版本。
请注意, libuv
是项目依赖项的一部分,在某些情况下可能会被CMake
克隆(有关更多详细信息,请参阅下文)。
因此,用户不必安装它来运行测试或通过CMake
编译uvw
库。
您可以将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
用作编译库,请在包含项目之前在 cmake 中设置UVW_BUILD_LIBS
选项。
此选项触发名为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
uvw
的分支master
将是一个正在进行中的分支,遵循libuv
的分支v1.x (至少只要它仍然是其主分支)。
该文档基于doxygen
。构建它:
$ cd build
$ cmake ..
$ make docs
API 参考将以 HTML 格式在目录build/docs/html
中创建。
要使用您最喜欢的浏览器进行导航:
$ cd build
$ your_favorite_browser docs/html/index.html
最新版本也可以在线使用相同的版本,这是最后一个稳定的标签。
出于显而易见的原因,该文档主要受到官方 libuv API 文档的启发。
要编译和运行测试, uvw
需要libuv
和googletest
。
CMake
将在编译其他内容之前下载并编译这两个库。
构建测试:
$ cd build
$ cmake .. -DUVW_BUILD_TESTING=ON
$ make
$ ctest -j4 -R uvw
如果您还想测试libuv
和其他依赖项,请省略-R uvw
。
使用uvw
时只有一条规则:始终初始化资源并终止它们。
资源主要属于两个系列:句柄和请求。
句柄代表能够在活动时执行某些操作的长期对象。
请求(通常)代表通过句柄或独立执行的短期操作。
以下各节将简要解释初始化和终止此类资源的含义。
有关更多详细信息,请参阅在线文档。
初始化通常在幕后执行,甚至可以忽略,只要使用loop::resource
成员函数创建句柄即可。
另一方面,句柄会保持活动状态,直到有人明确关闭它们为止。因此,如果用户忘记句柄,内存使用量将会增加。
因此规则很快就变成了永远握紧你的手。就像调用它们的close
成员函数一样简单。
通常不需要初始化请求对象。不管怎样,推荐的创建请求的方式仍然是通过loop::resource
成员函数。
只要请求与未完成的底层活动绑定,它们就会保持活动状态。这意味着用户不必明确放弃请求。
因此,规则很快就变得可以随意提出请求并忘记它。就像调用它们的成员函数一样简单。
使用uvw
要做的第一件事是创建一个循环。如果默认的就足够了,那么很容易这样做:
自动循环= uvw::loop::get_default();
请注意,循环对象不需要显式关闭,即使它们提供close
成员函数以防用户想要这样做。
可以使用run
成员函数启动循环。下面的两个调用是等效的:
循环->run(); 循环->运行(uvw::loop::run_mode::DEFAULT);
可用模式有: DEFAULT
、 ONCE
、 NOWAIT
。请参阅libuv
的文档以获取更多详细信息。
为了创建资源并将其绑定到给定的循环,只需执行以下操作:
自动 tcp = 循环->资源<uvw::tcp_handle>();
上面的行创建并初始化一个 tcp 句柄,然后返回指向该资源的共享指针。
用户应该检查指针是否已正确初始化:如果出现错误,则不会。
还可以创建未初始化的资源以供稍后初始化,如下所示:
自动 tcp = 循环->uninitialized_resource<uvw::tcp_handle>(); tcp->init();
所有资源还接受在任何情况下都不会被触及的任意用户数据。
用户可以通过data
成员函数来设置和获取它们,如下所示:
资源->数据(std::make_shared<int>(42)); std::shared_ptr<void> 数据 = 资源->data();
资源需要std::shared_pointer<void>
并返回它,因此欢迎任何类型的数据。
用户在调用data
成员函数时可以显式指定除void
之外的类型:
std::shared_ptr<int> data = resource->data<int>();
请记住,在上一节中,句柄将保持自身活动状态,直到调用它的close
成员函数为止。
要了解哪些句柄仍然有效并绑定到给定循环,可以使用walk
成员函数。它返回句柄及其类型。因此,建议使用overloaded
,以便能够拦截所有感兴趣的类型:
handle.parent().walk(uvw::重载{ [](uvw::timer_handle &h){ /* 此处定时器的应用程序代码 */ }, [](auto &&){ /* 忽略所有其他类型 */ } });
该函数也可用于完全通用的方法。例如,可以轻松关闭所有挂起的句柄,如下所示:
循环->步行([](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
成员函数是注册长时间运行的侦听器的方法:
resource.on<事件类型>(监听器)
要了解给定类型是否存在侦听器,该类提供了一个has
函数模板。类似地, reset
函数模板用于重置并断开侦听器(如果有)。还存在非模板版本的reset
来清除整个发射器。
几乎所有资源在出现错误时都会发出error_event
。
所有其他事件均特定于给定资源并记录在 API 参考中。
下面的代码显示了如何使用uvw
创建一个简单的 tcp 服务器:
自动循环 = uvw::loop::get_default();自动 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>(); 客户端->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(*客户端); 客户端->read(); }); tcp->绑定(“127.0.0.1”, 4242); tcp->listen();
另请注意, uvw::tcp_handle
已经支持IPv6开箱即用。
API 参考是有关资源及其方法的更多详细信息的推荐文档。
如果用户需要使用uvw
尚未封装的功能,或者出于其他原因想要获取libuv
定义的底层数据结构,几乎uvw
中的所有类都可以直接访问它们。
请注意,除非用户确切知道自己在做什么以及存在哪些风险,否则不应直接使用此功能。原始是危险的,主要是因为循环、句柄或请求的生命周期管理完全由库控制,并且解决它可能会很快破坏事情。
话虽这么说,原始是使用raw
成员函数的问题:
自动循环 = uvw::loop::get_default();自动 tcp = 循环->资源<uvw::tcp_handle>();uv_loop_t *raw = 循环->raw();uv_tcp_t *handle = tcp->raw() ;
使用原始方式需要您自担风险,但不要指望在出现错误时获得任何支持。
对基于uvw
构建的其他工具和库感兴趣?那么您可能会发现以下内容很有用:
uvw_net
:一个包含客户端集合(HTTP/Modbus/SunSpec)的网络库,还包括 dns-sd/mdns 等发现实现。
如果您愿意,请随意将您的工具添加到列表中。
如果您想做出贡献,请向分支主服务器发送补丁作为拉取请求。
检查贡献者列表,了解到目前为止谁参与了。
代码和文档 版权所有 (c) 2016-2024 Michele Caini。
徽标版权所有 (c) 2018-2021 理查德·卡塞雷斯。
根据 MIT 许可证发布的代码和文档。
徽标在 CC BY-SA 4.0 下发布。
如果你想支持这个项目,可以给我一杯浓缩咖啡。
如果您发现这还不够,请随时以您喜欢的方式帮助我。