jeudi 23 juin 2016

Asio (standalone) async_receive_from with std::bind behaving strangely

I have a UdpClient class written as a convenience wrapper around an Asio UDP socket, and in it I'm trying to use async_receive_from to push received messages onto a std::stack<std::string>.

The UdpClient.h header shown here:

#pragma once
#define ASIO_STANDALONE
#define ASIO_HAS_STD_ADDRESSOF
#define ASIO_HAS_STD_ARRAY
#define ASIO_HAS_CSTDINT
#define ASIO_HAS_STD_SHARED_PTR
#define ASIO_HAS_STD_TYPE_TRAITS
#define ASIO_HAS_STD_ATOMIC
#if !defined(WINVER) && defined(_WIN32)
#define WINVER 0x0A00
#define _WIN32_WINNT 0x0A00
#endif

#include <iostream>
#include <asio.hpp>
#include <array>
#include <functional>
#include <stack>
#include <mutex>

typedef uint8_t byte;
typedef unsigned short ushort;

using asio::ip::udp;

class UdpClient
{
private:
    udp::socket _sock;
    asio::error_code _err;
    std::array<char, 1024> inbuf;
    udp::endpoint _remote_end;

    std::stack<std::string> messages;
    std::mutex msg_mutex;

    /**
    * Handler, called when UDP data is received.
    *
    * @param    errcode The error code.
    * @param    len     The length of data received.
    */
    void rx_handler(const asio::error_code& errcode, std::size_t len);

public:

    /** Starts listening for connections. */
    void startListening();

    /**
    * Constructor.
    *
    * @param [in]   serv    The IO service to use.
    */
    UdpClient(asio::io_service& serv);

    /**
    * Writes the given message.
    *
    * @param    msg The message to write.
    */
    size_t write(std::string msg);

    /**
    * Writes a broadcast message.
    *
    * @param    msg     The message.
    * @param    port    The port on which the broadcast is sent.
    *
    * @return   The number of bytes transmitted.
    */
    size_t writeBroadcast(std::string msg, ushort port = 7777);

    /**
    * Connects to the specified endpoint (DCS).
    *
    * @param    ipstr   The IP address.
    * @param    port    The port to use.
    */
    void connect(std::string ipstr, unsigned short port = 7777);

    /**
    * Gets the topmost string on the message stack.
    *
    * @return   A std::string.
    */
    std::string read();

    /**
    * Gets the number of available messages.
    *
    * @return   An int.
    */
    int availableMessages();

    std::string errmsg() const;
};

UdpClient.cpp is this:

#if !defined(WINVER) && defined(_WIN32)
#define WINVER 0x0A00
#define _WIN32_WINNT 0x0A00 
#endif // _WIN32
#include "UdpClient.h"

void UdpClient::startListening()
{
    _sock.async_receive_from(asio::buffer(inbuf), _remote_end, std::bind(&UdpClient::rx_handler, this, std::placeholders::_1, std::placeholders::_2));
}

void UdpClient::rx_handler(const asio::error_code & errcode, std::size_t len)
{
    if (len == 0)
    {
        startListening();
        return;
    }

    msg_mutex.lock();
    messages.push(std::string(inbuf.data()));
    msg_mutex.unlock();

    startListening();
}

UdpClient::UdpClient(asio::io_service & serv) : _sock(serv)
{
    _sock.open(asio::ip::udp::v4(), _err);
    _sock.set_option(asio::ip::udp::socket::reuse_address(true));
    _sock.set_option(asio::ip::udp::socket::broadcast(true));

    startListening();
}

size_t UdpClient::write(std::string msg)
{
    return _sock.send(asio::buffer(msg.c_str(), msg.length()));
}

size_t UdpClient::writeBroadcast(std::string msg, ushort port)
{
    auto target = asio::ip::udp::endpoint(asio::ip::address_v4::broadcast(), port);
    //_sock.set_option(asio::socket_base::broadcast(true));

    return _sock.send_to(asio::buffer(msg.c_str(), msg.length()), target);
}

void UdpClient::connect(std::string ipstr, unsigned short port)
{
    auto ip = asio::ip::address_v4::from_string(ipstr);

    _sock.connect(asio::ip::udp::endpoint(ip, port));
}

std::string UdpClient::read()
{
    msg_mutex.lock();
    std::string temp;
    if (!messages.empty())
    {
        temp = messages.top();
        messages.pop();
    }
    msg_mutex.unlock();

    return temp;
}

int UdpClient::availableMessages()
{
    msg_mutex.lock();
    int ret = messages.size();
    msg_mutex.unlock();
    return ret;
}

std::string UdpClient::errmsg() const
{
    return _err.message();
}

I have a test program defined as such:

#include "stdafx.h"
#include "../DCS_100_Native/UdpClient.h"
#include "../DCS_100_Native/DCS100.h"
#include "asio.hpp"
#include <iostream>

int main()
{
    asio::io_service service;

    UdpClient client(service);
    service.run();

    auto N = client.writeBroadcast("*IDN?;");
    std::cout << "Sent " << N << " bytes." << std::endl;

    while (client.availableMessages() == 0);

    std::cout << "Got response: " << client.read() << std::endl;

    return 0;
}

The code above compiles with no errors, and I have confirmed that the UDP packet sent by the code is being sent, and that a response from a device I have is coming back. The rx_handler function is being called, but with no data in the buffer and a len of 0. I've also noticed that if I have another startListening() call in rx_handler, the handler gets called over and over (indefinitely) with no data and 0 length. I'm guessing that the issue lies somewhere with std::bind or possibly how/where I'm calling io_service.run.

What I'd like to happen is that rx_handler gets called with a non-zero len parameter and the inbuf gets the UDP response data put into it.

Aucun commentaire:

Enregistrer un commentaire