• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            陳碩的Blog

            Muduo 網(wǎng)絡(luò)編程示例之八:用 Timing wheel 踢掉空閑連接

            Muduo 網(wǎng)絡(luò)編程示例之八:Timing wheel 踢掉空閑連接

            陳碩 (giantchen_AT_gmail)

            Blog.csdn.net/Solstice  t.sina.com.cn/giantchen

            這是《Muduo 網(wǎng)絡(luò)編程示例》系列的第八篇文章,原計劃講文件傳輸,這里插入一點計劃之外的內(nèi)容。

            Muduo 全系列文章列表: http://blog.csdn.net/Solstice/category/779646.aspx

            本文介紹如何使用 timing wheel 來踢掉空閑的連接,一個連接如果若干秒沒有收到數(shù)據(jù),就認(rèn)為是空閑連接。

            本文的代碼見 http://code.google.com/p/muduo/source/browse/trunk/examples/idleconnection

             

            在嚴(yán)肅的網(wǎng)絡(luò)程序中,應(yīng)用層的心跳協(xié)議是必不可少的。應(yīng)該用心跳消息來判斷對方進(jìn)程是否能正常工作,“踢掉空閑連接”只是一時權(quán)宜之計。我這里想順便講講 shared_ptr 和 weak_ptr 的用法。

            如果一個連接連續(xù)幾秒鐘(后文以 8s 為例)內(nèi)沒有收到數(shù)據(jù),就把它斷開,為此有兩種簡單粗暴的做法:

            • 每個連接保存“最后收到數(shù)據(jù)的時間 lastReceiveTime”,然后用一個定時器,每秒鐘遍歷一遍所有連接,斷開那些 (now - connection.lastReceiveTime) > 8s 的 connection。這種做法全局只有一個 repeated timer,不過每次 timeout 都要檢查全部連接,如果連接數(shù)目比較大(幾千上萬),這一步可能會比較費時。
            • 每個連接設(shè)置一個 one-shot timer,超時定為 8s,在超時的時候就斷開本連接。當(dāng)然,每次收到數(shù)據(jù)要去更新 timer。這種做法需要很多個 one-shot timer,會頻繁地更新 timers。如果連接數(shù)目比較大,可能對 reactor 的 timer queue 造成壓力。

            使用 timing wheel 能避免上述兩種做法的缺點。timing wheel 可以翻譯為“時間輪盤”或“刻度盤”,本文保留英文。

            連接超時不需要精確定時,只要大致 8 秒鐘超時斷開就行,多一秒少一秒關(guān)系不大。處理連接超時可以用一個簡單的數(shù)據(jù)結(jié)構(gòu):8 個桶組成的循環(huán)隊列。第一個桶放下一秒將要超時的連接,第二個放下 2 秒將要超時的連接。每個連接一收到數(shù)據(jù)就把自己放到第 8 個桶,然后在每秒鐘的 callback 里把第一個桶里的連接斷開,把這個空桶挪到隊尾。這樣大致可以做到 8 秒鐘沒有數(shù)據(jù)就超時斷開連接。更重要的是,每次不用檢查全部的 connection,只要檢查第一個桶里的 connections,相當(dāng)于把任務(wù)分散了。

            Timing wheel 原理

            《Hashed and hierarchical timing wheels: efficient data structures for implementing a timer facility》這篇論文詳細(xì)比較了實現(xiàn)定時器的各種數(shù)據(jù)結(jié)構(gòu),并提出了層次化的 timing wheel 與 hash timing wheel 等新結(jié)構(gòu)。針對本文要解決的問題的特點,我們不需要實現(xiàn)一個通用的定時器,只用實現(xiàn) simple timing wheel 即可。

            Simple timing wheel 的基本結(jié)構(gòu)是一個循環(huán)隊列,還有一個指向隊尾的指針 (tail),這個指針每秒鐘移動一格,就像鐘表上的時針,timing wheel 由此得名。

            以下是某一時刻 timing wheel 的狀態(tài),格子里的數(shù)字是倒計時(與通常的 timing wheel 相反),表示這個格子(桶子)中的連接的剩余壽命。

            wheel1

            一秒鐘以后,tail 指針移動一格,原來四點鐘方向的格子被清空,其中的連接已被斷開。

            wheel2

            連接超時被踢掉的過程

            假設(shè)在某個時刻,conn 1 到達(dá),把它放到當(dāng)前格子中,它的剩余壽命是 7 秒。此后 conn 1 上沒有收到數(shù)據(jù)。

            wheel3

            1 秒鐘之后,tail 指向下一個格子,conn 1 的剩余壽命是 6 秒。

            wheel4

            又過了幾秒鐘,tail 指向 conn 1 之前的那個格子,conn 1 即將被斷開。

            wheel5

            下一秒,tail 重新指向 conn 1 原來所在的格子,清空其中的數(shù)據(jù),斷開 conn 1 連接。

            wheel6

            連接刷新

            如果在斷開 conn 1 之前收到數(shù)據(jù),就把它移到當(dāng)前的格子里。

            wheel4

            收到數(shù)據(jù),conn 1 的壽命延長為 7 秒。

            wheel7

            時間繼續(xù)前進(jìn),conn 1 壽命遞減,不過它已經(jīng)比第一種情況長壽了。

            wheel8

            多個連接

            timing wheel 中的每個格子是個 hash set,可以容納不止一個連接。

            比如一開始,conn 1 到達(dá)。

            wheel3

            隨后,conn 2 到達(dá),這時候 tail 還沒有移動,兩個連接位于同一個格子中,具有相同的剩余壽命。(下圖中畫成鏈表,代碼中是哈希表。)

            wheel9

            幾秒鐘之后,conn 1 收到數(shù)據(jù),而 conn 2 一直沒有收到數(shù)據(jù),那么 conn 1 被移到當(dāng)前的格子中。這時 conn 1 的壽命比 conn 2 長。

            wheel10

            代碼實現(xiàn)與改進(jìn)

            我們用以前多次出現(xiàn)的 EchoServer 來說明具體如何實現(xiàn) timing wheel。代碼見 http://code.google.com/p/muduo/source/browse/trunk/examples/idleconnection

            在具體實現(xiàn)中,格子里放的不是連接,而是一個特制的 Entry struct,每個 Entry 包含 TcpConnection 的 weak_ptr。Entry 的析構(gòu)函數(shù)會判斷連接是否還存在(用 weak_ptr),如果還存在則斷開連接。

            數(shù)據(jù)結(jié)構(gòu):

              typedef boost::weak_ptr<muduo::net::TcpConnection> WeakTcpConnectionPtr;
            struct Entry : public muduo::copyable
            {
            Entry(const WeakTcpConnectionPtr& weakConn)
            : weakConn_(weakConn)
            {
            }
            ~Entry()
            {
            muduo::net::TcpConnectionPtr conn = weakConn_.lock();
            if (conn)
            {
            conn->shutdown();
            }
            }
            WeakTcpConnectionPtr weakConn_;
            };
            typedef boost::shared_ptr<Entry> EntryPtr;
            typedef boost::weak_ptr<Entry> WeakEntryPtr;
            typedef boost::unordered_set<EntryPtr> Bucket;
            typedef boost::circular_buffer<Bucket> WeakConnectionList;
            

            在實現(xiàn)中,為了簡單起見,我們不會真的把一個連接從一個格子移到另一個格子,而是采用引用計數(shù)的辦法,用 shared_ptr 來管理 Entry。如果從連接收到數(shù)據(jù),就把對應(yīng)的 EntryPtr 放到這個格子里,這樣它的引用計數(shù)就遞增了。當(dāng) Entry 的引用計數(shù)遞減到零,說明它沒有在任何一個格子里出現(xiàn),那么連接超時,Entry 的析構(gòu)函數(shù)會斷開連接。

            Timing wheel 用 boost::circular_buffer 實現(xiàn),其中每個 Bucket 元素是個 hash set of EntryPtr。

             

            在構(gòu)造函數(shù)中,注冊每秒鐘的回調(diào)(EventLoop::runEvery() 注冊 EchoServer::onTimer() ),然后把 timing wheel 設(shè)為適當(dāng)?shù)拇笮 ?/p>

            EchoServer::EchoServer(EventLoop* loop,
            const InetAddress& listenAddr,
            int idleSeconds)
            : loop_(loop),
            server_(loop, listenAddr, "EchoServer"),
            connectionBuckets_(idleSeconds)
            {
            server_.setConnectionCallback(
            boost::bind(&EchoServer::onConnection, this, _1));
            server_.setMessageCallback(
            boost::bind(&EchoServer::onMessage, this, _1, _2, _3));
            loop->runEvery(1.0, boost::bind(&EchoServer::onTimer, this));
            connectionBuckets_.resize(idleSeconds);
            }

            其中 EchoServer::onTimer() 的實現(xiàn)只有一行:往隊尾添加一個空的 Bucket,這樣 circular_buffer 會自動彈出隊首的 Bucket,并析構(gòu)之。在析構(gòu) Bucket 的時候,會依次析構(gòu)其中的 EntryPtr 對象,這樣 Entry 的引用計數(shù)就不用我們?nèi)ゲ傩模珻++ 的值語意會幫我們搞定一切。

            void EchoServer::onTimer()
            {
            connectionBuckets_.push_back(Bucket());
            }

            在連接建立時,創(chuàng)建一個 Entry 對象,把它放到 timing wheel 的隊尾。另外,我們還需要把 Entry 的弱引用保存到 TcpConnection 的 context 里,因為在收到數(shù)據(jù)的時候還要用到 Entry。(思考題:如果 TcpConnection::setContext 保存的是強(qiáng)引用 EntryPtr,會出現(xiàn)什么情況?)

            void EchoServer::onConnection(const TcpConnectionPtr& conn)
            {
            LOG_INFO << "EchoServer - " << conn->peerAddress().toHostPort() << " -> "
            << conn->localAddress().toHostPort() << " is "
            << (conn->connected() ? "UP" : "DOWN");
            if (conn->connected())
            {
            EntryPtr entry(new Entry(conn));
            connectionBuckets_.back().insert(entry);
            WeakEntryPtr weakEntry(entry);
            conn->setContext(weakEntry);
            }
            else
            {
            assert(!conn->getContext().empty());
            WeakEntryPtr weakEntry(boost::any_cast<WeakEntryPtr>(conn->getContext()));
            LOG_DEBUG << "Entry use_count = " << weakEntry.use_count();
            }
            }

            在收到消息時,從 TcpConnection 的 context 中取出 Entry 的弱引用,把它提升為強(qiáng)引用 EntryPtr,然后放到當(dāng)前的 timing wheel 隊尾。(思考題,為什么要把 Entry 作為 TcpConnection 的 context 保存,如果這里再創(chuàng)建一個新的 Entry 會有什么后果?)

            void EchoServer::onMessage(const TcpConnectionPtr& conn,
            Buffer* buf,
            Timestamp time)
            {
            string msg(buf->retrieveAsString());
            LOG_INFO << conn->name() << " echo " << msg.size() << " bytes at " << time.toString();
            conn->send(msg);
            assert(!conn->getContext().empty());
            WeakEntryPtr weakEntry(boost::any_cast<WeakEntryPtr>(conn->getContext()));
            EntryPtr entry(weakEntry.lock());
            if (entry)
            {
            connectionBuckets_.back().insert(entry);
            }
            }
            

            然后呢?沒有然后了,程序已經(jīng)完成了我們想要的功能。(完整的代碼會打印 circular_buffer 變化的情況,運行一下即可理解。)

            希望本文有助于您理解 shared_ptr 和 weak_ptr。

            改進(jìn)

            在現(xiàn)在的實現(xiàn)中,每次收到消息都會往隊尾添加 EntryPtr (當(dāng)然,hash set 會幫我們?nèi)ブ亍#┮粋€簡單的改進(jìn)措施是,在 TcpConnection 里保存“最后一次往隊尾添加引用時的 tail 位置”,然后先檢查 tail 是否變化,若無變化則不重復(fù)添加 EntryPtr。這樣或許能提高效率。

            以上改進(jìn)留作練習(xí)。

            posted on 2011-05-04 21:19 陳碩 閱讀(4074) 評論(5)  編輯 收藏 引用 所屬分類: muduo

            評論

            # re: Muduo 網(wǎng)絡(luò)編程示例之八:用 Timing wheel 踢掉空閑連接[未登錄] 2011-05-05 08:01 by

            Timing wheel 是個好東西。
              回復(fù)  更多評論   

            # re: Muduo 網(wǎng)絡(luò)編程示例之八:用 Timing wheel 踢掉空閑連接 2011-05-05 10:02 百思寒

            Timing wheel 是個好東西  回復(fù)  更多評論   

            # re: Muduo 網(wǎng)絡(luò)編程示例之八:用 Timing wheel 踢掉空閑連接[未登錄] 2011-05-05 15:57 jejer

            真是太有才了  回復(fù)  更多評論   

            # re: Muduo 網(wǎng)絡(luò)編程示例之八:用 Timing wheel 踢掉空閑連接 2011-05-14 11:21 kzjay

            在很多用況下超時時間都差不多,而且精確度要求低,俺一般會直接用最簡單的一兩條鏈表來實現(xiàn)定時器。
            TimingWhell或有序堆(NGINX)的共同點是超時時間可以隨意定義,而且實現(xiàn)起來比鏈表復(fù)雜。
            當(dāng)時給我掃盲的文章:http://www.ibm.com/developerworks/cn/linux/l-cn-timers/  回復(fù)  更多評論   

            # re: Muduo 網(wǎng)絡(luò)編程示例之八:用 Timing wheel 踢掉空閑連接[未登錄] 2012-12-21 17:15 春秋十二月

            看了代碼實現(xiàn),使用引用計數(shù)和unordered_set,這會造成每個桶內(nèi)都可能存在對某相同連接的entry對象,以致空間占用較大,但換來了時間上的效率,如果保存臨時tail,則是常數(shù)時間。如果不用這種方法,而采用連接從所在桶內(nèi)移到tail桶內(nèi),則至少是對數(shù)級的時間。  回復(fù)  更多評論   

            <2025年8月>
            272829303112
            3456789
            10111213141516
            17181920212223
            24252627282930
            31123456

            導(dǎo)航

            統(tǒng)計

            常用鏈接

            隨筆分類

            隨筆檔案

            相冊

            搜索

            最新評論

            閱讀排行榜

            評論排行榜

            国产精品99久久久精品无码| 东方aⅴ免费观看久久av| 久久精品国产免费一区| 久久亚洲综合色一区二区三区| 国产精品毛片久久久久久久| 久久精品国产精品亜洲毛片 | 久久99国产精品久久久 | 久久精品成人影院| 人妻少妇久久中文字幕| 国产精品欧美亚洲韩国日本久久| 思思久久99热只有频精品66| 久久人人妻人人爽人人爽| 久久e热在这里只有国产中文精品99| 亚洲伊人久久综合中文成人网| 久久综合给合久久狠狠狠97色| 久久久久亚洲精品无码网址| av午夜福利一片免费看久久| 久久人人爽人人人人爽AV| 久久精品无码一区二区三区免费| 久久精品毛片免费观看| 国产精品久久久久久久久软件| 久久这里只有精品首页| 日韩电影久久久被窝网| 久久精品中文騷妇女内射| 中文字幕久久精品| 久久久久99精品成人片三人毛片 | 亚洲欧美日韩精品久久| 无码国内精品久久人妻蜜桃| 亚洲Av无码国产情品久久| 久久国产福利免费| 精品久久久久久国产牛牛app| 91精品国产9l久久久久| 国产美女久久久| 国产精品久久久久影院嫩草| 99re这里只有精品热久久| 精品久久久久中文字幕日本| 久久精品欧美日韩精品| 国产精品一久久香蕉国产线看观看 | 久久夜色精品国产噜噜亚洲AV| 久久久久久国产精品美女| 久久婷婷国产剧情内射白浆|