简体   繁体   English

boost asio async_receive_from() 当帧连续发送时丢失 udp 帧数据检索

[英]boost asio async_receive_from() missing udp frames data retrieval when frames sent consecutive

The MVCE below simplified from real codebase shows the same issue.下面从真实代码库简化的 MVCE 显示了相同的问题。

The server continuously sends a "burst" of 5 UDP frames filled with 150 bytes of value 0xA5 with small or no delay in between.服务器连续发送 5 个 UDP 帧的“突发”,其中填充了 150 个字节的 0xA5 值,中间有很小的延迟或没有延迟。 A pause of 1 second is made.暂停 1 秒。

The client use the boost::asio async_receive_from() function in parallel of a 1 second timer.客户端使用boost::asio async_receive_from()函数与 1 秒计时器并行。 The client works relatively well except when the delay between the UDP frames is "too" small.客户端工作相对较好,除非 UDP 帧之间的延迟“太”小。 It seems that the correct size ( here 150 bytes ) is retrieved but the buffer/vector seems not to be updated.似乎检索到了正确的大小(此处为 150 字节),但缓冲区/向量似乎没有更新。

  • 5 x 150 bytes of UDP frames does not seem much. 5 x 150 字节的 UDP 帧似乎并不多。
  • Wireshark DOES see the complete and correct frames sent. Wireshark 确实看到发送的完整和正确的帧。
  • If I use a synchronous boost asio socket synchronous receive_from() I meet no issues如果我使用同步提升 asio 套接字同步 receive_from() 我不会遇到任何问题

I tried maybe half a dozen times to dive into boost asio without much success in finding a single truth or rationale.我尝试了大约六次来深入了解 boost asio,但在找到一个真理或基本原理方面没有取得多大成功。 Same posts on SO show very different code so that it is difficult to transpose them to the present code SO 上的相同帖子显示了非常不同的代码,因此很难将它们转换为当前代码

Here are the code client (client_with_timer.cc)这里是代码客户端(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;
}

Here is the server (server.cc) so that you do not have to roll your own这是服务器(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;
}

I compiled both with the naive commands below:我用下面的简单命令编译了两者:

g++ client_with_timer.cc -std=c++11 -O2 -Wall -o client_with_timer -lboost_system 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 server -lboost_system g++ server.cc -std=c++11 -O2 -Wall -o server -lboost_system

It produces output like below when delay is too small当延迟太小时,它会产生如下输出

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

How to correct the client code to avoid missed frames ?如何更正客户端代码以避免丢失帧? Any hint for a better understanding of boost asio rationale is welcome欢迎任何有关更好地理解 boost asio 原理的提示

I think in your code are data races.我认为在您的代码中是数据竞争。 If timer is expired (timeout occures) before read operation is completed below code is executed:如果在读取操作完成之前计时器已过期(发生超时),则执行以下代码:

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

you are breaking from while loop, socket.cancel() cancels asynchronus read operation, its handler with operation_aborted error is queued and waits for processing in event loop.您正在中断 while 循环, socket.cancel()取消异步读取操作,其带有operation_aborted错误的处理程序排队等待事件循环中的处理。 Because you jumped from while loop, run_one is not invoked, and this handler is still in queue.因为您是从 while 循环中跳转的, run_one不会调用run_one ,并且该处理程序仍在队列中。

io_service.reset() doesn't clear queue. io_service.reset()不清除队列。 Handler for aborted operation is still there.中止操作的处理程序仍然存在。 And waits to be invoked.并等待被调用。 reset() only sets stopped flag of io_service to false , then handlers can be processed by calls run_one , one .. methods, you are using reset to restore processing handlers from queue. reset()仅将io_service stopped标志设置为false ,然后可以通过调用run_one来处理处理程序, one .. 方法,您正在使用reset从队列中恢复处理处理程序。

So we have unprocessed handler in queue, in main while loop new vector vec is created, all its elements are initialized to 0. async_receive_from started (it is reading into vec and set ReceivedDataSize in its handler), then reset is called, run_one can process handler and invokes handler for aborted operation!所以我们在队列中有未处理的处理程序,在 main while 循环中创建了新的向量vec ,它的所有元素都被初始化为 0。 async_receive_from开始(它正在读入vec并在其处理程序中设置ReceivedDataSize ),然后调用resetrun_one可以处理处理程序并为中止的操作调用处理程序! and you are testing ReceivedDataSize and vec for aborted operation... but you should do it for the last started async operation.并且您正在测试ReceivedDataSizevec的中止操作......但您应该为上次启动的异步操作执行此操作。

I would rewrite clause with timeout to:我会用超时重写子句:

if ( timeout ) {
                socket.cancel();
                timer.cancel();
} // no break

after removing break we guarantee that aborted operation is processed by run_one and there is no outstanding handler to be invoked when new async operation is started.删除中断后,我们保证中止的操作由run_one处理,并且在启动新的异步操作时没有要调用的未完成处理程序。 After this modifcation I have not seen bad reception while testing your code.在此修改之后,我在测试您的代码时没有看到bad reception

EDIT编辑

Regarding your comment, yes the other break statement should also be removed from the code.关于您的评论,是的,还应从代码中删除其他break语句。

Output of the program is unpredictable because you are starting async operations which takes reference to local variable ( vec is modified by async_receive_from ), handler is queued, local variable is destroyed and later the handler is called from io_service while vec has been already destroyed.程序的输出是不可预测的,因为您正在启动引用局部变量的异步操作( vecasync_receive_from修改),处理程序排队,局部变量被销毁,稍后从 io_service 调用处理程序,而vec已经被销毁。

You can test the code below, and see what happens:你可以测试下面的代码,看看会发生什么:

  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

we are calling io_service::reset but all handlers are executed.我们正在调用io_service::reset但所有处理程序都被执行。 After removing break s from your code, you ensure that all handlers will be performed, and it guarantees that the local data are valid when these handlers are called.从代码中删除break后,确保所有处理程序都将被执行,并保证在调用这些处理程序时本地数据是有效的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM