C ++ Boost ASIO async_send_to内存泄漏 [英] C++ Boost ASIO async_send_to memory leak

查看:259
本文介绍了C ++ Boost ASIO async_send_to内存泄漏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在UDP套接字客户端上工作.我目前注意到内存泄漏,并且尝试了几种方法来缓解这种情况,但是这种情况仍然普遍存在.在我的主机上,我有一个char*,已经被malloc了.然后,我调用下面的函数来发送数据:

I am currently working on a UDP socket client. I am currently noticing a memory leak and I've tried several things in hopes to squash it, but it still prevails. In my main, I have a char* that has been malloc'd. I then call the below function to send the data:

void Send(const char* data, const int size) {
    Socket.async_send_to(boost::asio::buffer(data, size), Endpoint, boost::bind(&MulticastSender::HandleSendTo, this, boost::asio::placeholders::error));   
}

如果我运行此代码,它将始终泄漏内存.但是,如果我注释掉async_send_to调用,则内存保持一致. 我已经对此进行了多种尝试(请参见下文),但它们似乎只会加快内存泄漏的速度.

If I run this code, it will always leak memory. However, if I comment out the async_send_to call, the memory stays consistent. I have tried several variations(see below) on this, but they all only appear to speed up the memory leak.

请注意,在呼叫完成之前,传递给Send的char*可能会得到free.但是,在我的变体中,我已采取预防措施来处理此问题.

A couple notes, there is a chance that the char* that gets passed to Send may get free'd before the call completes. However, in my variations, I have taken precaution to do handle that.

变化1:

void Send(const char* data, const int size) {
    char* buf = (char*)malloc(size);
    memcpy(buf, data, size);
    Socket.async_send_to(boost::asio::buffer(buf, size), Endpoint, boost::bind(&MulticastSender::HandleSendTo, this, boost::asio::placeholders::error, buf));   
}

void HandleSendTo(const boost::system::error_code& ec, const char* buf) {
    free(buf);
}

变化2:

class MulticastSender {
    char* Buffer;

    public:
    void Send(const char* data, const int size) {
        Buffer = (char*)malloc(size);
        memcpy(Buffer, data, size);
        Socket.async_send_to(boost::asio::buffer(Buffer, size), Endpoint, boost::bind(&MulticastSender::HandleSendTo, this, boost::asio::placeholders::error));
    }

    void HandleSendTo(const boost::system::error_code& ec) {
        free(Buffer);
    }
}

但是,这两种变化似乎只会加速内存泄漏.我也尝试过删除async_send_to并仅调用boost::asio::buffer(data, size),但是正如其他问题所解释的那样,缓冲区不拥有内存,因此用户可以安全地管理它.关于什么可能导致此问题以及如何解决该问题的任何想法?

However, both variations seem to only speed up the memory leak. I have also tried removing the async_send_to and just calling boost::asio::buffer(data, size), but as has been explained in other questions, the buffer does not own the memory and thus it is up to the user to safely manage it. Any thoughts on what could be causing this issue and how to resolve it?

正如评论中所建议的,我已经预先分配了一个缓冲区(出于测试目的),并且我从未取消分配它,但是,内存泄漏仍然存在.

As suggested in the comments, I have preallocated a single buffer (for test purposes) and I am never deallocating it, however, the memory leak still persists.

class MulticastSender {
    char* Buffer;
    const int MaxSize = 16384;

    public:
    MulticastSender() {
        Buffer = (char*)malloc(MaxSize);
    }

    void Send(const char* data, const int size) {
        memcpy(Buffer, data, size);
        Socket.async_send_to(boost::asio::buffer(Buffer, size), Endpoint, boost::bind(&MulticastSender::HandleSendTo, this, boost::asio::placeholders::error));
    }

    void HandleSendTo(const boost::system::error_code& ec) {

    }
}

根据要求,这里是问题的MCVE.在进行此操作时,我还观察到了一种有趣的行为,下面将对此进行解释.

EDIT 2: As requested here is an MCVE of the problem. In making this I have also observed an interesting behavior that I will explain below.

#include <string>
#include <iostream>
#include <functional>
#include <thread>

#include <boost/asio.hpp>
#include <boost/bind.hpp>

class MulticastSender {
private:
    boost::asio::io_service IOService;
    const unsigned short Port;
    const boost::asio::ip::address Address;
    boost::asio::ip::udp::endpoint Endpoint;
    boost::asio::ip::udp::socket Socket;
    boost::asio::streambuf Buffer;

    void HandleSendTo(const boost::system::error_code& ec) {
        if(ec) {
            std::cerr << "Error writing data to socket: " << ec.message() << '\n';
        }
    }

    void Run() {
        IOService.run();
    }

public:
    MulticastSender(const std::string& address,
                    const std::string& multicastaddress,
                    const unsigned short port) : Address(boost::asio::ip::address::from_string(address)),
                                                 Port(port),
                                                 Endpoint(Address, port),
                                                 Socket(IOService, Endpoint.protocol()) {
        std::thread runthread(&MulticastSender::Run, this);
        runthread.detach();
    }

    void Send(const char* data, const int size) {
        std::ostreambuf_iterator<char> out(&Buffer);
        std::copy(data, data + size, out);
        Socket.async_send_to(Buffer.data(), Endpoint, boost::bind(&MulticastSender::HandleSendTo, this, boost::asio::placeholders::error));
    }
};

const int SIZE = 8192;

int main() {
    MulticastSender sender("127.0.0.1", "239.255.0.0", 30000);
    while(true) {
        char* data = (char*)malloc(SIZE);
        std::memset(data, 0, SIZE);
        sender.Send(data, SIZE);
        usleep(250);
        free(data);
    }
}

上面的代码仍然会产生内存泄漏.我应该提到我正在CentOS 6.6 with kernel Linux dev 2.6.32-504.el6.x86_64上运行此命令,并正在运行Boost 1.55.0.我只是通过观察top中的过程来观察到这一点.

The above code still produces a memory leak. I should mention that I am running this on CentOS 6.6 with kernel Linux dev 2.6.32-504.el6.x86_64 and running Boost 1.55.0. I am observing this simply by watching the process in top.

但是,如果我只是简单地将MulticastSender的创建移到了while循环中,就不再观察到内存泄漏.不过,我担心应用程序的速度,因此这不是有效的选择.

However, if I simply move the creation of the MulticastSender into the while loop, I no longer observe the memory leak. I am concerned about the speed of the application though, so this is not a valid option.

推荐答案

内存没有泄漏,因为仍然有分配的内存句柄.但是,将会持续增长,因为:

Memory is not leaking, as there is still a handle to the allocated memory. However, there will be continual growth because:

  • io_service未运行,因为问题.
  • streambuf的输入序列永远不会被消耗.主循环中的每个迭代将附加到streambuf,然后将发送先前的消息内容和新附加的数据.有关streambuf总体用法的更多详细信息,请参见答案.
  • The io_service is not running because run() is returning as there is no work. This results in completion handlers being allocated, queued into the io_service, but neither executed nor freed. Additionally, any cleanup that is expected to occur within the completion handler is not occurring. It is worth noting that during the destruction of the io_service, completion handlers will be destroyed and not invoked; hence, one cannot depend on only performing cleanup within the execution of the completion handler. For more details as to when io_service::run() blocks or unblocks, consider reading this question.
  • The streambuf's input sequence is never being consumed. Each iteration in the main loop will append to the streambuf, which will then send the prior message content and the newly appended data. See this answer for more details on the overall usage of streambuf.

其他几点:

  • 程序无法满足 async_send_to() ,其中调用者保留对底层缓冲存储器的所有权,调用者必须保证其保持有效,直到调用处理程序为止.在这种情况下,当通过ostreambuf_iterator复制到streambuf时,会修改streambuf的输入顺序,并使从
  • The program fails to meet a requirement of async_send_to(), where ownership of the underlying buffer memory is retained by the caller, who must guarantee that it remains valid until the handler is called. In this case, when copying into the streambuf via the ostreambuf_iterator, the streambuf's input sequence is modified and invalidates the buffer returned from streambuf.data().
  • During shutdown, some form of synchronization will need to occur against threads that are running the io_service. Otherwise, undefined behavior may be invoked.

要解决这些问题,请考虑:

To resolve these issues, consider:

  • 使用 boost::asio::io_service::work ,以确保在没有剩余工作时,io_service对象的run()不会退出.
  • 通过std::shared_ptr或将通过资源获取是初始化(RAII)惯用语.这样可以进行适当的清理,并满足async_send_to()的缓冲区有效性的要求.
  • 不分离并加入工作线程.
  • Using boost::asio::io_service::work to ensure that the io_service object's run() does not exit when there is no work remaining.
  • Passing ownership of the memory to the completion handler via std::shared_ptr or another class that will manage the memory via resource acquisition is initialization (RAII) idiom. This will allow for proper cleanup and meet the requirement's of the buffer validity for async_send_to().
  • Not detaching and joining upon the worker thread.

这是一个基于原始示例的完整示例,演示了这些更改:

Here is a complete example based on the original that demonstrates these changes:

#include <string>
#include <iostream>
#include <thread>
#include <boost/asio.hpp>

class multicast_sender
{
public:

  multicast_sender(
    const std::string& address,
    const std::string& multicast_address,
    const unsigned short multicast_port)
  : work_(io_service_),
    multicast_endpoint_(
      boost::asio::ip::address::from_string(multicast_address),
      multicast_port),
    socket_(io_service_, boost::asio::ip::udp::endpoint(
      boost::asio::ip::address::from_string(address),
      0 /* any port */))
  {
    // Start running the io_service.  The work_ object will keep
    // io_service::run() from returning even if there is no real work
    // queued into the io_service.
    auto self = this;
    work_thread_ = std::thread([self]()
      {
        self->io_service_.run();
      });
  }

  ~multicast_sender()
  {
    // Explicitly stop the io_service.  Queued handlers will not be ran.
    io_service_.stop();

    // Synchronize with the work thread.
    work_thread_.join();
  }

  void send(const char* data, const int size)
  {
    // Caller may delete before the async operation finishes, so copy the
    // buffer and associate it to the completion handler's lifetime.  Note
    // that the completion may not run in the event the io_servie is
    // destroyed, but the the completion handler will be, so managing via
    // a RAII object (std::shared_ptr) is ideal.
    auto buffer = std::make_shared<std::string>(data, size);
    socket_.async_send_to(boost::asio::buffer(*buffer), multicast_endpoint_,
      [buffer](
        const boost::system::error_code& error,
        std::size_t bytes_transferred)
      {
        std::cout << "Wrote " << bytes_transferred << " bytes with " <<
                     error.message() << std::endl;
      });
  }

private:
  boost::asio::io_service io_service_;
  boost::asio::io_service::work work_;
  boost::asio::ip::udp::endpoint multicast_endpoint_;
  boost::asio::ip::udp::socket socket_;
  std::thread work_thread_;

};

const int SIZE = 8192;

int main()
{
  multicast_sender sender("127.0.0.1", "239.255.0.0", 30000);
  char* data = (char*) malloc(SIZE);
  std::memset(data, 0, SIZE);
  sender.send(data, SIZE);
  free(data);

  // Give some time to allow for the async operation to complete 
  // before shutting down the io_service.
  std::this_thread::sleep_for(std::chrono::seconds(2));
}

输出:

Wrote 8192 bytes with Success

这篇关于C ++ Boost ASIO async_send_to内存泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆