muduo網路庫學習筆記(13):TcpConnection生命期的管理
本篇通過分析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()函式。
相關文章
- muduo網路庫學習筆記(12):TcpServer和TcpConnection類筆記TCPServer
- muduo網路庫學習之EventLoop(五):TcpConnection生存期管理(連線關閉)OOPTCP
- muduo網路庫學習筆記(1):Timestamp類筆記
- muduo網路庫學習筆記(2):原子性操作筆記
- muduo網路庫學習筆記(3):Thread類筆記thread
- muduo網路庫學習之EventLoop(六):TcpConnection::send()、shutdown()、handleRead()、handleWrite()OOPTCP
- muduo網路庫學習筆記(11):有用的runInLoop()函式筆記OOP函式
- muduo網路庫學習筆記(14):chargen服務示例筆記
- muduo網路庫學習筆記(10):定時器的實現筆記定時器
- muduo網路庫學習筆記(9):Reactor模式的關鍵結構筆記React模式
- muduo網路庫學習筆記(8):高效日誌類的封裝筆記封裝
- muduo網路庫學習筆記(5):執行緒池的實現筆記執行緒
- muduo網路庫學習筆記(7):執行緒特定資料筆記執行緒
- muduo網路庫學習筆記(6):單例類(執行緒安全的)筆記單例執行緒
- muduo網路庫學習筆記(4):互斥量和條件變數筆記變數
- muduo網路庫學習之EventLoop(三):Socket、Acceptor、TcpServer、TcpConnection(連線建立,接收訊息)OOPTCPServer
- React生命週期學習筆記React筆記
- muduo網路庫學習之muduo_http 庫涉及到的類HTTP
- muduo網路庫學習之muduo_inspect 庫涉及到的類
- muduo網路庫學習筆記(15):關於使用stdio和iostream的討論筆記iOS
- Vue學習筆記(2)—— Vue的生命週期Vue筆記
- Android學習筆記04——Activity的生命週期Android筆記
- ReactNative學習筆記五之生命週期React筆記
- muduo網路庫學習之EventLoop(七):TcpClient、ConnectorOOPTCPclient
- react-native學習筆記之 生命週期React筆記
- iOS初級開發學習筆記:APP生命週期的學習總結iOS筆記APP
- Arduino學習筆記13UI筆記
- JavaScript學習筆記13JavaScript筆記
- 【學習筆記】網路流筆記
- [網路]NIO學習筆記筆記
- Spring學習筆記二: Bean裝配及生命週期Spring筆記Bean
- muduo網路庫學習之EventLoop(四):EventLoopThread 類、EventLoopThreadPool 類OOPthread
- react生命週期筆記React筆記
- Fragment生命週期筆記Fragment筆記
- 信管筆記-- 生命週期筆記
- muduo網路庫Timestamp類
- muduo網路庫使用心得
- <react學習筆記(8)>生命週期回顧與再認識React筆記