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()函式。
相關文章
- React生命週期學習筆記React筆記
- Vue學習筆記(2)—— Vue的生命週期Vue筆記
- muduo網路庫Timestamp類
- muduo網路庫Exception異常類Exception
- muduo網路庫編譯安裝編譯
- iOS初級開發學習筆記:APP生命週期的學習總結iOS筆記APP
- JavaScript學習筆記13JavaScript筆記
- Arduino學習筆記13UI筆記
- 網路流學習筆記筆記
- 【學習筆記】網路流筆記
- muduo網路庫AtomicIntegerT原子整數類
- JSP筆記-生命週期JS筆記
- react生命週期筆記React筆記
- CMake構建學習筆記13-opencv庫的構建筆記OpenCV
- 【三】Kubernetes學習筆記-Pod 生命週期與 Init C 介紹筆記
- <react學習筆記(8)>生命週期回顧與再認識React筆記
- Adaptive AUTOSAR 學習筆記 16 - 時間同步和網路管理APT筆記
- Mudo C++網路庫第七章學習筆記C++筆記
- Mudo C++網路庫第十一章學習筆記C++筆記
- Mudo C++網路庫第五章學習筆記C++筆記
- 學習筆記16:殘差網路筆記
- 學習筆記13:微調模型筆記模型
- rust學習十一.3、生命週期標記Rust
- Mudo C++網路庫第六章學習筆記C++筆記
- Mudo C++網路庫第八章學習筆記C++筆記
- Mudo C++網路庫第十章學習筆記C++筆記
- 學習vue生命週期Vue
- iOS學習筆記14 網路(三)WebViewiOS筆記WebView
- substrate學習筆記13:連線parachain筆記AI
- Flutter學習筆記(13)--表單元件Flutter筆記元件
- flutter學習日記(三)————Flutter的生命週期和路由Flutter路由
- 磁碟管理--學習筆記筆記
- docker筆記23-pod的生命週期Docker筆記
- Mpmath庫-學習筆記筆記
- 13、Linux網路管理Linux
- (長期更新)DP 學習筆記筆記
- 深度學習筆記------卷積神經網路深度學習筆記卷積神經網路
- 全連線神經網路學習筆記神經網路筆記
- 網路流最大流、最小割學習筆記筆記