boost::asio acceptor 避免内存泄漏

4

我将使用boost::asio来处理异步连接请求。这个方法很有效,但是有一个问题需要解决。使用典型的async_accept方法:

  Listener::Listener(int port)
        : acceptor(io, ip::tcp::endpoint(ip::tcp::v4(), port))
        , socket(io) {
          start_accept();
  }

  void Listener::start_accept() {
      Request *r = new Request(io);
      acceptor.async_accept(r->socket(), 
        boost::bind(&Listener::handle_accept, this, r, placeholders::error));
  }

代码能够正常运行,但存在一个问题:Request对象是用普通的new创建的,因此可能会出现内存“泄漏”。其实不是真正的泄漏,只有在程序停止时才会泄漏,但我希望让valgrind满意。

当然也有一种选择:我可以用shared_ptr替换它,并将其传递给每个事件处理程序。这种方法在程序停止时仍然有效,因为asio io_service停止时,所有对象都将被销毁并释放Request。但是,这样做我总是必须拥有一个活动的asio事件来处理Request,否则它将被销毁!我认为这是直接导致崩溃的方法,所以我也不喜欢这个变体。

第三种选择是:使用Listener保存活动连接的共享指针列表。看起来很好,我更愿意使用这个方案,除非找到更好的方法。缺点是:由于该方案允许在空闲连接上进行“垃圾收集”,因此不安全:从Listener中删除连接指针将立即销毁它,这可能导致其他线程中某些连接处理程序仍活动时发生段错误。使用互斥锁无法解决这个问题,因为在这种情况下我们几乎必须锁定任何东西。

有没有一种可以使acceptor与连接管理以更美观和安全的方式工作的方法?我很高兴听到任何建议。


1
这不是你的“Listener”类设计问题,或者使用指针而不是函数对象的“bind”策略问题吗?这与“acceptor”类无关吧? - user1084944
这不是一个函数对象=) 但无论如何,我不认为普通函数能解决这个问题。 - Galimov Albert
@PSIAlt,您能否详细说明为什么惯用的shared_ptr/enable_shared_from_this方法行不通?我不理解一个活动的asio事件的上下文。此外,如果Request没有创建自己的异步调用链,并将其寿命绑定到该链,则其他对象是否会维护对Request对象的句柄? - Tanner Sansbury
@twsansbury所说的“active asio event”,是指某些挂起的async_read/async_write或其他保持shared_ptr以保持连接对象活动的异步操作。至于“Request”对象存储——这是一种好的变体,但也有缺点;我已经将它添加到问题中。 - Galimov Albert
3个回答

2
使用该库时避免内存泄漏的典型方法是使用shared_ptrio_servicedocumentation特别提到了这一点。
引用如下:
备注
上述销毁顺序允许程序通过使用shared_ptr<>来简化资源管理。当一个对象的生命周期与连接(或其他一些异步操作序列)的生命周期相关联时,一个shared_ptr会被绑定到与它相关的所有异步操作的处理程序中。其工作原理如下:
当单个连接结束时,所有相关的异步操作都会完成。相应的处理程序对象被销毁,并且所有指向对象的shared_ptr引用也被销毁。要关闭整个程序,调用io_service函数stop()尽快终止任何run()调用。上面定义的io_service析构函数销毁所有处理程序,导致销毁所有连接对象的所有shared_ptr引用。
针对您的情况,将您的Listener :: handle_accept()方法更改为接受boost :: shared_ptr<Request>参数。您的第二个问题

从侦听器中删除连接指针会立即销毁它, 这可能会导致在其他线程中某些连接处理程序仍在活动时出现段错误。 使用互斥锁无法解决这个问题,因为在这种情况下我们必须 锁定几乎所有内容。

通过从您的类中继承boost :: enable_shared_from_this模板来缓解这个问题:
class Listener : public boost::enable_shared_from_this<Listener>
{
   ...
};

然后当您派发处理程序时,绑定到Listener的成员函数时,请使用shared_from_this()而不是this

正如我所提到的,在这种情况下,如果没有活动异步事件,连接就无法存在,这对我来说很糟糕,因为我必须处理其他异步连接(获取数据)以处理每个请求,所以当我使用连接获取数据时,它是空闲的,并且没有事件。 - Galimov Albert
@PSIAlt 我已经更新了我的答案,你需要使用 shared_from_this() - Sam Miller
谢谢帮助,我会尝试这个,看起来很合理。 - Galimov Albert

0

不确定这是否直接与您的问题相关,但我也使用了Boost Asio库,特别是您提到的相同acceptor对象,遇到了类似的内存泄漏问题。结果发现我没有正确关闭服务;一些连接会保持打开状态,它们对应的对象不会从内存中释放。调用以下内容可以消除Valgrind报告的泄漏:

acceptor.close();

希望这对某个人有用!


0

如果有人感兴趣,我找到了另一种方法。 Listener 持有活动连接的 shared_ptr 列表。通过 io_service::post 进行连接结束/终止,该调用使用 asio::strand 封装的 Listener::FinishConnection。通常我总是使用 strand 包装 Request 的方法 - 在 DDOS 和/或线程安全方面更安全。因此,使用 strandpost 调用 FinishConnection 可以保护其他线程中的 segfault。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接