当连续发送帧时,增强asio async_receive_from()缺少udp帧数据检索

时间:2019-02-05 21:13:52

标签: c++ boost-asio

下面从实际代码库中简化的MVCE显示了相同的问题。

服务器连续发送5个UDP帧的“突发”,这些帧填充150个字节的值0xA5,其间的延迟很小或没有延迟。暂停一秒钟。

客户端在1秒钟的计时器中并行使用 boost :: asio async_receive_from()函数。 客户端工作得比较好,除非UDP帧之间的延迟很小。似乎已检索到正确的大小(此处为150个字节),但缓冲区/向量似乎未更新。

  • 5 x 150字节的UDP帧似乎并不多。
  • Wireshark确实看到了发送的完整和正确的帧。
  • 如果我使用同步Boost asio套接字同步receive_from(),我不会遇到任何问题

我尝试了六次尝试进入boost asio,但未成功找到一个单一的真理或理由。 SO上的相同帖子显示了截然不同的代码,因此很难将它们转换为当前代码

这是代码 客户端(client_with_timer.cc)

#include <iostream>
#include <vector>
#include <string>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

using namespace boost::asio;
void asyncReadHandler( const boost::system::error_code& error, std::size_t bytesTransferred );
void timeoutHandler( const boost::system::error_code& error, bool* ptime_out );

size_t ReceivedDataSize;
std::string ReadError;

int main(int argc, char * argv[])
{
    io_service io;

    ip::udp::socket socket(io, ip::udp::endpoint(ip::udp::v4(), 1620));

    size_t num = 0;

    while (true)
    {
        std::vector<unsigned char> vec(1500);

        ip::udp::endpoint from;

        socket.async_receive_from(
                        boost::asio::buffer( vec ),
                        from,
                        boost::bind(
                                asyncReadHandler,
                                boost::asio::placeholders::error,
                                boost::asio::placeholders::bytes_transferred ) );

        bool timeout = false;
        ReceivedDataSize = 0;
        ReadError = "";

        // Creating and starting timer (by registering timeout handler)
        deadline_timer timer( io, boost::posix_time::seconds( 1 ) );
        timer.async_wait(
            boost::bind( timeoutHandler, boost::asio::placeholders::error, &timeout ) );

        // Resetting IO service instance
        io.reset();

        while(io.run_one())
        {
            if ( timeout ) {
                socket.cancel();
                timer.cancel();
                //Leave the io run_one loop
                break;
            }
            else if ( (0 != ReceivedDataSize ) || (!ReadError.empty())) {
                timer.cancel();
                socket.cancel();
                std::cout << "Received n°" <<  num++ << ": " << ReceivedDataSize << "\r" << std::flush;

                if (0 != ReceivedDataSize )
                    vec.resize(ReceivedDataSize);

                if (!ReadError.empty())
                    std::cout << "Error: " << ReadError << std::endl;

                bool result = true;
                for ( auto x : vec )
                    if ( 0xA5 != x ) { result = false; break; }

                if ( false == result ) {
                    std::cout << std::endl << "Bad reception" << std::endl << std::hex;
                    for ( auto x : vec )
                        std::cout << (int)x << " ";

                    std::cout << std::dec << "\n";
                }
                //Leave the io run_one loop
                break;
            }
            else {
                //What shall I do here ???
                //another potential io.reset () did not bring much
            }

        }
    }

    return 0;
}

void asyncReadHandler( const boost::system::error_code& error, std::size_t bytesTransferred )
{
    // If read canceled, simply returning...
    if( error == boost::asio::error::operation_aborted ) return;

    ReceivedDataSize = 0;

    // If no error
    if( !error ) {
        ReceivedDataSize = bytesTransferred;
    }
    else {
        ReadError = error.message();
    }
}

void timeoutHandler( const boost::system::error_code& error, bool* ptime_out )
{
    // If timer canceled, simply returning...
    if( error == boost::asio::error::operation_aborted ) return;

    // Setting timeout flag
    *ptime_out = true;
}

这里是服务器(server.cc),因此您不必自己动手

#include <iostream>
#include <vector>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <unistd.h>

using namespace boost::asio;

int main(int argc, char * argv[])
{
    io_service io;

    ip::udp::socket socket(io, ip::udp::endpoint(ip::udp::v4(), 0));

    std::vector<char> vec(150,0xA5);
#if 1
    int separator = 1 * 1000;
#else
    int separator = 0;
#endif

    while (true)
    {
        socket.send_to(buffer(vec), ip::udp::endpoint(ip::udp::v4(), 1620));
        if ( separator ) usleep(separator);
        socket.send_to(buffer(vec), ip::udp::endpoint(ip::udp::v4(), 1620));
        if ( separator ) usleep(separator);
        socket.send_to(buffer(vec), ip::udp::endpoint(ip::udp::v4(), 1620));
        if ( separator ) usleep(separator);
        socket.send_to(buffer(vec), ip::udp::endpoint(ip::udp::v4(), 1620));
        if ( separator ) usleep(separator);
        socket.send_to(buffer(vec), ip::udp::endpoint(ip::udp::v4(), 1620));

        usleep(1000*1000);
    }

    return 0;
}

我都使用以下朴素的命令进行了编译:

g ++ client_with_timer.cc -std = c ++ 11 -O2 -Wall -o client_with_timer -lboost_system

g ++ server.cc -std = c ++ 11 -O2 -Wall -o服务器-lboost_system

当延迟太小时会产生如下输出

nils@localhost ASIO_C]$ ./client_with_timer 
Received n°21: 150
Bad reception
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
Received n°148: 150
Bad reception
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
Received n°166: 150
Bad reception
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
Received n°194: 150

如何更正客户端代码以避免丢失帧? 欢迎提供任何进一步了解Boost Asio基本原理的提示

1 个答案:

答案 0 :(得分:1)

我认为您的代码中是数据竞赛。 如果在完成读取操作之前计时器到期(发生超时),则执行以下代码:

if ( timeout ) {
                socket.cancel();
                timer.cancel();
                //Leave the io run_one loop
                break; // [1]
            }

您正在从while循环中中断,socket.cancel()取消了异步读取操作,其错误为operation_aborted的处理程序已排队,并等待事件循环中的处理。由于您从while循环中跳出,因此run_one不会被调用,并且此处理程序仍在队列中。

io_service.reset()不会清除队列。用于中止操作的处理程序仍然存在。并等待被调用。 reset()仅将stopped的{​​{1}}标志设置为io_service,然后可以通过调用falserun_one ..方法处理处理程序。使用one从队列中恢复处理程序。

因此,我们在队列中有未处理的处理程序,主要是在创建新向量reset的循环时,其所有元素都初始化为0。vec开始运行(它正在读入async_receive_from并在其处理程序中设置vec,然后调用ReceivedDataSizereset可以处理处理程序并为中止的操作调用处理程序!并且您正在测试run_oneReceivedDataSize是否中止了操作...但是您应该对上次启动的异步操作进行此操作。

我将带有超时的子句重写为:

vec

除去中断后,我们保证if ( timeout ) { socket.cancel(); timer.cancel(); } // no break 处理已中止的操作,并且在启动新的异步操作时没有要调用的未处理程序。 进行此修改之后,在测试您的代码时我没有看到run_one

编辑

关于您的评论,是的,其他bad reception语句也应从代码中删除。

程序的输出是不可预测的,因为您正在启动异步操作,该操作将引用局部变量(breakvec修改),处理程序进入队列,局部变量被销毁,稍后处理程序被async_receive_from已被销毁时从io_service调用。

您可以测试以下代码,然后看看会发生什么:

vec

我们正在调用 boost::asio::io_context io; // alias on io_service boost::asio::system_timer t1{io}; t1.expires_from_now(std::chrono::seconds(1)); boost::asio::system_timer t2{io}; t2.expires_from_now(std::chrono::seconds(1)); boost::asio::system_timer t3{io}; t3.expires_from_now(std::chrono::seconds(1)); t1.async_wait ([](const boost::system::error_code& ec){ cout << "[1]" << endl;}); t2.async_wait ([](const boost::system::error_code& ec){ cout << "[2]" << endl;}); t3.async_wait ([](const boost::system::error_code& ec){ cout << "[3]" << endl;}); // 3 handlers are queueud cout << "num of handlers executed " << io.run_one() << endl; // wait for handler, print 1 io.reset(); // RESET is called cout << "num of handlers executed " << io.run_one() << endl; // wait for handler, print 1 io.reset(); // RESET is called cout << "num of handlers executed " << io.run_one() << endl; // wait for handler, print 1 cout << "executed: " << io.poll_one() << endl; // call handler if any ready, print 0 ,但所有处理程序均已执行。从代码中删除io_service::reset后,请确保将执行所有处理程序,并确保在调用这些处理程序时本地数据有效。