您的位置:首页 > 理论基础 > 计算机网络

muduo网络库学习笔记(13):TcpConnection生命期的管理

2016-11-13 22:07 239 查看
本篇通过分析muduo中TcpConnection对断开连接事件的处理,来学习muduo网络库对TcpConnection生命期的管理。

TcpConnection对连接断开事件的处理

首先,我们来看一下TcpConnection处理连接断开事件时函数调用的流程:



我们这里所指的连接断开,都是指被动关闭,即对方先关闭连接,本地read(2)返回0,触发关闭逻辑。

分析:一个服务器(TcpServer)维护了一个连接列表,当一个连接断开时,TcpConnection中的通道处于活跃的状态,EventLoop的事件循环返回了这个活跃的通道,然后调用通道的handleEvent()函数来处理。连接关闭是可读事件,进而回调了TcpConnection的handleRead()函数,handleRead()中又调用了read()返回为0,判断read()返回为0又会调用handleClose()函数。handleClose()函数会回调TcpServer的removeConnection()函数,其中会调用erase()将该连接从连接列表移除。

这里我们需要注意的是——一般情况下,将连接从连接列表移除后,我们就可以将这个连接对象销毁(delete)掉了,但是在这里我们不能立即销毁这个连接对象,原因如下:

如果我们销毁了这个对象,TcpConnection所包含的Channel对象也就跟着被销毁了,而我们当前正在调用Channel对象的handleEvent()函数,就会出现core dump。所以,我们必须保证TcpConnection的生存期长于Channel::handleEvent()函数。

muduo选择用智能指针shared_ptr来管理TcpConnection的生命期,并且让TcpConnection类继承自boost::enable_shared_from_this。

源码分析

具体代码改动如下:

代码片段1:Channel的改动
文件名:Channel.cc

// 析构函数中会判断Channel是否仍处于事件处理状态
// 在事件处理期间Channel对象不会析构
Channel::~Channel()
{
assert(!eventHandling_);
}

// 开始处理事件之前,会将事件处理标志位置为true
// 直到事件处理完,事件处理标志位再置为false
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
eventHandling_ = true;
if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
{
if (logHup_)
{
LOG_WARN << "Channel::handle_event() POLLHUP";
}
if (closeCallback_) closeCallback_();
}
if (revents_ & POLLNVAL)
{
LOG_WARN << "Channel::handle_event() POLLNVAL";
}
if (revents_ & (POLLERR | POLLNVAL))
{
if (errorCallback_) errorCallback_();
}
if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))
{
if (readCallback_) readCallback_(receiveTime);
}
if (revents_ & POLLOUT)
{
if (writeCallback_) writeCallback_();
}
eventHandling_ = false;
}


代码片段2:TcpConnection::handleRead()函数的改动
文件名:TcpConnection.cc

void TcpConnection::handleRead(Timestamp receiveTime)
{
loop_->assertInLoopThread();
int savedErrno = 0;
char buf[65536];
ssize_t n = ::read(channel_->fd(), buf, sizeof buf);
// 根据read(2)的返回值分别调用messageCallback_()、handleClose()和handleError()
if (n > 0)
{
messageCallback_(shared_from_this(), buf, n);
}
else if (n == 0)
{
handleClose();
}
else
{
errno = savedErrno;
LOG_SYSERR << "TcpConnection::handleRead";
handleError();
}
}


代码片段3:TcpConnection::handleClose()函数
文件名:TcpConnection.cc

void TcpConnection::handleClose()
{
loop_->assertInLoopThread();
LOG_TRACE << "fd = " << channel_->fd() << " state = " << state_;
// 断定此时连接处于已连接状态
assert(state_ == kConnected);
// we don't close fd, leave it to dtor, so we can find leaks easily.
channel_->disableAll();

// must be the last line
// 调用所设置的连接关闭回调函数
closeCallback_(shared_from_this());
}


代码片段4:TcpServer::removeConnection()函数
文件名:TcpServer.cc

void TcpServer::removeConnection(const TcpConnectionPtr& conn)
{
loop_->assertInLoopThread();
LOG_INFO << "TcpServer::removeConnectionInLoop [" << name_
<< "] - connection " << conn->name();
// 将断开的连接从连接列表中移除
size_t n = connections_.erase(conn->name());
(void)n;
assert(n == 1);
// 此处一定要用EventLoop::queueInLoop(),避免Channel对象被提前销毁
// 这里用boost::bind让TcpConnection的生命期长到调用connectDestroyed()的时刻
// 使用boost::bind得到一个boost::function对象,会把conn传递进去,引用计数会加1
loop_->queueInLoop(
boost::bind(&TcpConnection::connectDestroyed, conn));
}


Channel::handleEvent()事件处理完后就会调用functors(见博客“muduo网络库学习笔记(11):有用的runInLoop()函数”)即调用TcpConnection::connectDestroyed()。

代码片段5:TcpConnection::connectDestroyed()函数
文件名:TcpConnection.cc

// connectDestroyed()是TcpConnection析构前最后调用的一个成员函数
// 它通知用户连接已断开
void TcpConnection::connectDestroyed()
{
loop_->assertInLoopThread();
assert(state_ == kConnected);
setState(kDisconnected);
channel_->disableAll();
connectionCallback_(shared_from_this());  // 回调用户设定的回调函数
loop_->removeChannel(get_pointer(channel_));  // 从poll/epoll中移除channel
}


boost::enable_shared_from_this

由于TcpConnection模糊的生命期,我们用到了shared_ptr来管理它的生命期,并让TcpConnection类继承自boost::enable_shared_from_this。那么,boost::enable_shared_from_this的作用是什么呢?

使用示例:

#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
#include <cassert>

// class Y继承自boost::enable_shared_from_this<Y>
class Y: public boost::enable_shared_from_this<Y>
{
public:
boost::shared_ptr<Y> f()
{
return shared_from_this();  // 返回指向自身的shared_ptr
}

Y* f2()
{
return this;
}
};

int main()
{
boost::shared_ptr<Y> p(new Y);  // p的引用计数为1
boost::shared_ptr<Y> q = p->f();  // 将当前对象转换为一个shared_ptr,赋值给q,此时p/q引用计数为2

Y* r = p->f2();
assert(p == q);  // 断言正确
assert(p.get() == r);  // 断言正确

std::cout << p.use_count() << std::endl;  // 输出:2

// 构造一个shared_ptr对象s,将r赋给s
// 打印出的s的引用计数应该为1,而不是3
// 因为此时构造了一个新的、独立的shared_ptr对象,而不是将一个shared_ptr对象赋值给另一个shared_ptr对象
boost::shared_ptr<Y> s(r);
std::cout << s.use_count() << std::endl;
assert(p == s);  // 断言失败

return 0;
}


测试结果如图:



所以,在TcpConnection的生命期管理过程中,如果我们直接用this指针传递对象,可能会构建一个新的shared_ptr对象,并不是直接将我们之前管理的对象的shared_ptr拷贝过去从而使引用计数加1,故我们需要用到boost::enable_shared_from_this的shared_from_this()函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: