Netudp fournit une prise UDP qui peut envoyer et recevoir un datagramme.
Cette bibliothèque est en mode de maintenance. Cela signifie que seuls les bogues critiques seront corrigés. Aucune nouvelle fonctionnalité ne sera ajoutée. Si vous voulez reprendre cette bibliothèque, veuillez le faire.
La bibliothèque a un énorme défaut de conception, des API et des tests affreux. Cela faisait partie de mon expérience quand j'étais jeune, et j'en ai beaucoup appris. Je ne veux pas réparer l'API, car lorsque vous utilisez QT, vous ne devriez pas compter sur la bibliothèque tierce, mais utilisez plutôt simplement QUdpSocket .
Gardez vos dépendances aussi petites que possible, donc la maintenabilité est plus facile.
Les deux classes principales qui fonctionnent hors de la boîte sont Socket et RecycledDatagram . Créez simplement un serveur, démarrez-le. Ensuite, envoyez et recevez des datagrammes. Le serveur peut rejoindre un groupe de multidiffusion pour recevoir des paquets de multidiffusion.
Le Socket utilise un Worker qui peut fonctionner sur un thread séparé ou dans un fil principal.
Chaque allocation de datagram est stockée dans std::shared_ptr<Datagram> . Cela permet de réutiliser la structure d'objets Datagram déjà allouée plus tard sans rien réaffecter.
ISocket peut être hérité pour représenter une Socket sans aucune fonctionnalité.
Socket et Worker peuvent être hérités pour implémenter une communication personnalisée entre le serveur et le travailleur. Par exemple, envoyer des objets personnalisés qui peuvent être sérialisés / désérialisés dans le fil de travail.
Datagram peut être hérité si un conteneur de données personnalisé si nécessaire. Par exemple, si les données sont déjà sérialisées dans une structure. Mettre une référence à cette structure à l'intérieur du Datagram évite une copie dans RecycledDatagram .
Un client / socket de base peut être trouvé dans examples/EchoClientServer.cpp .
Cet exemple montre comment créer un serveur qui envoie un datagramme pour l'adresser 127.0.0.1 sur le port 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 ();
}Le datagramme est émis par un port aléatoire choisi par le système d'exploitation. Il peut être explicitement spécifié en appelant
setTxPort(uint16_t).Si le socket reçoit également Datagram (c'est-à-dire
inputEnabledest vrai et l'appelsetRxPort), le port RX utilisera. Pour modifier ce comportement par défaut,setSeparateRxTxSockets(true).
Cet exemple montre comment recevoir un paquet à l'adresse 127.0.0.1 sur le port 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 ();
} Des erreurs peuvent être observées via des signaux socketError(int error, QString description) . Si la prise ne se lie pas, ou si quelque chose s'est produit, le travailleur commencera une minuterie de chien de garde pour redémarrer la prise.
Le temps de redémarrage par défaut est défini sur 5 secondes mais peut être modifié via une propriété watchdogPeriod . La propriété est exprimée en millisecondes.
Par défaut, si la prise interne est délimitée à une interface avec un port, le Worker recevra un datagramme entrant. Pour éviter de recevoir ces datagram dans Socket , appelez setInputEnabled(false) .
multicastGroups est la liste des adresses multidiffurées qui sont écoutées. Pour rejoindre le groupe de multidiffusion, appelez joinMulticastGroup(QString) , leaveMulticastGroup(QString) , leaveAllMulticastGroups .multicastListeningInterfaces : Définissez les interfaces sur lesquelles le socket écoute des multicastGroups . Par défaut, toutes les interfaces sont écoutées. Utilisez joinMulticastInterface , leaveMulticastInterface , leaveAllMulticastInterfaces et isMulticastInterfacePresent .multicastLoopback si les datagrammes multidiffurés bouclent dans le système. Sur Windows, il doit être réglé du côté récepteur. Sur les systèmes UNIX, il doit être réglé du côté de l'expéditeur. multicastOutgoingInterfaces : interfaces sortantes pour le paquet de multidiffusion. S'il n'est pas spécifié, Packet va à toutes les interfaces par défaut pour fournir une expérience de fiche et de jeu. En interne, le Socket suit plusieurs informations pour avoir une idée de ce qui se passe.
isBounded indique si le socket est actuellement emballé à une interface réseau.*xBytesPerSeconds est une valeur moyenne de tous les octets reçus / envoyés dans la dernière seconde. Cette valeur est mise à jour toutes les secondes. * can be replaced by t and r*xBytesTotal total reçue / envoyée octets depuis commencer. * can be replaced by t and r*xPacketsPerSeconds est une valeur moyenne de tous les paquets reçus / envoyés dans la dernière seconde. Cette valeur est mise à jour toutes les secondes. * can be replaced by t and r*xPacketsTotal TOTAL RECOST / SEND PACKETS Depuis le début. * can be replaced by t and r Ces propriétés peuvent être effacées avec clearRxCounter / clearTxCounter / clearCounters .
Lors de l'appel de l'une des fonctions suivantes, un memcpy arrivera à 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 ); Pour éviter une copie de mémoire inutile, il est recommandé de récupérer un datagramme à partir du cache Socket avec makeDatagram(const size_t length) . Utilisez ensuite ce netudp::SharedDatagram pour sérialiser les données. Et appelez:
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 vous n'êtes pas satisfait par le comportement Socket , ou si vous voulez vous moquer Socket sans aucune dépendance à QtNetwork . Il est possible d'étendre ISocket pour utiliser ses fonctionnalités de base.
isRunning / isBounded .Vous devez remplacer:
bool start() : démarrez la prise. Auto redémarrer pour survivre d'une erreur est attendue. N'oubliez pas d'appeler ISocket::start au début.bool stop() : Arrêtez la prise. Effacer toute la tâche d'exécution, le cache vide, les tampons, etc ... n'oubliez pas d'appeler ISocket::stop au début. Pour assurer un nettoyage maximal, arrêtez-vous toujours, même si l'arrêt d'une pièce a échoué.joinMulticastGroup(const QString& groupAddress) : implémentation pour rejoindre un groupe de multidiffusion. N'oubliez pas d'appeler ISocket::joinMulticastGroup .leaveMulticastGroup(const QString& groupAddress) : implémentation pour quitter un groupe de multidiffusion. N'oubliez pas d'appeler 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 et Worker fonctionnent principalement en paire, donc si vous le dépassiez, il est souvent logique de remplacer l'autre.
Raisons de remplacer Worker :
Datagram personnalisée Raisons de remplacer Socket
Worker personnalisée.Datagram personnalisée. L'utilisation d'un Datagram personnalisé peut réduire la copie de mémoire en fonction de votre application.
Worker .Socket::sendDatagram(SharedDatagram, ...) avec.Socket pour l'utiliser lors de l'appel avec Socket::sendDatagram(const uint8_t*, ...) . Un memcpy se produira. N'utilisez donc pas un Datagram personnalisé à cette fin. # 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; }
}; Lorsque vous héritez de SocketWorker vous pouvez remplacer:
bool isPacketValid(const uint8_t* buffer, const size_t length) const : appelée chaque fois qu'un datagramme est reçu. Vérifiez si un paquet est valide en fonction de votre protocole. L'implémentation par défaut renvoie simplement vrai. Vous pouvez ajouter un chèque CRC ou quelque chose comme ça. Le rendement faux ici incrément le compteur rxInvalidPacketTotal dans Socket .void onReceivedDatagram(const SharedDatagram& datagram) : appelé chaque fois qu'un datagram valide arrive. L'implémentation par défaut émettra le signal d' receivedDatagram . Remplacez cette fonction pour ajouter un système de messagerie personnalisé ou une désérialisation personnalisée.std::shared_ptr<Datagram> makeDatagram(const size_t length) : créez Datagram personnalisé pour rx.Worker , appelez void onSendDatagram(const SharedDatagram& datagram) pour envoyer un datagramme au réseau.SocketWorker hérite de QObject , alors utilisez la macro Q_OBJECT pour générer des signaux personnalisés.Exemple:
# 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 personnalisation des travailleurs a principalement un sens lorsqu'il s'exécute dans un fil séparé. Sinon, cela ne donnera aucun coup de pouce. N'oubliez pas d'appeler
Socket::setUseWorkerThread(true).
Lorsque vous héritez de Socket , vous pouvez remplacer:
bool std::unique_ptr<SocketWorker> createWorker() const : créez un travailleur personnalisé.void onDatagramReceived(const SharedDatagram& datagram) : y gérer datagram. Implémentation par défaut émettre des signaux datagramReceivedstd::shared_ptr<Datagram> makeDatagram(const size_t length) : créer Datagram personnalisé qui sera utilisé dans Socket::sendDatagram(const uint8_t*, ...) .Socket hérite de QObject , alors utilisez la macro Q_OBJECT pour générer des signaux personnalisés.Exemple:
# 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);
}
} Cet exemple démontre un écho entre un serveur et un client. Socket Envoyez un paquet à un client, le client répond au même paquet. Ctrl+C pour quitter.
$ > 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
> ... Cet exemple se casse également en 2 exemples: NetUdp_EchoClient & NetUdp_EchoServer .
Démontrez comment rejoindre le groupe IP de multidiffusion. Envoyez un paquet et lisez-le 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(); doit être appelé dans l'essentiel pour enregistrer les types QML. Cet exemple montre comment envoyer un datagramme unicast en tant que chaîne à 127.0.0.1:9999 . N'oubliez pas de démarrer la prise avant d'envoyer des messages.
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 ( )
}
}Cet exemple montre comment recevoir le datagramme. N'oubliez pas de commencer à écouter une adresse et un port. Le datagramme est toujours reçu sous forme de chaîne. Il peut facilement être décodé pour manipuler un réseau d'octets.
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 )
}Envoyer un travail de datagramme de multidiffusion presque le même que Unicast. La seule différence est que vous contrôlez sur quelle interface les données vont.
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 ( )
}Ensuite, envoyez des données comme en unicast:
socket . sendDatagram ( {
address : "239.1.2.3" ,
port : 9999 ,
data : "My Data"
} )Pour le recevoir, abonnez-vous au groupe de multidiffusion et choisissez les 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 )
}Cette bibliothèque fournit également un objet d'outil qui démontre toutes les fonctionnalités QMLS. Ceci est destiné à un débogage rapide ou à tester les fonctionnalités si l'interface utilisateur n'est pas encore construite.
Le graphique des dépendances peut être généré avec:
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 utilise le format automatique pour cpp , cmake . Les scripts du dossier contient un script d'assistance. Il est recommandé de configurer le format automatique dans l'IDE.
cd scripts
./clangformat.sh
./cmakeformat.sh
? Netudp -> netudp? netudp -> netudp ➖ supprimer la dépendance SPDLOG dans la saveur de QCDebug / QCWarning ➕ Gérer les dépendances via CPM ♻️ Worker: Interface -> iface pour éviter les conflits avec MSVC # Définir l'interface Struct https://stackoverflow.com/questions/25234203 ♻️ Pimpl socketprivate ♻️ Pimpl recyclédatagramprivate? Faire recycler privé puisque tous les recycler incluent ont été déplacés à l'intérieur de pimpl ⚡️ netudp_enable_unity_build? InterfaceProvider: Utilisez stable_clock au lieu du système pour éviter le retour en arrière? Commande de construction d'impression au niveau de la mise à jour CMake Readme avec des dépendances graphique
? Incluez une en-tête QElapSedTimer manquante dans le travailleur
? Utilisez le pointeur brut pour le fil des travailleurs et les travailleurs. ? ️ Cela devrait résoudre le problème lorsque le port n'a pas été complètement libéré.
NETUDP_ENABLE_PCH , NETUDP_ENABLE_EXAMPLES , NETUDP_ENABLE_TESTSresize .multicastOutgoingInterfaces au lieu de multicastInterfaceName . Si multicastOutgoingInterfaces sont des paquets vides vont être envoyés sur toutes les interfaces.multicastListenOnAllInterfaces et faites-en la valeur par défaut lorsque multicastListeningInterfaces est vide.