Você tem uma pergunta que não exige que você abra um problema? Junte-se ao canal gitter.
Se você usa uvw
e deseja agradecer ou apoiar o projeto, considere se tornar um patrocinador .
Você pode me ajudar a fazer a diferença. Muito obrigado a todos aqueles que me apoiaram e ainda me apoiam hoje.
uvw
começou como um wrapper somente de cabeçalho, baseado em eventos, pequeno e fácil de usar para libuv
escrito em C++ moderno.
Agora está finalmente disponível também como uma biblioteca estática compilável.
A ideia básica é envolver a interface C do libuv
por trás de uma API C++ elegante.
Observe que uvw
permanece fiel à API do libuv
e não adiciona nada à sua interface. Pelas mesmas razões, os usuários da biblioteca devem seguir as mesmas regras usadas com libuv
.
Por exemplo, um identificador deve ser inicializado antes de qualquer outra operação e fechado quando não estiver mais em uso.
#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> cliente = srv.parent().resource<uvw::tcp_handle>(); cliente->on<uvw::close_event>([ptr = srv.shared_from_this()](const uvw::close_event &, uvw::tcp_handle &) { ptr->close(); }); cliente->on<uvw::end_event>([](const uvw::end_event &, uvw::tcp_handle &client) { client.close(); }); srv.accept(*cliente); cliente->ler(); }); tcp->bind("127.0.0.1", 4242); tcp->ouvir(); }void conn(uvw::loop &loop) {auto tcp = loop.resource<uvw::tcp_handle>(); tcp->on<uvw::error_event>([](const uvw::error_event &, uvw::tcp_handle &) { /* tratar erros */ }); 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); loop->executar(); }
A principal razão pela qual uvw
foi escrito é o fato de não existir um wrapper libuv
válido em C++. Isso é tudo.
Para poder usar uvw
, os usuários devem fornecer as seguintes ferramentas para todo o sistema:
Um compilador completo que suporta pelo menos C++17.
libuv
(cuja versão depende da tag do uvw
em uso)
Se você usar meson
, o libuv será baixado para você
Os requisitos abaixo são obrigatórios para compilar os testes e extrair a documentação:
CMake versão 3.13 ou posterior.
Doxygen versão 1.8 ou posterior.
Observe que libuv
faz parte das dependências do projeto e pode ser clonado pelo CMake
em alguns casos (veja abaixo para mais detalhes).
Por isso, os usuários não precisam instalá-lo para executar os testes ou quando as bibliotecas uvw
são compiladas através do CMake
.
Você pode usar uvw
com meson simplesmente adicionando-o ao diretório subprojects
do seu projeto.
Para compilar uvw
do código-fonte sem usá-lo como um subprojeto, no diretório de origem uvw
, execute:
$ meson setup build
Se você quiser uma biblioteca estática, adicione --default-library=static
$ cd build
$ meson compile
uvw
é uma biblioteca de modo duplo. Ele pode ser usado apenas no formato de cabeçalho ou como uma biblioteca estática compilada.
As seções a seguir descrevem o que fazer em ambos os casos para colocar uvw
em funcionamento em seu próprio projeto.
Para usar uvw
como uma biblioteca somente de cabeçalho, tudo o que é necessário é incluir o cabeçalho uvw.hpp
ou um dos outros arquivos uvw/*.hpp
.
É uma questão de adicionar a seguinte linha no topo de um arquivo:
#include <uvw.hpp>
Em seguida, passe o argumento -I
adequado ao compilador para adicionar o diretório src
aos caminhos de inclusão.
Observe que os usuários são obrigados a configurar corretamente os diretórios de inclusão e os caminhos de pesquisa de bibliotecas para libuv
neste caso.
Quando usado por meio de CMake
, o destino uvw::uvw
é exportado por conveniência.
Para usar uvw
como uma biblioteca compilada, defina as opções UVW_BUILD_LIBS
em cmake antes de incluir o projeto.
Esta opção aciona a geração de um alvo chamado uvw::uvw-static
. A versão correspondente do libuv
também é compilada e exportada como uv::uv-static
por conveniência.
Caso você não use ou não queira usar CMake
, você ainda pode compilar todos os arquivos .cpp
e incluir todos os arquivos .h
para realizar o trabalho. Nesse caso, os usuários são obrigados a configurar corretamente os diretórios de inclusão e os caminhos de pesquisa de bibliotecas para libuv
.
Começando com a tag v1.12.0 de libuv
, uvw
segue o esquema de versionamento semântico.
O problema é que qualquer versão do uvw
também exige rastrear explicitamente a versão do libuv
à qual está vinculada.
Por isso, este último será anexado à versão do uvw
. Por exemplo:
vU.V.W_libuv-vX.Y
Em particular, aplica-se o seguinte:
UVW são versões principais, secundárias e de patch do uvw
.
XY é a versão da libuv
à qual se referir (onde qualquer versão do patch é válida).
Em outros termos, as tags ficarão assim a partir de agora:
v1.0.0_libuv-v1.12
O branch master
do uvw
será um branch de trabalho em andamento que segue o branch v1.x do libuv
(pelo menos enquanto permanecer como seu branch master ).
A documentação é baseada em doxygen
. Para construí-lo:
$ cd build
$ cmake ..
$ make docs
A referência da API será criada em formato HTML dentro do diretório build/docs/html
.
Para navegar com seu navegador favorito:
$ cd build
$ your_favorite_browser docs/html/index.html
A mesma versão também está disponível online para a versão mais recente, que é a última tag estável.
A documentação é inspirada principalmente na documentação oficial da API libuv por razões óbvias.
Para compilar e executar os testes, uvw
requer libuv
e googletest
.
CMake
baixará e compilará ambas as bibliotecas antes de compilar qualquer outra coisa.
Para construir os testes:
$ cd build
$ cmake .. -DUVW_BUILD_TESTING=ON
$ make
$ ctest -j4 -R uvw
Omita -R uvw
se você também quiser testar libuv
e outras dependências.
Existe apenas uma regra ao usar uvw
: sempre inicialize os recursos e finalize-os.
Os recursos pertencem principalmente a duas famílias: identificadores e solicitações .
Os identificadores representam objetos de longa duração, capazes de executar determinadas operações enquanto estão ativos.
As solicitações representam (normalmente) operações de curta duração executadas por meio de um identificador ou de forma independente.
As seções a seguir explicarão resumidamente o que significa inicializar e encerrar esses tipos de recursos.
Para obter mais detalhes, consulte a documentação on-line.
A inicialização geralmente é realizada nos bastidores e pode até ser ignorada, desde que os identificadores sejam criados usando a função de membro loop::resource
.
Por outro lado, os identificadores permanecem ativos até que sejam fechados explicitamente. Por causa disso, o uso de memória aumentará se os usuários simplesmente esquecerem um identificador.
Portanto, a regra rapidamente se torna sempre feche as alças . É tão simples quanto chamar a função de membro close
neles.
Normalmente, não é necessário inicializar um objeto de solicitação. De qualquer forma, a forma recomendada de criar uma solicitação ainda é através da função membro loop::resource
.
As solicitações permanecerão vivas enquanto estiverem vinculadas a atividades subjacentes inacabadas. Isso significa que os usuários não precisam descartar uma solicitação explicitamente.
Portanto a regra rapidamente se torna à vontade para fazer uma solicitação e esquecê-la . É tão simples quanto chamar uma função membro neles.
A primeira coisa a fazer para usar uvw
é criar um loop. Caso o padrão seja suficiente, é fácil fazer isso:
loop automático = uvw::loop::get_default();
Observe que os objetos de loop não precisam ser fechados explicitamente, mesmo que ofereçam a função de membro close
caso um usuário queira fazer isso.
Os loops podem ser iniciados usando a função de membro run
. As duas chamadas abaixo são equivalentes:
loop->executar(); loop->executar(uvw::loop::run_mode::DEFAULT);
Os modos disponíveis são: DEFAULT
, ONCE
, NOWAIT
. Consulte a documentação do libuv
para obter mais detalhes.
Para criar um recurso e vinculá-lo ao loop fornecido, basta fazer o seguinte:
auto tcp = loop->recurso<uvw::tcp_handle>();
A linha acima cria e inicializa um identificador tcp e, em seguida, um ponteiro compartilhado para esse recurso é retornado.
Os usuários devem verificar se os ponteiros foram inicializados corretamente: em caso de erros, não serão.
Também é possível criar recursos não inicializados para inicializar posteriormente como:
auto tcp = loop->uninitialized_resource<uvw::tcp_handle>(); tcp->init();
Todos os recursos também aceitam dados arbitrários do usuário que não serão tocados em nenhum caso.
Os usuários podem configurá-los e obtê-los por meio da função de membro data
da seguinte maneira:
recurso->dados(std::make_shared<int>(42)); std::shared_ptr<void> dados = recurso->dados();
Os recursos esperam um std::shared_pointer<void>
e o retornam, portanto qualquer tipo de dado é bem-vindo.
Os usuários podem especificar explicitamente um tipo diferente de void
ao chamar a função membro data
:
std::shared_ptr<int> dados = recurso->dados<int>();
Lembre-se da seção anterior que um identificador se manterá ativo até que alguém invoque a função de membro close
nele.
Para saber quais são os identificadores que ainda estão ativos e vinculados a um determinado loop, existe a função membro walk
. Ele retorna identificadores com seus tipos. Portanto, recomenda-se o uso de overloaded
para poder interceptar todos os tipos de juros:
handle.parent().walk(uvw::sobrecarregado{ [](uvw::timer_handle &h){ /* código do aplicativo para temporizadores aqui */ }, [](auto &&){ /* ignorar todos os outros tipos */ } });
Esta função também pode ser usada para uma abordagem completamente genérica. Por exemplo, todas as alças pendentes podem ser fechadas facilmente da seguinte forma:
loop->walk([](auto &&h){ h.close(); });
Não há necessidade de acompanhá-los.
uvw
oferece uma abordagem baseada em eventos onde os recursos são pequenos emissores de eventos aos quais os ouvintes estão anexados.
Anexar ouvintes aos recursos é a forma recomendada de receber notificações sobre as suas operações.
Ouvintes são objetos que podem ser chamados do tipo void(event_type &, resource_type &)
, onde:
event_type
é o tipo de evento para o qual foram projetados.
resource_type
é o tipo de recurso que originou o evento.
Isso significa que os seguintes tipos de função são todos válidos:
void(event_type &, resource_type &)
void(const event_type &, resource_type &)
void(event_type &, const resource_type &)
void(const event_type &, const resource_type &)
Observe que não há necessidade de manter referências aos recursos, pois eles se passam como argumento sempre que um evento é publicado.
A função de membro on
é o caminho a seguir para registrar ouvintes de longa duração:
recurso.on<event_type>(ouvinte)
Para saber se existe um ouvinte para um determinado tipo, a classe oferece um modelo de função has
. Da mesma forma, o modelo de função reset
é usado para redefinir e, portanto, desconectar ouvintes, se houver. Também existe uma versão de reset
sem modelo para limpar um emissor como um todo.
Quase todos os recursos emitem error_event
em caso de erros.
Todos os outros eventos são específicos para determinado recurso e documentados na referência da API.
O código abaixo mostra como criar um servidor TCP simples usando uvw
:
loop automático = uvw::loop::get_default();auto tcp = loop->resource<uvw::tcp_handle>(); tcp->on<uvw::error_event>([](const uvw::error_event &, uvw::tcp_handle &) { /* algo deu errado */ }); tcp->on<uvw::listen_event>([](const uvw::listen_event &, uvw::tcp_handle &srv) { std::shared_ptr<uvw::tcp_handle> cliente = srv.parent().resource<uvw::tcp_handle>(); cliente->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 &) { /* dados recebidos */ }); srv.accept(*cliente); cliente->ler(); }); tcp->bind("127.0.0.1", 4242); tcp->ouvir();
Observe também que uvw::tcp_handle
já suporta IPv6 pronto para uso.
A referência da API é a documentação recomendada para obter mais detalhes sobre os recursos e seus métodos.
Caso os usuários precisem usar funcionalidades ainda não empacotadas pelo uvw
ou se desejarem obter as estruturas de dados subjacentes conforme definidas pela libuv
por algum outro motivo, quase todas as classes no uvw
dão acesso direto a elas.
Por favor, note que estas funções não devem ser usadas diretamente, a menos que os usuários saibam exatamente o que estão fazendo e quais são os riscos. Ficar bruto é perigoso, principalmente porque o gerenciamento vitalício de um loop, um identificador ou uma solicitação é completamente controlado pela biblioteca e contornar isso pode quebrar as coisas rapidamente.
Dito isto, tornar-se bruto é uma questão de usar as funções-membro raw
:
loop automático = uvw::loop::get_default();auto tcp = loop->resource<uvw::tcp_handle>();uv_loop_t *raw = loop->raw();uv_tcp_t *handle = tcp->raw() ;
Siga o caminho bruto por sua própria conta e risco, mas não espere nenhum suporte em caso de bugs.
Interessado em ferramentas e bibliotecas adicionais baseadas no uvw
? Você pode achar o seguinte útil:
uvw_net
: uma biblioteca de rede com uma coleção de clientes (HTTP/Modbus/SunSpec) que também inclui implementações de descoberta como dns-sd/mdns.
Sinta-se à vontade para adicionar sua ferramenta à lista, se desejar.
Se você quiser contribuir, envie patches como solicitações pull para o branch master.
Verifique a lista de contribuidores para ver quem já participou até agora.
Código e documentação Copyright (c) 2016-2024 Michele Caini.
Logotipo Copyright (c) 2018-2021 Richard Caseres.
Código e documentação liberados sob a licença do MIT.
Logotipo lançado sob CC BY-SA 4.0.
Se quiser apoiar este projeto, pode oferecer-me um expresso.
Se achar que não é suficiente, fique à vontade para me ajudar da maneira que preferir.