多个背靠背boost :: asio async_send_to调用导致缓冲区溢出

时间:2016-03-26 03:29:25

标签: c++ asynchronous boost boost-asio

我遇到使用boost asio发送多个背靠背单独的UDP缓冲区的问题。我有一个1秒的asio计时器,它会触发一个回调,它通过udp传输2个独立的UDP数据报结构。这些消息结构中的每一个都是通过std :: unique_ptr分配的,因此在调用异步CADaemon :: handle_send回调时它们不应超出范围。

void
CADaemon::heartBeatTimer(
    const milliseconds& rHeartBeatMs)
{
    mpStatusTimer->expires_from_now(rHeartBeatMs);
    mpStatusTimer->async_wait(boost::bind(
        &CADaemon::heartBeatTimer,
        this, rHeartBeatMs));
    if (mpALBFSocket && mpALBFEndpoint) {
        mpALBFSocket->async_send_to(
            buffer(mpStatusMessage.get(),
                sizeof(MemberSystemStatusMessage)),
            *mpALBFEndpoint,
            boost::bind(&CADaemon::handle_send, this,
                boost::asio::placeholders::error,
                boost::asio::placeholders::bytes_transferred));


        // must insert delay to prevent buffer overwrites
        std::this_thread::sleep_for(std::chrono::milliseconds(10);

        // heartbeat messages are also sent to this socket/endpoint
        mpALBFSocket->async_send_to(
            buffer(mpHeartbeatMessage.get(),
                sizeof(CAServiceHeartbeatMessage)),
            *mpALBFEndpoint,
            boost::bind(&CADaemon::handle_send, this,
                boost::asio::placeholders::error,
                boost::asio::placeholders::bytes_transferred));
    }
}

如果我在发送第一条消息和第二条消息之间放了一小段延迟,接收应用程序就可以正常工作,但是,如果我按原样发送它们,看来第二条缓冲区在它到达时会覆盖第一条消息。收到申请。

我做错了什么?

我也尝试使用下面的代码发送多个缓冲区,但是由于它将两个数据报合并为一个长数据报,因此表现更差。

void
CADaemon::heartBeatTimer(
    const milliseconds& rHeartBeatMs)
{
    mpStatusTimer->expires_from_now(rHeartBeatMs);
    mpStatusTimer->async_wait(boost::bind(
        &CADaemon::heartBeatTimer,
        this, rHeartBeatMs));
    if (mpALBFSocket && mpALBFEndpoint) {
        std::vector<boost::asio::const_buffer> transmitBuffers;
        transmitBuffers.push_back(buffer(
            mpStatusMessage.get(), 
            sizeof(MemberSystemStatusMessage)));
        //transmitBuffers.push_back(buffer(
        //    mpHeartbeatMessage.get(), 
        //    sizeof(CAServiceHeartbeatMessage)));
        mpALBFSocket->async_send_to(
            transmitBuffers, *mpALBFEndpoint,
            boost::bind(&CADaemon::handle_send, this,
                boost::asio::placeholders::error,
                boost::asio::placeholders::bytes_transferred));
    }
}

以下是来自相关头文件的ASIO中涉及的类的成员。

// this message is transmitted @1HZ
std::unique_ptr<MemberSystemStatusMessage> mpStatusMessage;
// this message is transmitted @1HZ
std::unique_ptr<CAServiceHeartbeatMessage> mpHeartbeatMessage;
// this message is received @1HZ
std::unique_ptr<WOperationalSupportMessage> mpOpSupportMessage;
// this message is received @1HZ when valid
std::unique_ptr<MaintenanceOTPMessage> mpOTPMessage;

std::shared_ptr<boost::asio::io_service> mpIOService;
std::unique_ptr<boost::asio::ip::udp::socket> mpALBFSocket;
std::unique_ptr<boost::asio::ip::udp::endpoint> mpALBFEndpoint;
std::unique_ptr<boost::asio::ip::udp::socket> mpServerSocket;
std::unique_ptr<boost::asio::ip::udp::endpoint> mpServerEndpoint;
std::unique_ptr<boost::asio::steady_timer> mpStatusTimer;
std::unique_ptr<uint8_t[]> mpReceiveBuffer;

这是回调处理程序

void
CADaemon::handle_send(
    const boost::system::error_code& error,
    std::size_t bytes_transferred)
{
    static auto& gEvtLog = gpLogger->getLoggerRef(
        Logger::LogDest::EventLog);
    if (!error || (error == boost::asio::error::message_size)) {
        // Critical Section - exclusive write
        boost::unique_lock<boost::shared_mutex> uniqueLock(gRWMutexGuard);
        LOG_EVT_INFO(gEvtLog) << *mpStatusMessage;
        LOG_EVT_INFO(gEvtLog) << *mpHeartbeatMessage;
        LOG_EVT_INFO(gEvtLog) << "Sent " << bytes_transferred << " bytes";
        mpStatusMessage->incrementSequenceCounter();
    } else {
        LOG_EVT_ERROR(gEvtLog) << "handle_send: asio error code["
            << error.value() << "]";
    }
}

编辑:在缓冲区腐败中添加接收的JAVA应用程序代码

下面的代码显示了接收java应用程序中的代码,注意到接收到的数据报的大小永远不会被破坏,只是内容,大小似乎总是长数据报的大小。希望这有助于追踪问题。

    @Override
    protected Task<Void> createTask() {
        return new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                updateMessage("Running...");
                try {
                    DatagramSocket serverSocket = new DatagramSocket(mPortNum);
                    // allocate space for received datagrams
                    byte[] bytes = new byte[1024];
                    DatagramPacket packet = new DatagramPacket(bytes, bytes.length);                    
                    while (!isCancelled()) {                    
                        serverSocket.receive(packet);
                        int bytesReceived = packet.getLength();
                        MemberSystemStatusMessage statusMessage = 
                            new MemberSystemStatusMessage();
                        int statusMessageSize = statusMessage.size();
                        CAServiceHeartbeatMessage heartbeatMessage = 
                            new CAServiceHeartbeatMessage();
                        int heartbeatMessageSize = heartbeatMessage.size();
                        if (Platform.isFxApplicationThread()) {
                            if (bytesReceived == statusMessage.size()) {
                                statusMessage.setByteBuffer(ByteBuffer.wrap(bytes), 0);
                                setMemberSystemMessage(statusMessage);
                            } else if (bytesReceived == heartbeatMessage.size()){
                                heartbeatMessage.setByteBuffer(ByteBuffer.wrap(bytes), 0);
                                setHeartbeatMessage(heartbeatMessage);
                            } else {
                                System.out.println("unexpected datagram");
                            }
                        } else { // update later in FxApplicationThread
                            if (bytesReceived == statusMessage.size()) {
                                statusMessage.setByteBuffer(ByteBuffer.wrap(bytes), 0);
                                Platform.runLater(() -> setMemberSystemMessage(statusMessage));
                            } else if (bytesReceived == heartbeatMessage.size()){
                                heartbeatMessage.setByteBuffer(ByteBuffer.wrap(bytes), 0);
                                Platform.runLater(() -> setHeartbeatMessage(heartbeatMessage));
                            } else {
                                System.out.println("unexpected datagram");
                            }
                        }
                    }
                } catch (Exception ex) {
                    System.out.println(ex.getMessage());
                }
                updateMessage("Cancelled");
                return null;
            } 
        };
    }
}

2 个答案:

答案 0 :(得分:3)

只要缓冲区的大小正确并且缓冲区的底层内存在调用处理程序之前保持有效,代码就会很好。对于给定的I / O对象,可以安全地启动多个非组合异步操作,例如async_send_to()。虽然没有说明这些操作的执行顺序。

接收器应用程序有一个共享字节数组,其中读取数据报。如果接收到两个数据报,并且发生两个读操作,则缓冲区将包含最后读取数据报的内容。根据提供的代码,由于Runnable将在未来未指定的时间调用,因此可能会产生竞争条件。例如,考虑发送两个数据报的场景,第一个包含系统消息,第二个包含心跳消息。在以下代码中:

byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
while (...)
{
  serverSocket.receive(packet);
  int bytesReceived = packet.getLength();
  MemberSystemStatusMessage statusMessage =  ...;
  CAServiceHeartbeatMessage heartbeatMessage =  ...;
  if (bytesReceived == statusMessage.size())
  {
    statusMessage.setByteBuffer(ByteBuffer.wrap(bytes), 0);
    Platform.runLater(() -> setMemberSystemMessage(statusMessage));
  }
  ...
}

在while循环的第一次迭代之后,bytes包含状态消息,statusMessage对象引用bytes缓冲区。 Runnable已被安排在未来未指定的时间运行。在读取第二个数据报时,bytes缓冲区包含心跳消息。 Runnable现在运行,将statusMessage对象传递给setMemberSystemMessage();但是,它的底层缓冲区现在包含一个心跳消息。要解决此问题,请考虑在需要执行延迟执行时深度复制字节数组:

if (bytesReceived == statusMessage.size())
{
  byte[] bytes_copy = Arrays.copyOf(bytes, bytesReceived);
  statusMessage.setByteBuffer(ByteBuffer.wrap(bytes_copy), 0);
  Platform.runLater(() -> setMemberSystemMessage(statusMessage));
}

或者,可以为每个读取操作使用新缓冲区。

底层协议的期望可能也存在问题。 UDP被称为不可靠的协议,因为它不向发送方提供关于数据报传送的通知。每个async_send_to()操作将导致最多一个数据报被传输。完成处理程序的状态指示数据是否已写入,并且如果已收到数据报,则表示没有状态。即使通过scatter-gather I/O提供了多个缓冲区,也是如此。因此,协议允许问题中描述的场景,其中两个async_send_to()操作被启动,但接收者仅接收单个数据报。应用程序协议应考虑此行为。例如,不是在错过单个心跳截止期限之后报告错误,一旦连续数量的错过的心跳截止时间超过阈值,接收方可以报告错误。在写入之间添加一个小延迟不能保证协议的行为。

答案 1 :(得分:1)

更新

正如Tanner Sansbury所说,这个答案可能是错误的。我将其留在这里,以便人们搜索一个问题的答案,即同时拨打async_send_to多个来电是否有效。答案似乎是&#34;是&#34;。

原件:

此代码的问题在于,对async_send_to()的第二次调用不会等待第一次调用完成。假设没有错误,你应该从第一个完成处理程序第二次调用async_send_to()

在第二个示例中将两个缓冲区合并为一个数据报是预期的行为。