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為空時使其成為默認值。