NetUDP จัดเตรียมซ็อกเก็ต UDP ที่สามารถส่งและรับดาตาแกรม
ห้องสมุดนี้อยู่ในโหมดการบำรุงรักษา หมายความว่ามีเพียงข้อบกพร่องที่สำคัญเท่านั้นที่จะได้รับการแก้ไข จะไม่มีการเพิ่มคุณสมบัติใหม่ หากคุณต้องการที่จะครอบครองห้องสมุดนี้โปรดทำเช่นนั้น
ห้องสมุดมีข้อบกพร่องในการออกแบบขนาดใหญ่ API และทดสอบ มันเป็นส่วนหนึ่งของประสบการณ์ของฉันเมื่อฉันยังเด็กและฉันได้เรียนรู้มากมายจากมัน ฉันไม่ต้องการแก้ไข API เพราะเมื่อใช้ QT คุณไม่ควรพึ่งพาไลบรารีของบุคคลที่สาม แต่เพียงแค่ใช้ QUdpSocket
ทำให้คุณมีขนาดเล็กที่สุดเท่าที่จะเป็นไปได้ดังนั้นการบำรุงรักษาจึงง่ายขึ้น
สองคลาสหลักที่ทำงานนอกกรอบคือ Socket และ RecycledDatagram Datatagram เพียงสร้างเซิร์ฟเวอร์เริ่มต้น จากนั้นส่งและรับดาตาแกรม เซิร์ฟเวอร์สามารถเข้าร่วมกลุ่มมัลติคาสต์เพื่อรับแพ็คเก็ตมัลติคาสต์
Socket ใช้ Worker ที่สามารถทำงานบนเธรดแยกต่างหากหรือในเธรดหลัก
การจัดสรรดาต้าแกรมทุกครั้งจะถูกเก็บไว้ใน std::shared_ptr<Datagram> สิ่งนี้อนุญาตให้นำโครงสร้างวัตถุ DataGram กลับมาใช้ใหม่ได้ในภายหลังโดยไม่ต้องจัดวางสิ่งใด
ISocket สามารถสืบทอดได้เพื่อเป็นตัวแทนของ Socket โดยไม่มีฟังก์ชั่นใด ๆ
Socket และ Worker สามารถสืบทอดเพื่อใช้การสื่อสารที่กำหนดเองระหว่างเซิร์ฟเวอร์และคนงาน ตัวอย่างเช่นการส่งวัตถุที่กำหนดเองที่สามารถเป็นอนุกรม/deserialized ในเธรดคนงาน
Datagram สามารถสืบทอดได้หากมีคอนเทนเนอร์ข้อมูลที่กำหนดเองหากจำเป็น ตัวอย่างเช่นหากข้อมูลได้รับการจัดลำดับในโครงสร้างแล้ว การอ้างอิงถึงโครงสร้างนั้นภายใน Datagram หลีกเลี่ยงการคัดลอกไปยัง RecycledDatagram
ไคลเอนต์/ซ็อกเก็ตพื้นฐานสามารถพบได้ใน examples/EchoClientServer.cpp
ตัวอย่างนี้แสดงวิธีสร้างเซิร์ฟเวอร์ที่ส่งดาต้าแกรมไปยังที่อยู่ 127.0.0.1 บนพอร์ต 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 ();
}ดาต้าแกรมถูกปล่อยออกมาจากพอร์ตสุ่มที่เลือกโดยระบบปฏิบัติการ สามารถระบุได้อย่างชัดเจนโดยการเรียกใช้
setTxPort(uint16_t)หากซ็อกเก็ตยังได้รับดาต้าแกรม (เช่น
inputEnabledที่เป็นจริงและเรียกsetRxPort) ดังนั้นพอร์ต RX จะใช้ หากต้องการเปลี่ยนพฤติกรรมเริ่มต้นการโทรเริ่มต้นนี้setSeparateRxTxSockets(true)
ตัวอย่างนี้แสดงให้เห็นถึงวิธีการรับแพ็กเก็ตตามที่อยู่ 127.0.0.1 บนพอร์ต 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 ();
} ข้อผิดพลาดสามารถสังเกตได้ผ่าน socketError(int error, QString description) หากซ็อกเก็ตล้มเหลวในการผูกหรือหากมีสิ่งใดเกิดขึ้นคนงานจะเริ่มจับเวลานาฬิกาจับเวลาเพื่อรีสตาร์ทซ็อกเก็ต
เวลารีสตาร์ทเริ่มต้นถูกตั้งค่าเป็น 5 วินาที แต่สามารถเปลี่ยนแปลงได้ผ่านคุณสมบัติ watchdogPeriod สถานที่ให้บริการแสดงเป็นมิลลิวินาที
โดยค่าเริ่มต้นหากซ็อกเก็ตภายในถูก จำกัด ไว้กับอินเทอร์เฟซกับพอร์ต Worker จะได้รับดาต้าแกรมขาเข้า เพื่อหลีกเลี่ยงการได้รับดาต้าแกรมภายใน Socket โทร setInputEnabled(false)
multicastGroups เป็นรายการของที่อยู่มัลติคาสต์ที่ฟัง leaveMulticastGroup(QString) การเข้าร่วมการโทรแบบหลายกลุ่มแบบ joinMulticastGroup(QString) leaveAllMulticastGroupsmulticastListeningInterfaces : ตั้งค่าส่วนต่อประสานที่ซ็อกเก็ตกำลังฟัง multicastGroups โดยค่าเริ่มต้นอินเทอร์เฟซทั้งหมดจะถูกฟัง ใช้ joinMulticastInterface , leaveMulticastInterface , leaveAllMulticastInterfaces และ isMulticastInterfacePresentmulticastLoopback หาก DataGram มัลติคาสต์วนลูปในระบบ บน Windows ควรตั้งค่าไว้ที่ด้านตัวรับสัญญาณ ในระบบ 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
เมื่อเรียกใช้ฟังก์ชั่นใด ๆ ต่อไปนี้ memcpy จะเกิดขึ้นกับ 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 ); เพื่อหลีกเลี่ยงการคัดลอกหน่วยความจำที่ไร้ประโยชน์แนะนำให้ดึงข้อมูลจาก Socket เก็ตแคชด้วย makeDatagram(const size_t length) จากนั้นใช้ 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::joinMulticastGroupleaveMulticastGroup(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 ที่กำหนดเองสามารถลดการคัดลอกหน่วยความจำได้ขึ้นอยู่กับแอปพลิเคชันของคุณ
WorkerSocket::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 : เรียกว่าทุกครั้งที่ได้รับดาตาแกรม ตรวจสอบว่าแพ็คเก็ตนั้นถูกต้องขึ้นอยู่กับโปรโตคอลของคุณหรือไม่ การใช้งานเริ่มต้นเพียงแค่ส่งคืนจริง คุณสามารถเพิ่มการตรวจสอบ CRC หรืออะไรทำนองนั้น การกลับมาเป็นเท็จที่นี่จะเพิ่มตัวนับ rxInvalidPacketTotal ใน Socketvoid onReceivedDatagram(const SharedDatagram& datagram) : เรียกว่าแต่ละครั้งที่ข้อมูลที่ถูกต้องมาถึง การใช้งานเริ่มต้นการใช้งาน EMIT receivedDatagram สัญญาณ DATAGRAM แทนที่ฟังก์ชั่นนี้เพื่อเพิ่มระบบการส่งข้อความที่กำหนดเองหรือ deserialization ที่กำหนดเองstd::shared_ptr<Datagram> makeDatagram(const size_t length) : สร้าง Datagram ที่กำหนดเองสำหรับ RxWorker ให้โทร void onSendDatagram(const SharedDatagram& datagram) เพื่อส่ง 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) : จัดการดาต้าแกรมในนั้น การใช้งานเริ่มต้นสัญญาณ datagramReceivedstd::shared_ptr<Datagram> makeDatagram(const size_t length) : สร้าง Datagram ที่กำหนดเองที่จะใช้ใน Socket::sendDatagram(const uint8_t*, ...)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
> ... ตัวอย่างนี้แบ่งออกเป็น 2 ตัวอย่าง: NetUdp_EchoClient & NetUdp_EchoServer
สาธิตวิธีการเข้าร่วมกลุ่ม IP แบบมัลติคาสต์ ส่งแพ็คเก็ตและอ่านกลับผ่าน 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(); ควรเรียกในหลักเพื่อลงทะเบียนประเภท QML ตัวอย่างนี้แสดงวิธีส่งดาต้าแกรม Unicast เป็นสตริงไปที่ 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 ➖ลบการพึ่งพา spdlog ในรสชาติของ qcdebug/qcwarning ➕จัดการการพึ่งพาผ่าน CPM ♻คนงาน: อินเตอร์เฟส-> iface เพื่อหลีกเลี่ยงความขัดแย้งกับ msvc # define struct struct https://stackoverflow.com/questions/25234203/ WorkerPrivate ♻ pimpl socketprivate ♻ pimpl recycledDatagramprivate? ทำให้ recycler เป็นส่วนตัวเนื่องจากผู้รีไซเคิลทั้งหมดรวมอยู่ใน PIMPL ⚡ netUDP_ENABLE_UNITY_BUILD? InterfaceProvider: ใช้ steady_clock แทนระบบเพื่อหลีกเลี่ยงการย้อนกลับ? พิมพ์คำสั่ง build ที่ cmake update readme ด้วยกราฟการพึ่งพา
- รวมส่วนหัว qelapsedtimer ที่หายไปในคนงาน
- ใช้ตัวชี้ดิบสำหรับเธรดคนงานและคนงาน ? ️สิ่งนี้ควรแก้ไขปัญหาเมื่อพอร์ตไม่ได้เปิดตัวอย่างสมบูรณ์
NETUDP_ENABLE_PCH , NETUDP_ENABLE_EXAMPLES , NETUDP_ENABLE_TESTSresizemulticastOutgoingInterfaces แทน multicastInterfaceName หาก multicastOutgoingInterfaces เป็นแพ็กเก็ตที่ว่างเปล่าจะถูกส่งไปยังทุกอินเตอร์เฟสmulticastListenOnAllInterfaces และทำให้เป็นค่าเริ่มต้นเมื่อ multicastListeningInterfaces ว่างเปล่า