Agradeço a todos que dedicaram seu tempo para me ajudar com isso. Estou escrevendo um pequeno programa para operar uma aeronave multirrotor via mensagens mavlink. O drone, com seu piloto automático, transmite dados de telemetria via UDP e eu, como cliente, preciso ouvir suas mensagens e enviar algumas minhas. O problema é que, em cerca de metade das vezes, meu programa parece não receber nenhum pacote do drone. Tenho certeza de que o problema está no meu código, pois consigo ver todas as mensagens enviadas pelo piloto automático via UDP usando o Wireshark. Outros softwares funcionam sem problemas com o mesmo piloto automático no mesmo computador. O problema está em algum lugar do meu aplicativo.
Tenho uma classe BaseUDP que gerencia toda a comunicação UDP.
class UDPBase
{
public:
using IoContext = boost::asio::io_service; // backwards compatibility forces me to use an older boost
using UDP = boost::asio::ip::udp;
using Socket = UDP::socket;
using Endpoint = UDP::endpoint;
using Address = boost::asio::ip::address;
using ErrorCode = boost::system::error_code;
public:
UDPBase(IoContext &ioContext);
UDPBase(IoContext &ioContext, unsigned short int listenPort);
virtual ~UDPBase();
virtual void startListening();
void startIoContext();
void stopIoContext();
protected:
void init();
virtual void close();
void doListen();
virtual void onDataReceived(const ErrorCode &ec, unsigned char *buffer, size_t bufferSize);
private:
IoContext &ioContext;
Socket socket;
Endpoint listeningEndpoint;
Endpoint destinationEndpoint;
};
listeningEndpoint
está configurado com a porta em que eu escuto. Editei alguns setters e getters para reduzir o tamanho do código.
UDPBase::UDPBase(IoContext &ioContext) : ioContext(ioContext), socket(ioContext)
{
init();
}
UDPBase::UDPBase(IoContext &ioContext, unsigned short int listenPort) : ioContext(ioContext), socket(ioContext)
{
// set listenEndpoint based on the port
init();
}
UDPBase::~UDPBase()
{
// close();
}
void UDPBase::startListening(unsigned short int listenPort)
{
init();
doListen();
}
void UDPBase::close()
{
socket.close();
}
void UDPBase::startIoContext()
{
ioContext.run();
}
void UDPBase::stopIoContext()
{
ioContext.post([this]()
{
if (socket.is_open())
{
socket.close();
}
});
ioContext.stop();
}
void UDPBase::doListen()
{
socket.async_receive_from(
boost::asio::buffer(receiveBuffer, maxBufferSize), listeningEndpoint,
[this](ErrorCode ec, std::size_t bytesRecvd)
{
// std::cout << "Received from IP: " << listeningEndpoint.address().to_string() << " and Port: " << listeningEndpoint.port() << std::endl;
handleListen(ec);
onDataReceived(ec, receiveBuffer.data(), bytesRecvd);
doListen();
});
}
void UDPBase::onDataReceived(const ErrorCode &ec, unsigned char *buffer, size_t bufferSize)
{
if (ec)
{
std::cout << "! Receive error: " << ec.message() << std::endl;
return;
}
if (bufferSize)
{
// decode buffer
}
else
{
std::cout << "# No data received" << std::endl;
}
}
void UDPBase::init()
{
close();
socket.open(UDP::v4());
boost::asio::socket_base::reuse_address option(true);
socket.set_option(option);
boost::system::error_code ec;
socket.bind(listeningEndpoint, ec);
if (ec)
{
std::cerr << "Failed to bind socket: " << ec.message() << std::endl;
throw std::runtime_error("Socket bind failed");
}
}
Eu herdei UDPBase em outra classe que lida com a decodificação de mensagens e inicia um thread executando o ioContext.
void MavlinkWrapper::start()
{
mAppThread = std::thread([this]()
{ startIoContext(); });
}
void MavlinkWrapper::stop()
{
stopIoContext();
if (mAppThread.joinable())
{
mAppThread.join();
}
}
main.cpp
#include "MavlinkWrapper/MavlinkWrapper.hpp"
int main()
{
boost::asio::io_service ioContext;
MavlinkWrapper mavlinkWrapper(ioContext, 14550); // listen port is forwarded to UDPBase to bind the socket to it
mavlinkWrapper.start();
std::this_thread::sleep_for(std::chrono::seconds(20)); // wait for the messages that the autopilot broadcasts and print some data
mavlinkWrapper.stop();
return 0;
}
Suspeito que possa haver uma falha na maneira como fecho o socket ou entro na thread no final. Ou talvez eu não esteja interrompendo o ioContext corretamente e o socket esteja em um estado estranho. Estou usando o Windows 10.
Tornando seu código autocontido: https://coliru.stacked-crooked.com/a/d57170579882d1cf
Seu
startIoContext
pode chamar
run()
antes de qualquer trabalho ser publicado. Isso faz com que o tópico seja encerrado imediatamente. Então, vamos adicionarstartListening
ANTES do início do tópico:Embaralhar as coisas porque
listenPort
não está disponível lá.seus
async_receive_from
passeslisteningEndpoint
, que substituem isso...Mudar mais coisas faz com que funcione para mim:
Ao vivo no Coliru
Mas eu provavelmente simplificaria bastante, por exemplo, percebendo
io_service
que é thread-safe.Ao vivo no Coliru
Apenas metade do código e muito menos complicações.