muduo网络库学习笔记(13):TcpConnection生命期的管理
2016-11-13 22:07
239 查看
本篇通过分析muduo中TcpConnection对断开连接事件的处理,来学习muduo网络库对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。
Channel::handleEvent()事件处理完后就会调用functors(见博客“muduo网络库学习笔记(11):有用的runInLoop()函数”)即调用TcpConnection::connectDestroyed()。
使用示例:
测试结果如图:
所以,在TcpConnection的生命期管理过程中,如果我们直接用this指针传递对象,可能会构建一个新的shared_ptr对象,并不是直接将我们之前管理的对象的shared_ptr拷贝过去从而使引用计数加1,故我们需要用到boost::enable_shared_from_this的shared_from_this()函数。
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()函数。
相关文章推荐
- Muduo网络库源码分析(六)TcpConnection 的生存期管理
- TCP连接管理(TCP Connection Management)
- failed to open gcomm backend connection: 13: error while trying to listen 'tcp:/
- Muduo网络库源码分析(六)TcpConnection 的生存期管理
- 项目管理13禁忌
- A sample for TCP connection
- 跟我学管理(13)
- 到主机 的 TCP/IP 连接失败。 java.net.ConnectException: Connection refused: connect
- SCO TCP/IP 网络管理
- TCP Connection Established! ^_^
- 网络管理之TCP/UDP篇
- Internet 安全编程(信任管理器,HttpsURLConnection ...)
- Start with Database Connection Pool 连接管理
- com.microsoft.sqlserver.jdbc.SQLServerException: 到主机 的 TCP/IP 连接失败。 java.net.ConnectException: Connection timed out: connect数据库
- linux嵌入式编程高手历程系列13-安装缺陷管理工具bugzilla
- 使用pb的connection对象建立TCP连接
- 跟我学管理(13)
- 项目管理13禁忌
- 网络管理之TCP/UDP篇
- (13) 需求征集 -- 参数管理