muduo源碼學(xué)習(xí)(四) 實(shí)現(xiàn)TCP網(wǎng)絡(luò)庫(中)

runInLoop相關(guān)

在之前得文章中提到了EventLoop::runInLoop()赌渣,該函數(shù)用于在EventLoop的IO線程執(zhí)行某個(gè)用戶的任務(wù)回調(diào)肾胯,源碼如下:

void EventLoop::runInLoop(const Functor& cb)
{
    if (isInLoopThread()) { //判斷是否在當(dāng)前IO線程
        cb(); //同步調(diào)用
    } else {
        queueInLoop(cb); //加入隊(duì)列
    }
}

若用戶在其他線程調(diào)用runInLoop()岸霹,則執(zhí)行queueInLoop(),其實(shí)現(xiàn)如下:

void EventLoop::queueInLoop(const Functor& cb)
{
    {
    MutexLockGuard lock(mutex_);
    pendingFunctors_.push_back(cb);
    }

    if (!isInLoopThread() || callingPendingFunctors_) {
        wakeup();
    }
}

該函數(shù)將cb添加到隊(duì)列pendingFunctors_中大咱,并喚醒IO線程赌厅。

1. 隊(duì)列中的回調(diào)是如何觸發(fā)的
EventLoop::loop()的事件循環(huán)中咆霜,通過doPendingFunctors()來執(zhí)行隊(duì)列中的任務(wù)回調(diào)邓馒,實(shí)現(xiàn)如下:

void EventLoop::loop()
{
    //...
    while (!quit_) {
        //...
        doPendingFunctors();
    }
    //...
}

這里可能會(huì)有疑問,IO線程會(huì)阻塞在事件循環(huán)EventLoop::loop()poll調(diào)用中蛾坯,因此通過loop()來觸發(fā)用戶回調(diào)光酣,如果EventLoop中一直沒有事件觸發(fā),那poll會(huì)一直阻塞脉课,從而導(dǎo)致用戶回調(diào)一直無法執(zhí)行救军。
所以為了及時(shí)觸發(fā)用戶回調(diào)财异,我們需要去喚醒IO線程。

2. 喚醒的實(shí)現(xiàn)
書中提到唱遭,傳統(tǒng)的做法是使用pipe(2)戳寸,讓IO線程監(jiān)視此管道的可讀事件。在需要喚醒時(shí)胆萧,往管道寫入一個(gè)字節(jié)來觸發(fā)喚醒庆揩。
muduo中使用了eventfd(2)來實(shí)現(xiàn)IO線程的喚醒俐东。其不必管理緩沖區(qū)跌穗,可以更高效地喚醒。
EventLoop構(gòu)造時(shí)虏辫,創(chuàng)建eventfd并注冊可讀事件蚌吸,將事件分發(fā)至EventLoop::handleRead()
和傳統(tǒng)做法一樣砌庄,wakeup()的實(shí)現(xiàn)就是對(duì)eventfd進(jìn)行寫操作羹唠,從而觸發(fā)可讀事件達(dá)到喚醒IO線程的目的。

void EventLoop::wakeup()
{
    uint64_t one = 1;
    ssize_t n = sockets::write(wakeupFd_, &one, sizeof one);
    //...
}

3. doPendingFunctors的實(shí)現(xiàn)
上文提到在EventLoop::loop()事件循環(huán)最后娄昆,觸發(fā)隊(duì)列中的任務(wù)回調(diào)佩微,其實(shí)現(xiàn)如下:

void EventLoop::doPendingFunctors()
{
    std::vector<Functor> functors;
    callingPendingFunctors_ = true;

    {
    MutexLockGuard lock(mutex_);
    functors.swap(pendingFunctors_);
    }

    for (const Functor& functor : functors)
    {
        functor();
    }
    callingPendingFunctors_ = false;
}

這段代碼有兩處需要注意的:

  • 鎖的范圍
    doPendingFunctors()沒有直接在臨界區(qū)內(nèi)依次調(diào)用任務(wù)回調(diào),而且sawp()到局部變量中(減小了臨界區(qū)的長度)萌焰。否則哺眯,鎖會(huì)一直等到所有回調(diào)函數(shù)處理完才釋放,阻塞其他線程調(diào)用queueInLoop()扒俯。

  • callingPendingFunctors_
    代碼中使用callingPendingFunctors_來標(biāo)記是否在執(zhí)行doPendingFunctors()過程中奶卓,目的是為了queueInLoop()來判斷喚醒的時(shí)機(jī)。

4. 喚醒的時(shí)機(jī)
queueInLoop()中撼玄,是否需要weakup()進(jìn)行了這樣的判斷:

if (!isInLoopThread() || callingPendingFunctors_) {
    wakeup();
}

即夺姑,當(dāng)調(diào)用queueInLoop()的線程不是IO線程;以及在IO線程調(diào)用queueInLoop()掌猛,但此時(shí)正在執(zhí)行doPendingFunctors()過程中盏浙,才需要喚醒。
因?yàn)槔蟛纾?dāng)queueInLoop()在IO線程中調(diào)用废膘,doPendingFunctors()就會(huì)在EventLoop::loop()事件循環(huán)的最后被調(diào)用,所以此時(shí)無須喚醒兔院。

TcpServer

創(chuàng)建TcpServer對(duì)象殖卑,在其構(gòu)造時(shí)通過Acceptor來獲得新連接的fd。在Acceptor的構(gòu)造函數(shù)中調(diào)用了[1]socket()[2]bind()坊萝。
啟動(dòng)函數(shù)TcpServer::start()通過Acceptor::listen()(以runInLoop的方式)調(diào)用[3]listen()孵稽,并注冊了可讀事件许起。
當(dāng)新連接請求時(shí),觸發(fā)可讀事件菩鲜,在其回調(diào)函數(shù)Acceptor::handleRead()中調(diào)用了[4]accept()并回調(diào)了TcpServer::newConnection()园细,其實(shí)現(xiàn)如下:

void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
    loop_->assertInLoopThread();
    EventLoop* ioLoop = threadPool_->getNextLoop();
    char buf[64];
    snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);
    ++nextConnId_;
    string connName = name_ + buf;

    InetAddress localAddr(sockets::getLocalAddr(sockfd));
    TcpConnectionPtr conn(
        new TcpConnection(ioLoop, connName, sockfd, localAddr, peerAddr));
    connections_[connName] = conn;
    conn->setConnectionCallback(connectionCallback_);
    conn->setMessageCallback(messageCallback_);
    conn->setWriteCompleteCallback(writeCompleteCallback_);
    conn->setCloseCallback(std::bind(&TcpServer::removeConnection, this, _1));
    ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}

函數(shù)中創(chuàng)建了TcpConnection對(duì)象,把它加入ConnectionMap中管理并設(shè)置了相關(guān)回調(diào)接校。
注:以上的 [1] [2] [3] [4] 完成了一次完整的連接猛频。

TcpConnection

TcpConnection是muduo中唯一默認(rèn)使用智能指針管理的類,也是唯一繼承enable_shared_from_this的類蛛勉。有關(guān)enable_shared_from_this可以參考share_ptr相關(guān)鹿寻。
TcpConnection作用就是使用Channel來獲得socket上的IO事件,執(zhí)行各種回調(diào)诽凌。相關(guān)事件的處理毡熏,后面的文章再具體介紹。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侣诵,一起剝皮案震驚了整個(gè)濱河市痢法,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌杜顺,老刑警劉巖财搁,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異躬络,居然都是意外死亡尖奔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門洗鸵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來越锈,“玉大人,你說我怎么就攤上這事膘滨「势荆” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵火邓,是天一觀的道長丹弱。 經(jīng)常有香客問我,道長铲咨,這世上最難降的妖魔是什么躲胳? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮纤勒,結(jié)果婚禮上坯苹,老公的妹妹穿的比我還像新娘。我一直安慰自己摇天,他們只是感情好粹湃,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布恐仑。 她就那樣靜靜地躺著,像睡著了一般为鳄。 火紅的嫁衣襯著肌膚如雪裳仆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天孤钦,我揣著相機(jī)與錄音歧斟,去河邊找鬼。 笑死偏形,一個(gè)胖子當(dāng)著我的面吹牛静袖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播壳猜,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼勾徽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了统扳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤畅姊,失蹤者是張志新(化名)和其女友劉穎咒钟,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體若未,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡朱嘴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粗合。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片萍嬉。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖隙疚,靈堂內(nèi)的尸體忽然破棺而出壤追,到底是詐尸還是另有隱情,我是刑警寧澤供屉,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布行冰,位于F島的核電站,受9級(jí)特大地震影響伶丐,放射性物質(zhì)發(fā)生泄漏悼做。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一哗魂、第九天 我趴在偏房一處隱蔽的房頂上張望肛走。 院中可真熱鬧,春花似錦录别、人聲如沸朽色。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纵搁。三九已至吃衅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間腾誉,已是汗流浹背徘层。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留利职,地道東北人趣效。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像猪贪,于是被迫代替她去往敵國和親跷敬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348