繁体   English   中英

C ++ Boost ASIO async_send_to内存泄漏

[英]C++ Boost ASIO async_send_to memory leak

我目前正在研究UDP套接字客户端。 我目前注意到内存泄漏,我已经尝试了几个希望压制它的东西,但它仍然占上风。 在我的主要部分,我有一个char* ,一直是malloc 然后我调用下面的函数来发送数据:

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调用,则内存保持一致。 我已经尝试了几种变体(见下文),但它们都只是为了加速内存泄漏。

有几个注意事项,传递给Send的char*有可能在调用完成之前获得free 但是,在我的变化中,我已采取预防措施来处理这个问题。

变化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) ,但正如其他问题中所解释的那样,缓冲区不拥有内存,因此需要用户安全地管理它。 有关可能导致此问题的原因以及如何解决问题的任何想法?

编辑1:

正如评论中所建议的那样,我预先分配了一个缓冲区(用于测试目的)并且我永远不会释放它,但是,内存泄漏仍然存在。

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) {

    }
}

编辑2:这里要求的是问题的MCVE。 在做这个时,我也观察到了一个有趣的行为,我将在下面解释。

#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运行CentOS 6.6 with kernel Linux dev 2.6.32-504.el6.x86_64并运行Boost 1.55.0 我只是通过观察top的过程来观察这一点。

但是 ,如果我只是将MulticastSender的创建移动到while循环中,我就不再观察内存泄漏。 我担心应用程序的速度,所以这不是一个有效的选择。

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

  • io_service未运行,因为run()正在返回,因为没有工作。 这导致完成处理程序被分配,排队到io_service ,但既不执行也不释放。 此外,预计在完成处理程序中发生的任何清理都不会发生。 值得注意的是,在销毁io_service期间,完成处理程序将被销毁而不会被调用; 因此,不能仅依赖于在完成处理程序的执行中执行清理。 有关何时io_service::run()阻塞或取消阻止的更多详细信息,请考虑阅读问题。
  • streambuf的输入序列永远不会消耗掉。 主循环中的每次迭代将附加到streambuf ,然后streambuf将发送先前的消息内容和新附加的数据。 有关streambuf整体用法的更多详细信息,请参阅答案。

其他几点:

  • 该程序无法满足async_send_to()的要求,其中底层缓冲区内存的所有权由调用方保留,调用方必须保证它在调用处理程序之前保持有效。 在这种情况下,当通过ostreambuf_iterator复制到streambufstreambuf的输入序列被修改并使从streambuf.data()返回的缓冲区无效。
  • 在关闭期间,需要对运行io_service线程进行某种形式的同步。 否则,可能会调用未定义的行为。

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

  • 使用boost::asio::io_service::work确保当没有剩余工作时, io_service对象的run()不会退出。
  • 通过std::shared_ptr或将通过资源获取管理内存的另一个类将内存的所有权传递给完成处理程序是初始化 (RAII)习惯用法。 这将允许正确清理并满足async_send_to()的缓冲区有效性要求。
  • 不分离和加入工作线程。

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

#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

类变体看起来更好,你可以使用boost::asio::streambuf作为网络io的缓冲区(它不会泄漏,也不需要太多维护)。

// The send function
void
send(char const* data, size_t size)
{
    std::ostreambuf_iterator<char> out(&buffer_);
    std::copy(data, data + size, out);

    socket.async_send_to(buffer_, endpoint, 
            std::bind( &multicast_sender, 
                    this, std::placeholders::_1 ));
}

在类中移动套接字和端点是个好主意。 另外,您应该记住,当对象超出范围时,异步操作可以完成。 我会建议使用enable_shared_from_this (升压或std口味),并通过shared_from_this()而不是this来绑定功能。 整个解决方案看起来像这样:

#include <boost/asio.hpp>

class multicast_sender : 
        public std::enable_shared_from_this<multicast_sender> {
    using boost::asio::ip::udp;
    udp::socket socket_;
    udp::endpoint endpoint_;
    boost::asio::streambuf buffer_;
public:
    multicast_sender(boost::asio::io_service& io_service, short port, 
            udp::endpoint const& remote) : 
        socket_(io_service, udp::endpoint(udp::v4(), port)), 
        endpoint_(remote)
    {
    }
    void
    send(char const* data, size_t size)
    {
        std::ostreambuf_iterator<char> out(&buffer_);
        std::copy(data, data + size, out);

        socket_.async_send_to(buffer_, endpoint_, 
                std::bind( &multicast_sender, 
                        shared_from_this(), std::placeholders::_1 ));
    }

    void
    handle_send(boost::system::error_code const& ec)
    {
    }
};

编辑并且只要您不必在写入处理程序中执行任何操作,就可以使用lambda(需要C ++ 11)作为完成回调

// The send function
void
send(char const* data, size_t size)
{
    std::ostreambuf_iterator<char> out(&buffer_);
    std::copy(data, data + size, out);

    socket.async_send_to(buffer_, endpoint, 
            [](boost::system::error_code const& ec){
                std::cerr << "Error sending :" << ec.message() << "\n";
            });
}

暂无
暂无

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

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