NetUDP предоставляет UDP -сокет, который может отправлять и получать Datagram.
Эта библиотека находится в режиме обслуживания. Это означает, что только критические ошибки будут исправлены. Новые функции не будут добавлены. Если вы хотите захватить эту библиотеку, пожалуйста, сделайте это.
В библиотеке есть несколько огромных дизайнерских недостатков, Flanky API и тестов. Это было частью моего опыта, когда я был молодым, и я многому научился. Я не хочу исправлять API, потому что при использовании QT вы не должны полагаться на стороннюю библиотеку, а просто использовать QUdpSocket .
Держите вас как можно меньшие зависимости, так что обслуживание легче.
Два основных класса, которые работают из коробки, - это Socket и RecycledDatagram . Просто создайте сервер, запустите его. Затем отправьте и получите Datagrams. Сервер может присоединиться к многоадресной группе для получения многоадресных пакетов.
В Socket используется Worker , который может работать в отдельном потоке или в основном потоке.
Каждое распределение данных хранится в std::shared_ptr<Datagram> . Это позволяет повторно использовать структуру объекта Datagram, уже распределенную позже, не перераспределяя что -либо.
ISocket может быть унаследован, чтобы представлять Socket без каких -либо функциональности.
Socket и Worker могут быть унаследованы для реализации пользовательской связи между сервером и работником. Например, отправка пользовательских объектов, которые могут быть сериализованы/десериализованы в рабочем потоке.
Datagram может быть унаследована, если необходимо пользовательский контейнер данных. Например, если данные уже сериализованы в структуре. Поместив ссылку на эту структуру внутри Datagram Избегайте копии в RecycledDatagram .
Основной клиент/сокет можно найти в examples/EchoClientServer.cpp .
Этот пример демонстрирует, как создать сервер, который отправляет Datagram для адреса 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).Если сокет также получает Datagram (т.е.
inputEnabledявляется true и callsetRxPort), то порт 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 - это список многоадресных адресов, которые слушают. Чтобы присоединиться к многоадресной группе Call joinMulticastGroup(QString) , leaveMulticastGroup(QString) , leaveAllMulticastGroups .multicastListeningInterfaces : Установите интерфейсы, на которых сокет слушает multicastGroups . По умолчанию все интерфейсы слушаются. Используйте joinMulticastInterface , leaveMulticastInterface , leaveAllMulticastInterfaces и isMulticastInterfacePresent .multicastLoopback Control, если в системе зацикливается на многоадресной данных. В окнах это должно быть установлено на стороне приемника. В системах UNIX он должен быть установлен на стороне отправителя. multicastOutgoingInterfaces : исходящие интерфейсы для многоадресного пакета. Если не указано, то Packet по умолчанию отправляется на все интерфейсы, чтобы обеспечить проблему и воспроизведение. Socket отслеживает множественную информацию, чтобы иметь представление о том, что происходит.
isBounded указывает, является ли сокет в настоящее время подключен к сетевому интерфейсу.*xBytesPerSeconds - это среднее значение всех байтов, полученных/отправленных в последнюю секунду. Это значение обновляется каждые секунды. * can be replaced by t and r*xBytesTotal total получен/отправленные байты с начала. * 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 ); Чтобы избежать бесполезной копии памяти, рекомендуется извлечь документ из кеша 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 at Begning. Чтобы обеспечить максимальную очистку, всегда останавливайте все, даже если остановка какой -либо части не удалась.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 здесь увеличит счетчик rxInvalidPacketTotal в Socket .void onReceivedDatagram(const SharedDatagram& datagram) : каждый раз вызывается действительная Datagram. Реализация по умолчанию Эмит сигнал receivedDatagram . Переопределите эту функцию, чтобы добавить пользовательскую систему обмена сообщениями или пользовательскую десериализацию.std::shared_ptr<Datagram> makeDatagram(const size_t length) : создать пользовательскую Datagram для Rx.Worker , вызовите 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. В этом примере показано, как отправить одноадресную дейтаграмму в качестве строки на 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 ( )
}
}В этом примере показано, как получить Datagram. Не забудьте начать слушать адрес и порт. DataGram всегда принимается как строка. Его можно легко декодировать, чтобы манипулировать байтовым массивом.
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 )
}Отправить многоадресную варианту DataGram Работа почти так же, как 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 ( )
}Затем отправьте данные, как в одноадресной лайке:
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. Это предназначено для быстрого отладка или тестирования функциональных возможностей, если пользовательский интерфейс еще не построен.
График зависимостей может быть получен с помощью:
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 Работник Сделать recycler private, так как все 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 пусты.