<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

    TCP_NODELAY詳解

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀
    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    在網絡擁塞控制領域,我們知道有一個非常有名的算法叫做Nagle算法(Nagle algorithm),這是使用它的發明人John Nagle的名字來命名的,John Nagle在1984年首次用這個算法來嘗試解決福特汽車公司的網絡擁塞問題(RFC 896),該問題的具體描述是:如果我們的應用程序一次產生1個字節的數據,而這個1個字節數據又以網絡數據包的形式發送到遠端服務器,那么就很容易導致網絡由于太多的數據包而過載。比如,當用戶使用Telnet連接到遠程服務器時,每一次擊鍵操作就會產生1個字節數據,進而發送出去一個數據包,所以,在典型情況下,傳送一個只擁有1個字節有效數據的數據包,卻要發費40個字節長包頭(即ip頭20字節+tcp頭20字節)的額外開銷,這種有效載荷(payload)利用率極其低下的情況被統稱之為愚蠢窗口癥候群(Silly Window Syndrome)。可以看到,這種情況對于輕負載的網絡來說,可能還可以接受,但是對于重負載的網絡而言,就極有可能承載不了而輕易的發生擁塞癱瘓。
    針對上面提到的這個狀況,Nagle算法的改進在于:如果發送端欲多次發送包含少量字符的數據包(一般情況下,后面統一稱長度小于MSS的數據包為小包,與此相對,稱長度等于MSS的數據包為大包,為了某些對比說明,還有中包,即長度比小包長,但又不足一個MSS的包),則發送端會先將第一個小包發送出去,而將后面到達的少量字符數據都緩存起來而不立即發送,直到收到接收端對前一個數據包報文段的ACK確認、或當前字符屬于緊急數據,或者積攢到了一定數量的數據(比如緩存的字符數據已經達到數據包報文段的最大長度)等多種情況才將其組成一個較大的數據包發送出去,具體有哪些情況,我們來看看內核實現:
    1383:        Filename : \linux-3.4.4\net\ipv4\tcp_output.c
    1384:        /* Return 0, if packet can be sent now without violation Nagle's rules:
    1385:         * 1. It is full sized.
    1386:         * 2. Or it contains FIN. (already checked by caller)
    1387:         * 3. Or TCP_CORK is not set, and TCP_NODELAY is set.
    1388:         * 4. Or TCP_CORK is not set, and all sent packets are ACKed.
    1389:         *    With Minshall's modification: all sent small packets are ACKed.
    1390:         */
    1391:        static inline int tcp_nagle_check(const struct tcp_sock *tp,
    1392:                                          const struct sk_buff *skb,
    1393:                                          unsigned mss_now, int nonagle)
    1394:        {
    1395:                return skb->len < mss_now &&
    1396:                        ((nonagle & TCP_NAGLE_CORK) ||
    1397:                         (!nonagle && tp->packets_out && tcp_minshall_check(tp)));
    1398:        }
    1399:        
    1400:        /* Return non-zero if the Nagle test allows this packet to be
    1401:         * sent now.
    1402:         */
    1403:        static inline int tcp_nagle_test(const struct tcp_sock *tp, const struct sk_buff *skb,
    1404:                                         unsigned int cur_mss, int nonagle)
    1405:        {
    1406:                /* Nagle rule does not apply to frames, which sit in the middle of the
    1407:                 * write_queue (they have no chances to get new data).
    1408:                 *
    1409:                 * This is implemented in the callers, where they modify the 'nonagle'
    1410:                 * argument based upon the location of SKB in the send queue.
    1411:                 */
    1412:                if (nonagle & TCP_NAGLE_PUSH)
    1413:                        return 1;
    1414:        
    1415:                /* Don't use the nagle rule for urgent data (or for the final FIN).
    1416:                 * Nagle can be ignored during F-RTO too (see RFC413RFID設備管理軟件.
    1417:                 */
    1418:                if (tcp_urg_mode(tp) || (tp->frto_counter == 2) ||
    1419:                    (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN))
    1420:                        return 1;
    1421:        
    1422:                if (!tcp_nagle_check(tp, skb, cur_mss, nonagle))
    1423:                        return 1;
    1424:        
    1425:                return 0;
    1426:        }
    這一段Linux內核代碼非常容易看,因為注釋代碼足夠的多。從函數tcp_nagle_test()看起,第1412行是直接進行參數判斷,如果在外部(也就是調用者)主動設置了TCP_NAGLE_PUSH旗標,比如主動禁止Nagle算法或主動拔走塞子(下一節TCP_CORK內容)或明確是連接最后一個包(比如連接close()前發出的數據包),此時當然是返回1從而把數據包立即發送出去;第1418-1420行代碼處理的是特殊包,也就是緊急數據包、帶FIN旗標的結束包以及帶F-RTO旗標的包;第1422行進入到tcp_nagle_check()函數進行判斷,該函數的頭注釋有點混亂而不太清楚,我再逐句代碼解釋一下,首先要看明白如果該函數返回1,則表示該數據包不立即發送;再看具體實現就是:skb->len < mss_now為真表示如果包數據長度小于當前MSS;nonagle & TCP_NAGLE_CORK為真表示當前已主動加塞或明確標識立即還會有數據過來(內核表示為MSG_MORE);!nonagle為真表示啟用Nagle算法;tp->packets_out為真表示存在有發出去的數據包沒有被ACK確認;tcp_minshall_check(tp)是Nagle算法的改進,先直接認為它與前一個判斷相同,具體后續再講。把這些條件按與或組合起來就是:如果包數據長度小于當前MSS &&((加塞、有數據過來)||(啟用Nagle算法 && 存在有發出去的數據包沒有被ACK確認)),那么緩存數據而不立即發送。
    image002.jpg 
    上左圖(臺式主機圖樣為發送端,又叫客戶端,服務器主機圖樣為接收端,又叫服務器)是未開啟Nagle算法的情況,此時客戶端應用層下傳的數據包被立即發送到網絡上(暫不考慮發送窗口與接收窗口這些固有限制,下同),而不管該數據包的大小如何,因此在網絡里就有可能同時存在該連接的多個小包;而如上右圖所示上,在未收到服務器對第一個包的ACK確認之前,客戶端應用層下傳的數據包被緩存了起來,當收到ACK確認之后(圖中給的情況是這種,當然還有其他情況,前面已經詳細描述過)才發送出去,這樣不僅總包數由原來的3個變為2個,網絡負載降低,與此同時,客戶端和服務器都只需處理兩個包,消耗的CPU等資源也減少了。
    Nagle算法在一些場景下的確能提高網絡利用率、降低包處理(客戶端或服務器)主機資源消耗并且工作得很好,但是在某些場景下卻又弊大于利,要說清楚這個問題需要引入另一個概念,即延遲確認(Delayed ACK)。延遲確認是提高網絡利用率的另一種優化,但它針對的是ACK確認包。我們知道,對于TCP協議而言,正常情況下,接收端會對它收到的每一個數據包向發送端發出一個ACK確認包(如前面圖示那樣);而一種相對的優化就是把ACK延后處理,即ACK與數據包或窗口更新通知包等一起發送(文檔RFC 1122),當然這些數據包都是由接收端發送給發送端(接收端和發送端只是一個相對概念)的:
    image004.jpg 
    上左圖是一般情況,上右圖(這里只畫出了ACK延遲確認機制中的兩種情況:通過反向數據攜帶ACK和超時發送ACK)中,數據包A的ACK是通過接收端發回給發送端的數據包a攜帶一起過來的,而對應的數據包a的ACK是在等待超時之后再發送的。另外,雖然RFC 1122標準文檔上,超時時間最大值是500毫秒,但在實際實現中最大超時時間一般為200毫秒(并不是指每一次超時都要等待200毫秒,因為在收到數據時,定時器可能已經經歷一些時間了,在最壞情況的最大值也就是200毫秒,平均等待超時值為100毫秒),比如在linux3.4.4有個TCP_DELACK_MAX的宏標識該超時最大值:
    115:        Filename : \linux-3.4.4\include\net\tcp.h
    116:        #define TCP_DELACK_MAX        ((unsigned)(HZ/5))        /* maximal time to delay before sending an ACK */
    回過頭來看Nagle算法與ACK延遲確認的相互作用,仍然舉個例子來講,如果發送端暫有一段數據要發送給接收端,這段數據的長度不到最大兩個包,也就是說,根據Nagle算法,發送端發出去第一個數據包后,剩下的數據不足以組成一個可立即發送的數據包(即剩余數據長度沒有大于等于MSS),因此發送端就會等待,直到收到接收端對第一個數據包的ACK確認或者應用層傳下更多需要發送的數據等(這里暫只考慮第一個條件,即收到ACK);而在接收端,由于ACK延遲確認機制的作用,它不會立即發送ACK,而是等待,直到(具體情況請參考內核函數tcp_send_delayed_ack(),由于涉及到情況太過復雜,并且與當前內容關系不大,所以略過,我們僅根據RFC 1122來看):1,收到發送端的第二個大數據包;2,等待超時(比如,200毫秒)。當然,如果本身有反向數據包要發送,那么可以攜帶ACK,但是在最糟的情況下,最終的結果就是發送端的第二個數據包需要等待200毫秒才能被發送到網絡上。而在像HTTP這樣的應用里,某一時刻的數據基本是單向的,所以出現最糟情況的概率非常的大,而且第二個數據包往往用于標識這一個請求或響應的成功結束,如果請求和響應都要超時等待的話,那么時延就得增大400毫秒。
    針對在上面這種場景下Nagle算法缺點改進的詳細情況描述在文檔:http://tools.ietf.org/id/draft-minshall-nagle-01.txt里,在linux內核里也已經應用了這種改進,也就是前面未曾詳細講解的函數tcp_minshall_check():
    1376:        Filename : \linux-3.4.4\net\ipv4\tcp_output.c
    1377:        /* Minshall's variant of the Nagle send check. */
    1378:        static inline int tcp_minshall_check(const struct tcp_sock *tp)
    1379:        {
    1380:                return after(tp->snd_sml, tp->snd_una) &&
    1381:                        !after(tp->snd_sml, tp->snd_nxt);
    1382:        }
    函數名是按改進提出者的姓名來命名的,這個函數的實現很簡單,但要理解它必須先知道這些字段的含義(RFC 793、RFC 1122):tp->snd_nxt,下一個待發送的字節(序號,后同);tp->snd_una,下一個待確認的字節,如果它的值等于tp->snd_nxt,則表示所有已發數據都已經得到了確認;tp->snd_sml,已經發出去的最近的一個小包的最后一個字節(注意,不一定是已確認)。具體圖示如下:
    image006.jpg 
    總結前面所有介紹的內容,Minshall對Nagle算法所做的改進簡而言之就是一句話:在判斷當前包是否可發送時,只需檢查最近的一個小包是否已經確認(其它需要判斷的條件,比如包長度是否大于MSS等這些沒變,這里假定判斷到最后,由此處決定是否發送),如果是,即前面提到的tcp_minshall_check(tp)函數返回值為假,從而函數tcp_nagle_check()返回0,那么表示可以發送(前面圖示里的上圖),否則延遲等待(前面圖示里的下圖)。基于的原理很簡單,既然發送的小包都已經確認了,也就是說網絡上沒有當前連接的小包了,所以發送一個即便是比較小的數據包也無關大礙,同時更重要的是,這樣做的話,縮短了延遲,提高了帶寬利用率。
    那么對于前面那個例子,由于第一個數據包是大包,所以不管它所對應的ACK是否已經收到都不影響對是否發送第二個數據包所做的檢查與判斷,此時因為所有的小包都已經確認(其實是因為本身就沒有發送過小包),所以第二個包可以直接發送而無需等待。
    傳統Nagle算法可以看出是一種包-停-等協議,它在未收到前一個包的確認前不會發送第二個包,除非是“逼不得已”,而改進的Nagle算法是一種折中處理,如果未確認的不是小包,那么第二個包可以發送出去,但是它能保證在同一個RTT內,網絡上只有一個當前連接的小包(因為如果前一個小包未被確認,不會發出第二個小包);但是,改進的Nagle算法在某些特殊情況下反而會出現不利,比如下面這種情況(3個數據塊相繼到達,后面暫時也沒有其他數據到達),傳統Nagle算法只有一個小包,而改進的Nagle算法會產生2個小包(第二個小包是延遲等待超時產生),但這并沒有特別大的影響(所以說是它一種折中處理):
    image008.jpg 
    TCP中的Nagle算法默認是啟用的,但是它并不是適合任何情況,對于telnet或rlogin這樣的遠程登錄應用的確比較適合(原本就是為此而設計),但是在某些應用場景下我們卻又需要關閉它。在鏈接:http://www.isi.edu/lsam/publicat ... ractions/node2.html里提到Apache對HTTP持久連接(Keep-Alive,Prsistent-Connection)處理時凸現的奇數包&結束小包問題(The Odd/Short-Final-Segment Problem),這是一個并的關系,即問題是由于已有奇數個包發出,并且還有一個結束小包(在這里,結束小包并不是指帶FIN旗標的包,而是指一個HTTP請求或響應的結束包)等待發出而導致的。我們來看看具體的問題詳情,以3個包+1個結束小包為例,下圖是一種可能發生的發包情況:
    10.PNG 
    最后一個小包包含了整個響應數據的最后一些數據,所以它是結束小包,如果當前HTTP是非持久連接,那么在連接關閉時,最后這個小包會立即發送出去,這不會出現問題;但是,如果當前HTTP是持久連接(非pipelining處理,pipelining僅HTTP 1.1支持,并且目前有相當一部分陳舊但仍在廣泛使用中的瀏覽器版本尚不支持,nginx目前對pipelining的支持很弱,它必須是前一個請求完全處理完后才能處理后一個請求),即進行連續的Request/Response、Request/Response、…,處理,那么由于最后這個小包受到Nagle算法影響無法及時的發送出去(具體是由于客戶端在未結束上一個請求前不會發出新的request數據,導致無法攜帶ACK而延遲確認,進而導致服務器沒收到客戶端對上一個小包的的確認導致最后一個小包無法發送出來),導致第n次請求/響應未能結束,從而客戶端第n+1次的Request請求數據無法發出。
    image012.jpg 
    正是由于會有這個問題,所以遇到這種情況,nginx就會主動關閉Nagle算法,我們來看nginx代碼:
    2436:        Filename : \linux-3.4.4\net\ipv4\tcp_output.c
    2437:        static void
    2438:        ngx_http_set_keepalive(ngx_http_request_t *r)
    2439:        {
    2440:        …
    2623:            if (tcp_nodelay
    2624:                && clcf->tcp_nodelay
    2625:                && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET)
    2626:            {
    2627:                ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"RFID設備管理軟件;
    2628:        
    2629:                if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY,
    2630:                               (const void *) &tcp_nodelay, sizeof(int))
    2631:                    == -1)
    2632:                {
    2633:        …
    2646:                c->tcp_nodelay = NGX_TCP_NODELAY_SET;
    2647:            }
    Nginx執行到這個函數內部,就說明當前連接是持久連接。第2623行的局部變量tcp_nodelay是用于標記TCP_CORK選項的,由配置指令tcp_nopush指定,默認情況下為off,在linux下,nginx把TCP_NODELAY和TCP_CORK這兩個選項完全互斥使用(事實上它們可以一起使用,下一節詳細描述),禁用TCP_CORK選項時,局部變量tcp_nodelay值為1(從該變量可以看到,nginx對這兩個選項的使用,TCP_CORK優先級別高于TCP_NODELAY);clcf->tcp_nodelay對應TCP_NODELAY選項的配置指令tcp_nodelay的配置值,默認情況下為1;c->tcp_nodelay用于標記當前是否已經對該套接口設置了TCP_NODELAY選項,第一次執行到這里時,值一般情況下也就是NGX_TCP_NODELAY_UNSET(除非不是IP協議等),因為只有此處一個地方設置TCP_NODELAY選項。所以,整體來看,如果此判斷為真,于是第2629行對套接口設置TCP_NODELAY禁止Nagle算法(字段c->tcp_nodelay被賦值為NGX_TCP_NODELAY_SET,表示當前已經對該套接口設置了TCP_NODELAY選項),最后的響應數據會被立即發送出去,從而解決了前面提到的可能問題。

    http://lenky.info/ebook/

    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全