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

前 言

上一篇文章介紹了連接的創(chuàng)建,引出了TcpConnection類齐唆。其作用就是處理socket上的IO事件遣铝,執(zhí)行各種回調(diào)。本文介紹TcpConnection對(duì)斷開連接忘闻、讀取數(shù)據(jù)钝计、發(fā)送數(shù)據(jù)的處理。

斷開連接

連接的關(guān)閉分為主動(dòng)斷開和被動(dòng)斷開齐佳,兩者的處理方式基本一致葵蒂。muduo采用的連接關(guān)閉方式:被動(dòng)斷開,其核心函數(shù)為TcpConnection::handleClose()重虑。書中提到践付,如果需要主動(dòng)斷開厦滤,添加一個(gè)接口調(diào)用handleClose()即可问词。
對(duì)于遠(yuǎn)端連接斷開的感知:在可讀事件處理函數(shù)handleRead()中,當(dāng)read返回值為0時(shí)垛孔,即遠(yuǎn)端斷開了連接,調(diào)用TcpConnection::handleClose()命爬。此時(shí)處理如下:
1. 取消所有關(guān)注的IO事件
2. 調(diào)用用戶注冊(cè)回調(diào)ConnectionCallback
3. 調(diào)用closeCallback_()曹傀,此回調(diào)綁定到TcpServer::removeConnection()
removeConnection()中處理如下:
4. 將對(duì)應(yīng)的TcpConnection對(duì)象從TcpServer中移除
5. 調(diào)用TcpConnection::connectDestroyed(),并通過std::bind()TcpConnection對(duì)象的生命周期延長(zhǎng)到執(zhí)行完成connectDestroyed()
6. 將連接對(duì)應(yīng)的ChannelEventLoop中移除
7. TcpConnection析構(gòu)饲宛,成員socket_引用計(jì)數(shù)為0皆愉,其析構(gòu)時(shí)會(huì)調(diào)用close(),關(guān)閉連接的fd

讀取數(shù)據(jù)

新連接建立時(shí)艇抠,通過TcpConnection::connectEstablished()注冊(cè)可讀事件幕庐,當(dāng)觸發(fā)可讀事件時(shí)調(diào)用回調(diào),即TcpConnection::handleRead()家淤,其主要內(nèi)容如下:

void TcpConnection::handleRead(Timestamp receiveTime) {
    ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
    if (n > 0) {
        messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
    } else if (n == 0) {
        handleClose();
    } else {
        handleError();
    }
}

這里主要進(jìn)行了兩個(gè)處理:
1. 讀取數(shù)據(jù)到inputBuffer_中异剥,其使用Buffer::readFd()來(lái)實(shí)現(xiàn),具體如下絮重。
2. 調(diào)用用戶回調(diào)messageCallback_冤寿,此函數(shù)是在建立新連接時(shí),將TcpServer的成員函數(shù)MessageCallback設(shè)置為回調(diào)青伤,其由用戶提供督怜。

Buffer::readFd()實(shí)現(xiàn)(非源碼)如下:

ssize_t Buffer::readFd(int fd, int & savedErrno) {
    // 申請(qǐng)棧上空間
    char extrabuf[65536];
    struct iovec vec[2];
    const size_t writable = writableBytes();

    // 兩塊iovec分別指向內(nèi)部buffer的可寫空間和棧上空間
    vec[0].iov_base = begin() + m_writerIndex;
    vec[0].iov_len = writable;
    vec[1].iov_base = extrabuf;
    vec[1].iov_len = sizeof extrabuf;

    // 判斷內(nèi)部buffer的可寫空間是否足夠
    const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1;
    const ssize_t n = sockets::readv(fd, vec, iovcnt);
    if (n < 0) {
        savedErrno = errno;
    } else if (static_cast<size_t>(n) <= writable) {
        // 內(nèi)部空間足夠,直接寫入狠角,移動(dòng)可寫索引
        m_writerIndex += n;
    } else {
        // 內(nèi)部空間不足号杠,先寫入棧上空間,再將棧上數(shù)據(jù)append到內(nèi)部空間
        m_writerIndex = m_buffer.size();
        append(extrabuf, n - writable);
    }
    return n;
}

書中提到擎厢,此實(shí)現(xiàn)一是使用了scatter/gather IO(分離/聚散IO)究流,配合內(nèi)部棧空間使用动遭;二是muduo采用level trigger(LT)模式芬探,只需要調(diào)用一次read(2)且不會(huì)丟失數(shù)據(jù)。從而兼顧了內(nèi)存使用量和效率厘惦。

發(fā)送數(shù)據(jù)

數(shù)據(jù)的發(fā)送通過TcpConnection::send()實(shí)現(xiàn)偷仿,代碼如下:

void TcpConnection::send(const StringPiece& message)
{
    if (state_ == kConnected) {
        if (loop_->isInLoopThread()) {
            sendInLoop(message);
        } else {
            loop_->runInLoop(std::bind(&TcpConnection::sendInLoop, this, message.as_string()));
        }
    }
}

在確保是連接狀態(tài)的情況下,如果在當(dāng)前IO線程觸發(fā)就調(diào)用TcpConnection::sendInLoop()宵蕉,反之則使用runInLoop將該任務(wù)拋給IO線程執(zhí)行酝静。有關(guān)runInLoop的內(nèi)容在上一篇已經(jīng)介紹過,這里不再贅述羡玛。
TcpConnection::sendInLoop()中别智,處理如下:
1.outputBuffer_為空,直接發(fā)送數(shù)據(jù)
2. 若發(fā)送數(shù)據(jù)沒有寫完稼稿,統(tǒng)計(jì)剩余的字節(jié)數(shù)薄榛,將剩余數(shù)據(jù)寫入outputBuffer_
3. 注冊(cè)可寫事件
當(dāng)socket可寫時(shí)讳窟,調(diào)用TcpConnection::handleWrite(),繼續(xù)發(fā)送outputBuffer_中的數(shù)據(jù)敞恋,一旦發(fā)送完成丽啡,立刻將可寫事件移除。
此流程需要注意的是可寫事件觀察的范圍硬猫,可以看出只有在outputBuffer_中有數(shù)據(jù)時(shí)补箍,才會(huì)注冊(cè)觀察可寫事件,因?yàn)楫?dāng)outputBuffer_中沒數(shù)據(jù)時(shí)啸蜜,此時(shí)socket一直是處于可寫狀態(tài)的坑雅, 這將會(huì)導(dǎo)致一直觸發(fā)TcpConnection::handleWrite(),而我們并沒有數(shù)據(jù)需要發(fā)送盔性。所以此觸發(fā)沒有意義霞丧,不需要去關(guān)注呢岗。

此外冕香,數(shù)據(jù)發(fā)送的流程中:
當(dāng)outputBuffer_中的舊數(shù)據(jù)字節(jié)和剩余數(shù)據(jù)字節(jié)之和大于highWaterMark_時(shí),會(huì)將highWaterMarkCallback_放入待執(zhí)行隊(duì)列中
當(dāng)數(shù)據(jù)發(fā)送完畢時(shí)后豫,會(huì)調(diào)用writeCompleteCallback_
兩者配合使用悉尾,可以起到限流的作用。

更多內(nèi)容挫酿,詳見github NetLib
參考:
《Linux多線程服務(wù)端編程》陳碩 著
muduo源碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末构眯,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子早龟,更是在濱河造成了極大的恐慌惫霸,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件葱弟,死亡現(xiàn)場(chǎng)離奇詭異壹店,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)芝加,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門硅卢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人藏杖,你說我怎么就攤上這事将塑。” “怎么了蝌麸?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵点寥,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我来吩,道長(zhǎng)敢辩,這世上最難降的妖魔是什么汉柒? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮责鳍,結(jié)果婚禮上碾褂,老公的妹妹穿的比我還像新娘。我一直安慰自己历葛,他們只是感情好正塌,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著恤溶,像睡著了一般乓诽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咒程,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天鸠天,我揣著相機(jī)與錄音,去河邊找鬼帐姻。 笑死稠集,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的饥瓷。 我是一名探鬼主播剥纷,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼呢铆!你這毒婦竟也來(lái)了晦鞋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤棺克,失蹤者是張志新(化名)和其女友劉穎悠垛,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體娜谊,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡确买,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了因俐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拇惋。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖抹剩,靈堂內(nèi)的尸體忽然破棺而出撑帖,到底是詐尸還是另有隱情,我是刑警寧澤澳眷,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布胡嘿,位于F島的核電站,受9級(jí)特大地震影響钳踊,放射性物質(zhì)發(fā)生泄漏衷敌。R本人自食惡果不足惜勿侯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缴罗。 院中可真熱鬧助琐,春花似錦、人聲如沸面氓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)舌界。三九已至掘譬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間呻拌,已是汗流浹背葱轩。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留藐握,地道東北人靴拱。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像趾娃,于是被迫代替她去往敵國(guó)和親缭嫡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子缔御,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容