O NetUDP fornece um soquete UDP que pode enviar e receber datagrama.
Esta biblioteca está no modo de manutenção. Isso significa que apenas bugs críticos serão corrigidos. Nenhum novo recurso será adicionado. Se você quiser assumir essa biblioteca, faça -o.
A biblioteca possui uma grande falha de design, API flanky e testes. Fazia parte do meu experimento quando eu era jovem, e aprendi muito com isso. Não quero corrigir a API, porque, ao usar o QT, você não deve confiar na biblioteca de terceiros, mas simplesmente usar QUdpSocket .
Mantenha as dependências o mais pequeno possível, portanto a manutenção é mais fácil.
As duas classes principais que funcionam fora da caixa são Socket e RecycledDatagram . Basta criar um servidor, inicie -o. Em seguida, envie e receba datagramas. O servidor pode ingressar no grupo multicast para receber pacotes multicast.
O Socket usa um Worker que pode ser executado em encadeamento separado ou no encadeamento principal.
Toda alocação de datagrama é armazenada em std::shared_ptr<Datagram> . Isso permite reutilizar a estrutura do objeto de datagrama já alocada posteriormente sem realocar nada.
ISocket pode ser herdado para representar um Socket sem qualquer funcionalidade.
Socket e Worker podem ser herdados para implementar a comunicação personalizada entre servidor e trabalhador. Por exemplo, enviando objetos personalizados que podem ser serializados/desserializados no encadeamento do trabalhador.
Datagram pode ser herdado se um contêiner de dados personalizado, se necessário. Por exemplo, se os dados já estiverem serializados em uma estrutura. Colocando uma referência a essa estrutura dentro do Datagram Evite uma cópia para RecycledDatagram .
Um cliente/soquete básico pode ser encontrado em examples/EchoClientServer.cpp .
Este exemplo demonstra como criar um servidor que envie datagrama para endereçar 127.0.0.1 na porta 9999 .
# include < NetUdp/NetUdp.hpp >
int main ( int argc, char * argv[])
{
QCoreApplication app (argc, argv);
netudp::Socket server;
server. start ();
const std::string data = " Dummy Data " ;
server. sendDatagram (data. c_str (), data. length ()+ 1 , " 127.0.0.1 " , 9999 );
return QCoreApplication::exec ();
}O datagrama é emitido de uma porta aleatória escolhida pelo sistema operacional. Pode ser especificado explicitamente chamando
setTxPort(uint16_t).Se o soquete também receber o datagrama (ou seja,
inputEnabledfor True e CallsetRxPort), a porta RX usará. Para alterar esse comportamento padrão, ChamadasetSeparateRxTxSockets(true).
Este exemplo demonstra como receber um pacote no endereço 127.0.0.1 na porta 9999 .
# include < NetUdp/NetUdp.hpp >
int main ( int argc, char * argv[])
{
QCoreApplication app (argc, argv);
netudp::Socket client;
client. start ( " 127.0.0.1 " , 9999 );
QObject::connect (&client, &netudp::Socket::sharedDatagramReceived,
[]( const netudp::SharedDatagram& d)
{
qInfo ( " Rx : %s " , reinterpret_cast < const char *>(d-> buffer ()));
});
return QCoreApplication::exec ();
} Os erros podem ser observados via socketError(int error, QString description) sinais. Se o soquete não formar, ou se algo acontecer, o trabalhador iniciará um cronômetro de vigilância para reiniciar o soquete.
O tempo de reinicialização padrão é definido como 5 segundos, mas pode ser alterado via propriedade watchdogPeriod . A propriedade é expressa em milissegundos.
Por padrão, se o soquete interno for limitado a uma interface com uma porta, o Worker receberá datagrama recebido. Para evitar receber esse datagrama dentro Socket , ligue para setInputEnabled(false) .
multicastGroups é a lista de endereços multicast que são ouvidos. Para ingressar no Grupo Multicast, Call joinMulticastGroup(QString) , leaveMulticastGroup(QString) , leaveAllMulticastGroups .multicastListeningInterfaces : Defina as interfaces nas quais o soquete está ouvindo multicastGroups . Por padrão, todas as interfaces são ouvidas. Use joinMulticastInterface , leaveMulticastInterface , leaveAllMulticastInterfaces e isMulticastInterfacePresent .multicastLoopback se o datagrama multicast estiver em loop no sistema. No Windows, ele deve ser definido no lado do receptor. Nos sistemas Unix, ele deve ser definido no lado do remetente. multicastOutgoingInterfaces e interfaces de saída para o pacote multicast. Se não for especificado, o pacote está indo para todas as interfaces por padrão para fornecer uma experiência de plug and play. Internamente, o Socket rastreia várias informações para ter uma idéia do que está acontecendo.
isBounded indicam se o soquete está no momento em que uma interface de rede.*xBytesPerSeconds é um valor médio de todos os bytes recebidos/enviados no último segundo. Este valor é atualizado a cada segundos. * can be replaced by t and r*xBytesTotal Total recebido/enviado bytes desde o início. * can be replaced by t and r*xPacketsPerSeconds é um valor médio de todos os pacotes recebidos/enviados no último segundo. Este valor é atualizado a cada segundos. * can be replaced by t and r*xPacketsTotal Total recebido/enviado pacotes desde o início. * can be replaced by t and r Essas propriedades podem ser limpas com clearRxCounter / clearTxCounter / clearCounters .
Ao chamar qualquer uma das seguintes funções, um memcpy acontecerá com um RecycledDatagram .
virtual bool sendDatagram ( const uint8_t * buffer, const size_t length, const QHostAddress& address, const uint16_t port, const uint8_t ttl = 0 );
virtual bool sendDatagram ( const uint8_t * buffer, const size_t length, const QString& address, const uint16_t port, const uint8_t ttl = 0 );
virtual bool sendDatagram ( const char * buffer, const size_t length, const QHostAddress& address, const uint16_t port, const uint8_t ttl = 0 );
virtual bool sendDatagram ( const char * buffer, const size_t length, const QString& address, const uint16_t port, const uint8_t ttl = 0 ); Para evitar cópias inúteis da memória, é recomendável recuperar um datagrama do cache Socket com makeDatagram(const size_t length) . Em seguida, use este netudp::SharedDatagram para serializar dados. E ligue:
virtual bool sendDatagram (std::shared_ptr<Datagram> datagram, const QString& address, const uint16_t port, const uint8_t ttl = 0 );
virtual bool sendDatagram (std::shared_ptr<Datagram> datagram); Se você não estiver satisfeito com o comportamento Socket , ou se deseja zombar Socket sem qualquer dependência do QtNetwork . É possível estender ISocket para usar sua funcionalidade básica.
isRunning / isBounded .Você precisa substituir:
bool start() : inicie o soquete. O reinício automático para sobreviver do erro é esperado. Não se esqueça de chamar ISocket::start no começo.bool stop() : pare o soquete. Limpe toda a tarefa em execução, cache vazio, buffers, etc ... Não se esqueça de chamar ISocket::stop no começo. Para garantir a limpeza máxima, sempre pare a cada parto de qualquer peça falhada.joinMulticastGroup(const QString& groupAddress) : Implementação para ingressar em um grupo multicast. Não se esqueça de chamar ISocket::joinMulticastGroup .leaveMulticastGroup(const QString& groupAddress) : Implementação para deixar um grupo multicast. Não se esqueça de chamar ISocket::leaveMulticastGroup . # include < NetUdp/ISocket.hpp >
class MyAbstractSocket : netudp::ISocket
{
Q_OBJECT
public:
MyAbstractSocket (QObject* parent = nullptr ) : netudp::ISocket(parent) {}
public Q_SLOTS:
bool start () override
{
if (! netudp::ISocket::start ())
return false ;
// Do your business ...
return true ;
}
bool stop () override
{
auto stopped = netudp::ISocket::stop ()
// Do your business ...
return stopped;
}
bool joinMulticastGroup ( const QString& groupAddress) override
{
// Join groupAddress ...
return true ;
}
bool leaveMulticastGroup ( const QString& groupAddress) override
{
// Leave groupAddress ...
return true ;
}
} Socket e Worker trabalham principalmente no par; portanto, se substituir um, muitas vezes faz sentido substituir o outro.
Razões para substituir Worker :
Datagram personalizada Razões para substituir Socket
Worker personalizado.Datagram personalizada. O uso de um Datagram personalizado pode reduzir a cópia da memória, dependendo do seu aplicativo.
Worker .Socket::sendDatagram(SharedDatagram, ...) com ele.Socket para usá -lo ao ligar com Socket::sendDatagram(const uint8_t*, ...) . Um memcpy acontecerá. Portanto, não use um Datagram personalizado para esse fim. # include < NetUdp/Datagram.hpp >
class MyDatagram : netudp::Datagram
{
uint8_t * myBuffer = nullptr ;
size_t myLength = 0 ;
public:
uint8_t * buffer () { return myBuffer; }
const uint8_t * buffer () const { return myBuffer; }
size_t length () const { return myLength; }
}; Ao herdar do SocketWorker , você pode substituir:
bool isPacketValid(const uint8_t* buffer, const size_t length) const : Chamado cada vez que um datagrama é recebido. Verifique se um pacote é válido dependendo do seu protocolo. A implementação padrão apenas retorna true. Você pode adicionar uma verificação do CRC ou algo assim. Retornar false aqui aumentará o contador rxInvalidPacketTotal no Socket .void onReceivedDatagram(const SharedDatagram& datagram) : chamado cada vez que um datagrama válido chegou. Implementação padrão Emit receivedDatagram Signal. Substitua esta função para adicionar um sistema de mensagens personalizado ou uma desserialização personalizada.std::shared_ptr<Datagram> makeDatagram(const size_t length) : crie Datagram personalizado para rx.Worker , ligue para void onSendDatagram(const SharedDatagram& datagram) para enviar um datagrama para a rede.SocketWorker herdado do QObject , portanto, use a macro Q_OBJECT para gerar sinais personalizados.Exemplo:
# include < NetUdp/Worker.hpp >
class MySocketWorker : netudp::Worker
{
Q_OBJECT
public:
MySocketWorker (QObject* parent = nullptr ) : netudp::SocketWorker(parent) {}
public Q_SLOTS:
bool std::unique_ptr<SocketWorker> createWorker () override
{
auto myWorker = std::make_unique<MyWorker>();
// Init your worker with custom stuff ...
// Even keep reference to MyWorker* if you need later access
// It's recommended to communicate via signals to the worker
// Connect here ...
return std::move (myWorker);
}
// This is called before creating a SharedDatagram and calling onDatagramReceived
bool isPacketValid ( const uint8_t * buffer, const size_t length) const override
{
// Add your checks, like header, fixed size, crc, etc...
return buffer && length;
}
void onDatagramReceived ( const SharedDatagram& datagram) override
{
// Do your business ...
// This super call is optionnal. If not done Socket will never trigger onDatagramReceived
netudp::SocketWorker::onDatagramReceived (datagram);
}
std::shared_ptr<Datagram> makeDatagram ( const size_t length) override
{
// Return your custom diagram type used for rx
return std::make_shared<MyDiagram>(length);
}
}A personalização do trabalhador faz sentido principalmente quando está em execução em um tópico separado. Caso contrário, não dará nenhum impulso de desempenho. Não se esqueça de ligar para
Socket::setUseWorkerThread(true).
Ao herdar do Socket , você pode substituir:
bool std::unique_ptr<SocketWorker> createWorker() const : crie um trabalhador personalizado.void onDatagramReceived(const SharedDatagram& datagram) : lidera o datagrama lá. Implementação padrão emite sinais datagramReceivedstd::shared_ptr<Datagram> makeDatagram(const size_t length) : crie Datagram personalizado que será usado no Socket::sendDatagram(const uint8_t*, ...) .Socket herdar do QObject ; portanto, use a macro Q_OBJECT para gerar sinais personalizados.Exemplo:
# include < NetUdp/Socket.hpp >
class MySocket : netudp::Socket
{
Q_OBJECT
public:
MySocket (QObject* parent = nullptr ) : netudp::Socket(parent) {}
public Q_SLOTS:
bool std::unique_ptr<Worker> createWorker () override
{
auto myWorker = std::make_unique<MyWorker>();
// Init your worker with custom stuff ...
// Even keep reference to MyWorker* if you need later access
// It's recommended to communicate via signals to the worker
// Connect here ...
return std::move (myWorker);
}
void onDatagramReceived ( const SharedDatagram& datagram) override
{
// Do your business ...
// This super call is optionnal. If not done Socket will never trigger datagramReceived signal
netudp::Socket::onDatagramReceived (datagram);
}
std::shared_ptr<Datagram> makeDatagram ( const size_t length) override
{
// Return your custom diagram type used for tx
return std::make_shared<MyDiagram>(length);
}
} Este exemplo demonstra um eco entre um servidor e um cliente. Socket Enviar um pacote para um cliente, o cliente responde o mesmo pacote. Ctrl+C para sair.
$ > NetUdp_EchoClientServer --help
Options:
- ? , -h, --help Displays this help.
-t Make the worker live in a different thread. Default false
-s, --src < port > Port for rx packet. Default " 11111 " .
-d, --dst < port > Port for tx packet. Default " 11112 " .
--src-addr < ip > Ip address for server. Default " 127.0.0.1 "
--dst-addr < ip > Ip address for client. Default " 127.0.0.1 "
$ > NetUdp_EchoClientServer
> app: Init application
> server: Set Rx Address to 127.0.0.1
> server: Set Rx Port to 11111
> client: Set Rx Address to 127.0.0.1
> client: Set Rx Port to 11112
> app: Start application
> client: Rx : Echo 0
> server: Rx : Echo 0
> client: Rx : Echo 1
> server: Rx : Echo 1
> client: Rx : Echo 2
> server: Rx : Echo 2
> ... Este exemplo também é dividido em 2 exemplos: NetUdp_EchoClient & NetUdp_EchoServer .
Demonstre como ingressar no grupo IP multicast. Envie um pacote e leia -o de volta via loopback.
$ > NetUdp_EchoMulticastLoopback --help
Options:
- ? , -h, --help Displays this help.
-t Make the worker live in a different thread. Default
false
-p Print available multicast interface name
-s, --src < port > Port for rx packet. Default " 11111 " .
-i, --ip < ip > Ip address of multicast group. Default " 239.0.0.1 "
--if, --interface < if > Name of the iface to join. Default is os dependent
netudp::registerQmlTypes(); deve ser chamado no principal para registrar tipos de QML. Este exemplo mostra como enviar um datagrama unicast como uma string para 127.0.0.1:9999 . Não se esqueça de iniciar o soquete antes de enviar nenhuma mensagem.
import QtQuick 2.0
import QtQuick . Controls 2.0
import NetUdp 1.0 as NetUdp
Button
{
text : "send unicast"
onClicked : ( ) = > socket . sendDatagram ( {
address : "127.0.0.1" ,
port : 9999 ,
data : "My Data"
// Equivalent to 'data: [77,121,32,68,97,116,97]'
} )
NetUdp . Socket
{
id: socket
Component . onCompleted : ( ) = > start ( )
}
}Este exemplo mostra como receber o datagrama. Não se esqueça de começar a ouvir um endereço e uma porta. O datagrama é sempre recebido como uma string. Pode ser facilmente decodificado para manipular uma matriz de bytes.
import NetUdp 1.0 as NetUdp
NetUdp . Socket
{
onDatagramReceived : function ( datagram )
{
console . log ( `datagram : ${ JSON . stringify ( datagram ) } ` )
console . log ( `datagram.data (string) : " ${ datagram . data } "` )
let byteArray = [ ]
for ( let i = 0 ; i < datagram . data . length ; ++ i )
byteArray . push ( datagram . data . charCodeAt ( i ) )
console . log ( `datagram.data (bytes): [ ${ byteArray } ]` )
console . log ( `datagram.destinationAddress : ${ datagram . destinationAddress } ` )
console . log ( `datagram.destinationPort : ${ datagram . destinationPort } ` )
console . log ( `datagram.senderAddress : ${ datagram . senderAddress } ` )
console . log ( `datagram.senderPort : ${ datagram . senderPort } ` )
console . log ( `datagram.ttl : ${ datagram . ttl } ` )
}
Component . onCompleted : ( ) = > start ( "127.0.0.1" , 9999 )
}Envie o datagrama multicast funcionar quase o mesmo que o Unicast. A única diferença é que você controla em qual interface os dados estão indo.
import NetUdp 1.0 as NetUdp
NetUdp . Socket
{
id : socket
// A Packet will be send to each interface
// The socket monitor for interface connection/disconnection
multicastOutgoingInterfaces : [ "lo" , "eth0" ]
// Required in unix world if you want loopback on the same system
multicastLoopback : true
Component . onCompleted : ( ) => start ( )
}Em seguida, envie dados como no Unicast:
socket . sendDatagram ( {
address : "239.1.2.3" ,
port : 9999 ,
data : "My Data"
} )Para recebê -lo, inscreva -se no grupo multicast e escolha em quais interfaces.
import NetUdp 1.0 as NetUdp
NetUdp . Socket
{
multicastGroups : [ "239.1.3.4" ]
multicastListeningInterfaces : [ "lo" , "eth0" ]
// Required in the windows world if you want loopback on the same system
multicastLoopback : true
onDatagramReceived : ( datagram ) = > console . log ( `datagram : ${ JSON . stringify ( datagram ) } ` )
// Listen port 9999
Component . onCompleted : ( ) = > start ( 12999934 )
}Esta biblioteca também fornece um objeto de ferramenta que demonstre todas as funcionalidades QMLS. Isso se destina a depuração rápida ou funcionalidades de teste se ainda não estiver construído.
O gráfico de dependências pode ser gerado com:
mkdir -p build && cd build cmake --graphviz=dependencies.dot .. dot -Tsvg -o ../docs/dependencies.svg dependencies.dot -Gbgcolor=transparent -Nfillcolor=white -Nstyle=filled
pip install cmakelang ) Netudp Use formatação automática para cpp , cmake . Os scripts da pasta contém script auxiliar. Recomenda-se configurar o formato automático dentro do IDE.
cd scripts
./clangformat.sh
./cmakeformat.sh
? Netudp -> netudp? netudp-> netudp ➖ Remova a dependência do SPDLOG no sabor de QCDebug/QCWARNING ➕ Gerenciar dependências via CPM ♻️ Trabalhador: Interface-> Iface para evitar conflitos com MSVC # Definir Interface Struct https://stackoverflow.com/questions/25234203/whats://stackoverflow.com/questions/25234203/whats://stackoverflow.com/questions/25234203/wha ♻️ PIMPL SocketPrivate ♻️ PIMPL RecycledDataGramprivate? Torne recycler Private, pois todos os recicladores incluem foram movidos para dentro do PIMPL ⚡️ netudp_enable_unity_build? InterfaceProvider: Use Steady_Clock em vez do sistema para evitar reversão? Imprimir comando de construção no gráfico de readme de atualização cmake com dependências
? Inclua a falta de cabeçalho QelapSedTimer no trabalhador
? Use ponteiro bruto para o tópico de trabalhador e trabalhador. ? ️ Isso deve corrigir o problema quando a porta não foi completamente liberada.
NETUDP_ENABLE_PCH , NETUDP_ENABLE_EXAMPLES , NETUDP_ENABLE_TESTSresize .multicastOutgoingInterfaces em vez do multicastInterfaceName . Se multicastOutgoingInterfaces estiver, pacotes vazios serão enviados em todas as interfaces.multicastListenOnAllInterfaces e faça com que o padrão seja o padrão quando multicastListeningInterfaces estiver vazio.