NetUDP提供了一个可以发送和接收数据报的UDP套接字。
该库处于维护模式。这意味着只有关键的错误才能修复。不会添加新功能。如果您想接管此图书馆,请这样做。
该图书馆有一些巨大的设计缺陷,侧面的API和测试。这是我小时候经验的一部分,我从中学到了很多东西。我不想修复API,因为使用QT时,您不应该依靠第三方库,而应该简单地使用QUdpSocket 。
使您的依赖关系尽可能小,因此可维护性更容易。
开箱即用的两个主要类是Socket和RecycledDatagram 。只需创建服务器,启动它。然后发送并接收数据报。服务器可以加入多播组以接收多播数据包。
Socket使用可以在单独线程或主线程中运行的Worker 。
每个数据报分配都存储在std::shared_ptr<Datagram>中。这允许重复使用数据报对象结构以后已分配,而无需重新分配任何内容。
可以继承ISocket以表示Socket而无需任何功能。
可以继承Socket和Worker ,以实现服务器和工作人员之间的自定义通信。例如,发送可以在Worker线程中序列化/值序列化的自定义对象。
如果需要,可以继承Datagram如果需要自定义数据容器。例如,如果数据已经在结构中序列化。在Datagram中提到该结构的引用避免复制到RecycledDatagram 。
可以在examples/EchoClientServer.cpp中找到基本的客户端/套接字。
此示例演示了如何创建将数据报发送到端口9999上的127.0.0.1服务器。
# 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 ();
}数据报是从操作系统选择的随机端口发出的。可以通过调用
setTxPort(uint16_t)明确指定它。如果套接字还接收数据报(即
inputEnabled是True并调用setRxPort),则将使用RX端口。要更改此默认行为呼叫setSeparateRxTxSockets(true)。
此示例演示了如何在端口9999上的地址127.0.0.1上接收数据包。
# 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 ();
}可以通过socketError(int error, QString description)信号观察错误。如果插座无法绑定,或者发生任何事情,则工人将启动一个看门狗计时器以重新启动插座。
默认重新启动时间设置为5秒,但可以通过watchdogPeriod属性更改。该物业以毫秒为单位表示。
默认情况下,如果内部套接字与端口的接口有限,则该Worker将接收传入的数据报。要避免在Socket内接收这些数据报,请调用setInputEnabled(false) 。
multicastGroups是要侦听的多播地址的列表。加入多播组呼叫joinMulticastGroup(QString) , leaveMulticastGroup(QString) , leaveAllMulticastGroups 。multicastListeningInterfaces :设置插座正在收听multicastGroups的接口。默认情况下,所有接口都会侦听。使用joinMulticastInterface , leaveMulticastInterface , leaveAllMulticastInterfaces和isMulticastInterfacePresent 。multicastLoopback控制是否在系统中循环。在窗口上,应将其设置在接收器侧。在UNIX系统上,应将其设置在发件人端。 multicastOutgoingInterfaces :用于多播数据包的传出接口。如果未指定,则默认情况下,数据包将转到所有接口以提供插头和播放体验。在内部, Socket跟踪多个信息,以了解正在发生的事情。
isBounded表示当前是否将插座粘到网络接口。*xBytesPerSeconds是最后一秒钟收到/发送的所有字节的平均值。此值每秒更新一次。 * can be replaced by t and r*xBytesTotal字节已接收/已发送。 * can be replaced by t and r*xPacketsPerSeconds是最后一秒钟收到/发送的所有数据包的平均值。此值每秒更新一次。 * can be replaced by t and r*xPacketsTotal总数已接收/发送/发送数据包。 * can be replaced by t and r这些属性可以使用clearRxCounter / clearTxCounter / clearCounters清除。
当调用以下任何功能时, RecycledDatagram会发生一个memcpy 。
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 );为了避免使用无用的内存副本,建议使用makeDatagram(const size_t length)从Socket缓存中检索数据报。然后使用此netudp::SharedDatagram序列化数据。并致电:
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);如果您对Socket行为不满意,或者要模拟Socket而不依赖于QtNetwork 。可以ISocket使用其基本功能。
isRunning / isBounded 。您需要覆盖:
bool start() :启动插座。预计会自动重新启动以生存。不要忘记致电ISocket::start 。bool stop() :停止插座。清除所有运行任务,空缓存,缓冲区等...别忘了在开始时呼叫ISocket::stop 。为了确保最大的清洁,即使停止任何零件失败,也始终停止。joinMulticastGroup(const QString& groupAddress) :实现以加入多播组。不要忘记致电ISocket::joinMulticastGroup 。leaveMulticastGroup(const QString& groupAddress) :实现多播组。不要忘记致电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和Worker主要是配对,因此,如果覆盖一个,则覆盖另一个通常是有意义的。
覆盖Worker的原因:
Datagram报课覆盖Socket的原因
Worker类。Datagram报类。使用自定义Datagram可以根据您的应用程序减少内存副本。
Worker 。Socket::sendDatagram(SharedDatagram, ...) 。Socket在使用Socket::sendDatagram(const uint8_t*, ...)时使用它。会发生一个memcpy 。因此,不要将自定义Datagram用于此目的。 # 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; }
};从SocketWorker继承时,您可以覆盖:
bool isPacketValid(const uint8_t* buffer, const size_t length) const :每次收到数据报时都会调用。检查数据包是否有效,具体取决于您的协议。默认实现只需返回true即可。您可以添加CRC检查或类似的东西。在此处返回false将增加Socket中的rxInvalidPacketTotal计数器。void onReceivedDatagram(const SharedDatagram& datagram) :每次到达有效的数据报时都会打电话。默认实现EMIT receivedDatagram信号。覆盖此功能以添加自定义消息系统或自定义避难所。std::shared_ptr<Datagram> makeDatagram(const size_t length) :为rx创建自定义Datagram 。Worker中的自定义消息系统实现自定义序列化,请致电void onSendDatagram(const SharedDatagram& datagram)将数据报发送到网络。SocketWorker从QObject继承,因此请使用Q_OBJECT宏来生成自定义信号。例子:
# 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);
}
}自定义工人在单独的线程中运行时大多是有意义的。否则,它不会带来任何性能。不要忘记致电
Socket::setUseWorkerThread(true)。
从Socket继承时,您可以覆盖:
bool std::unique_ptr<SocketWorker> createWorker() const :创建一个自定义工人。void onDatagramReceived(const SharedDatagram& datagram) :在其中处理数据报。默认实现发射datagramReceived信号std::shared_ptr<Datagram> makeDatagram(const size_t length) :创建将在Socket::sendDatagram(const uint8_t*, ...)中使用的自定义Datagram 。Socket从QObject继承,因此请使用Q_OBJECT宏来生成自定义信号。例子:
# 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);
}
}此示例演示了服务器和客户端之间的回声。套接字将数据包发送到客户端,客户端回复同一数据包。 Ctrl+C退出。
$ > 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
> ...此示例也分为两个示例: NetUdp_EchoClient和NetUdp_EchoServer 。
演示如何加入多播IP组。发送一个数据包并通过环回读回去。
$ > 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();应在主机中调用以注册QML类型。此示例显示了如何将单播数据报作为字符串发送到127.0.0.1:9999 。在发送任何消息之前,不要忘记启动套接字。
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 ( )
}
}此示例显示了如何接收数据报。不要忘记开始聆听地址和端口。数据报总是以字符串接收。可以很容易地解码以操纵字节阵列。
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 )
}发送多播数据报工作与Unicast几乎相同。唯一的区别是,您可以控制数据进行哪个接口。
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 ( )
}然后像Unicast一样发送数据:
socket . sendDatagram ( {
address : "239.1.2.3" ,
port : 9999 ,
data : "My Data"
} )要接收它,请订阅多播组,然后选择哪些接口。
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 )
}该库还提供了一个工具对象,可以演示每个QMLS功能。如果尚未构建UI,则旨在快速调试或测试功能。
依赖项图可以生成:
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将自动形式用于cpp , cmake 。文件夹scripts包含辅助脚本。建议在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 ♻️PimplsocketPrivate♻️PimplrecycledDatagrampramprivate?将recycler私有化,因为所有回收器都包含在pimpl⚡️netudp_enable_unity_build中? InterfaceProvider:使用Steady_Clock代替系统来避免回滚?在cmake Update Readme的依赖项图中打印构建命令
?包括缺少QelapsedTimer标题
?使用原始指针进行工人和工人线程。 当端口未完全发布时,这应该解决问题。
NETUDP_ENABLE_PCH , NETUDP_ENABLE_EXAMPLES , NETUDP_ENABLE_TESTSresize方法调整数据报。multicastOutgoingInterfaces而不是multicastInterfaceName 。如果multicastOutgoingInterfaces是空数据包,则将在每个接口上发送。multicastListenOnAllInterfaces ,并在multicastListeningInterfaces为空时使其成为默认值。