Netudp proporciona un socket UDP que puede enviar y recibir datagrama.
Esta biblioteca está en modo de mantenimiento. Significa que solo se solucionarán errores críticos. No se agregarán nuevas características. Si desea hacerse cargo de esta biblioteca, hágalo.
La biblioteca tiene una gran falla de diseño, una API flanquea y pruebas. Era parte de mi experiencia cuando era joven, y aprendí mucho de él. No quiero arreglar la API, porque al usar QT no debe confiar en la biblioteca de terceros, sino simplemente usar QUdpSocket .
Mantenga sus dependencias lo más pequeñas posible, por lo que la capacidad de mantener es más fácil.
Las dos clases principales que funcionan fuera de la caja son Socket y RecycledDatagram . Simplemente cree un servidor, comience. Luego envíe y reciba datagramas. El servidor puede unir los grupos de multidifusión a los paquetes de multidifusión.
El Socket usa un Worker que puede ejecutarse en hilo separado o en el hilo principal.
Cada asignación de datagrama se almacena en std::shared_ptr<Datagram> . Esto permite reutilizar la estructura del objeto de datagrama ya asignada más tarde sin reasignar nada.
ISocket puede ser heredado para representar un Socket sin ninguna funcionalidad.
Socket y Worker pueden ser heredados para implementar una comunicación personalizada entre servidor y trabajador. Por ejemplo, enviar objetos personalizados que se pueden serializar/deserializar en el hilo de trabajadores.
Datagram se puede heredar si es necesario un contenedor de datos personalizado. Por ejemplo, si los datos ya están serializados en una estructura. Poner una referencia a esa estructura dentro del Datagram , evite una copia a RecycledDatagram .
Se puede encontrar un cliente/socket básico en examples/EchoClientServer.cpp .
Este ejemplo demuestra cómo crear un servidor que envíe datagrama a la dirección 127.0.0.1 en el puerto 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 ();
}El datagrama se emite desde un puerto aleatorio elegido por el sistema operativo. Se puede especificar explícitamente llamando
setTxPort(uint16_t).Si el socket también recibe datagram (es decir,
inputEnabledes verdadero y llamesetRxPort), entonces el puerto RX usará. Para cambiar este comportamiento predeterminado, llamesetSeparateRxTxSockets(true).
Este ejemplo demuestra cómo recibir un paquete en la dirección 127.0.0.1 en el puerto 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 ();
} Se pueden observar errores a través de señales socketError(int error, QString description) . Si el enchufe no se une, o si algo sucedió, el trabajador comenzará un temporizador de vigilancia para reiniciar el socket.
El tiempo de reinicio predeterminado se establece en 5 segundos, pero se puede cambiar a través de la propiedad watchdogPeriod . La propiedad se expresa en milisegundos.
Por defecto, si el socket interno está limitado a una interfaz con un puerto, el Worker recibirá datagrama entrante. Para evitar recibir esos datagramas dentro Socket , llamar setInputEnabled(false) .
multicastGroups es la lista de direcciones de multidifusión que se escuchan. Para unirse al grupo de multidifusión Llame joinMulticastGroup(QString) , leaveMulticastGroup(QString) , leaveAllMulticastGroups .multicastListeningInterfaces : establezca las interfaces en las que el zócalo está escuchando multicastGroups . Por defecto, se escuchan todas las interfaces. Use joinMulticastInterface , leaveMulticastInterface , leaveAllMulticastInterfaces e isMulticastInterfacePresent .multicastLoopback si el datagrama de multidifusión está en bucle en el sistema. En Windows debe configurarse en el lado del receptor. En los sistemas UNIX, debe configurarse en el lado del remitente. multicastOutgoingInterfaces : interfaces salientes para paquetes de multidifusión. Si no se especifica, entonces el paquete va a todas las interfaces de forma predeterminada para proporcionar una experiencia de enchufe y reproducción. Internamente, el Socket rastrea la información múltiple para tener una idea de lo que está sucediendo.
isBounded indica si el socket está actualmente vinculado a una interfaz de red.*xBytesPerSeconds es un valor promedio de todos los bytes recibidos/enviados en el último segundo. Este valor se actualiza cada segundos. * can be replaced by t and r*xBytesTotal Total recibido/enviado bytes desde el inicio. * can be replaced by t and r*xPacketsPerSeconds es un valor promedio de todos los paquetes recibidos/enviados en el último segundo. Este valor se actualiza cada segundos. * can be replaced by t and r*xPacketsTotal TOTAL recibió/envió paquetes desde el inicio. * can be replaced by t and r Esa propiedad se puede borrar con clearRxCounter / clearTxCounter / clearCounters .
Al llamar a cualquiera de la siguiente función, un memcpy se sucederá a un 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 una copia de memoria inútil, se recomienda recuperar un datagrama de Socket Cache con makeDatagram(const size_t length) . Luego use este netudp::SharedDatagram para serializar datos. Y llamar:
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); Si no está satisfecho con el comportamiento Socket , o si desea Socket sin dependencia de QtNetwork . Es posible extender ISocket para usar su funcionalidad básica.
isRunning / isBounded .Necesitas anular:
bool start() : inicie el socket. Se espera un reinicio automático para sobrevivir del error. No olvides llamar ISocket::start al comienzo.bool stop() : Detente el enchufe. Borre todas las tareas en ejecución, caché vacío, buffers, etc. No olvides llamar ISocket::stop al comienzo. Para garantizar la máxima limpieza, siempre detenga cada uno, incluso si la detención de cualquier parte falló.joinMulticastGroup(const QString& groupAddress) : implementación para unirse a un grupo de multidifusión. No olvides llamar ISocket::joinMulticastGroup .leaveMulticastGroup(const QString& groupAddress) : implementación para dejar un grupo de multidifusión. No olvides llamar 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 y Worker trabajan principalmente en pares, por lo que si anula uno, a menudo tiene sentido anular al otro.
Razones para anular Worker :
Datagram personalizada Razones para anular Socket
Worker personalizados.Datagram personalizada. El uso de un Datagram personalizado puede reducir la copia de memoria según su aplicación.
Worker .Socket::sendDatagram(SharedDatagram, ...) con él.Socket para usarlo cuando llame con Socket::sendDatagram(const uint8_t*, ...) . Un memcpy sucederá. Así que no use un Datagram personalizado para ese propósito. # 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; }
}; Al heredar desde SocketWorker puede anular:
bool isPacketValid(const uint8_t* buffer, const size_t length) const : llamado cada vez que se recibe un datagrama. Compruebe si un paquete es válido según su protocolo. Implementación predeterminada simplemente devuelva verdadero. Puede agregar un cheque de CRC o algo así. Devolver falso aquí incrementará el contador rxInvalidPacketTotal en Socket .void onReceivedDatagram(const SharedDatagram& datagram) : llamado cada vez que llegue un datagrama válido. EMIT de implementación predeterminado Recibido de la señal receivedDatagram . Anule esta función para agregar un sistema de mensajería personalizado o una deserialización personalizada.std::shared_ptr<Datagram> makeDatagram(const size_t length) : cree Datagram personalizado para RX.Worker , llame void onSendDatagram(const SharedDatagram& datagram) para enviar un datagrama a la red.SocketWorker Hereit de QObject , así que use Q_OBJECT Macro para generar señales personalizadas.Ejemplo:
# 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);
}
}La personalización del trabajador tiene sentido en su mayoría cuando se ejecuta en un hilo separado. De lo contrario, no dará ningún impulso de rendimiento. No olvide llamar
Socket::setUseWorkerThread(true).
Al heredar desde Socket , puede anular:
bool std::unique_ptr<SocketWorker> createWorker() const : crea un trabajador personalizado.void onDatagramReceived(const SharedDatagram& datagram) : manejar datagram allí. Implementación predeterminada emit de señales datagramReceivedstd::shared_ptr<Datagram> makeDatagram(const size_t length) : cree Datagram personalizado que se usará en Socket::sendDatagram(const uint8_t*, ...) .Socket herede de QObject , así que use Q_OBJECT Macro para generar señales personalizadas.Ejemplo:
# 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 ejemplo demuestra un eco entre un servidor y un cliente. Socket Envía un paquete a un cliente, el cliente responde el mismo paquete. Ctrl+C para dejar de fumar.
$ > 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 ejemplo también se divide en 2 ejemplos: NetUdp_EchoClient & NetUdp_EchoServer .
Demuestre cómo unirse al grupo IP de multidifusión. Envíe un paquete y lea de nuevo a través de 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(); Debe llamarse en la principal para registrar los tipos de QML. Este ejemplo muestra cómo enviar un datagrama de unicast como una cadena a 127.0.0.1:9999 . No olvide iniciar el socket antes de enviar cualquier mensaje.
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 ejemplo muestra cómo recibir el datagrama. No olvides comenzar a escuchar una dirección y un puerto. El datagrama siempre se recibe como una cadena. Se puede decodificar fácilmente para manipular una 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 )
}Envíe el trabajo de datagrama de multidifusión casi lo mismo que unicast. La única diferencia es que controla sobre qué interfaz van los datos.
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 ( )
}Luego envíe datos como en unicast:
socket . sendDatagram ( {
address : "239.1.2.3" ,
port : 9999 ,
data : "My Data"
} )Para recibirlo, suscribe el grupo de multidifusión y elige en qué 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 también proporciona un objeto de herramienta que demuestre cada funcionalidad QMLS. Esto está destinado a una depuración rápida o a las funcionalidades de prueba si la interfaz de usuario aún no está construida.
El gráfico de dependencias se puede generar con:
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 usa auto formateo para cpp , cmake . Los scripts de carpeta contienen script auxiliar. Se recomienda configurar el auto formato dentro de IDE.
cd scripts
./clangformat.sh
./cmakeformat.sh
? Netudp -> netudp? netudp -> netudp ➖ remove spdlog dependency in flavor of qCDebug/qCWarning ➕ Manage dependencies via CPM ♻️ Worker: interface -> iface to avoid conflict with MSVC # define interface struct https://stackoverflow.com/questions/25234203/what-is-the-interface-keyword-in-msvc ♻️ pimpl WorkerPrivate ♻️ PIMPL SocketPrivate ♻️ PIMPL RecycledDataGramPrivate? Hacer que recycler sea privado ya que todos los recicladores incluyen se movieron dentro de PIMPL ⚡️ netudp_enable_unity_build? Interfaceprovider: ¿Usar Steady_Clock en lugar del sistema para evitar la reversión? Imprimir el comando de compilación en el gráfico de actualización de cmake con dependencias
? Incluya el encabezado de Qelapsedtimer faltante en el trabajador
? Use un puntero RAW para el hilo de trabajadores y trabajadores. ? ️ Esto debería solucionar el problema cuando el puerto no se liberó por completo.
NETUDP_ENABLE_PCH , NETUDP_ENABLE_EXAMPLES , NETUDP_ENABLE_TESTSresize .multicastOutgoingInterfaces en lugar de multicastInterfaceName . Si multicastOutgoingInterfaces son los paquetes vacíos se enviarán en cada interfaces.multicastListenOnAllInterfaces y conviértalo en el valor predeterminado cuando multicastListeningInterfaces está vacío.