<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    莊周夢蝶

    生活、程序、未來
       :: 首頁 ::  ::  :: 聚合  :: 管理

    網絡編程中Nagle算法和Delayed ACK的測試

    Posted on 2011-06-30 16:01 dennis 閱讀(9014) 評論(5)  編輯  收藏 所屬分類: unix網絡編程

        Nagle算法的立意是良好的,避免網絡中充塞小封包,提高網絡的利用率。但是當Nagle算法遇到delayed ACK悲劇就發生了。Delayed ACK的本意也是為了提高TCP性能,跟應答數據捎帶上ACK,同時避免糊涂窗口綜合癥,也可以一個ack確認多個段來節省開銷。
        悲劇發生在這種情況,假設一端發送數據并等待另一端應答,協議上分為頭部和數據,發送的時候不幸地選擇了write-write,然后再read,也就是先發送頭部,再發送數據,最后等待應答。發送端的偽代碼是這樣
    write(head);
    write(body);
    read(response);

    接收端的處理代碼類似這樣:
    read(request);
    process(request);
    write(response);

       這里假設head和body都比較小,當默認啟用nagle算法,并且是第一次發送的時候,根據nagle算法,第一個段head可以立即發送,因為沒有等待確認的段;接收端收到head,但是包不完整,繼續等待body達到并延遲ACK;發送端繼續寫入body,這時候nagle算法起作用了,因為head還沒有被ACK,所以body要延遲發送。這就造成了發送端和接收端都在等待對方發送數據的現象,發送端等待接收端ACK head以便繼續發送body,而接收端在等待發送方發送body并延遲ACK,悲劇的無以言語。這種時候只有等待一端超時并發送數據才能繼續往下走。

       正因為nagle算法和delayed ack的影響,再加上這種write-write-read的編程方式造成了很多網貼在討論為什么自己寫的網絡程序性能那么差。然后很多人會在帖子里建議禁用Nagle算法吧,設置TCP_NODELAY為true即可禁用nagle算法。但是這真的是解決問題的唯一辦法和最好辦法嗎?

       其實問題不是出在nagle算法身上的,問題是出在write-write-read這種應用編程上。禁用nagle算法可以暫時解決問題,但是禁用nagle算法也帶來很大壞處,網絡中充塞著小封包,網絡的利用率上不去,在極端情況下,大量小封包導致網絡擁塞甚至崩潰。因此,能不禁止還是不禁止的好,后面我們會說下什么情況下才需要禁用nagle算法。對大多數應用來說,一般都是連續的請求——應答模型,有請求同時有應答,那么請求包的ACK其實可以延遲到跟響應一起發送,在這種情況下,其實你只要避免write-write-read形式的調用就可以避免延遲現象,利用writev做聚集寫或者將head和body一起寫,然后再read,變成write-read-write-read的形式來調用,就無需禁用nagle算法也可以做到不延遲。

       writev是系統調用,在Java里是用到GatheringByteChannel.write(ByteBuffer[] srcs, int offset, int length)方法來做聚集寫。這里可能還有一點值的提下,很多同學看java nio框架幾乎都不用這個writev調用,這是有原因的。主要是因為Java的write本身對ByteBuffer有做臨時緩存,而writev沒有做緩存,導致測試來看write反而比writev更高效,因此通常會更推薦用戶將head和body放到同一個Buffer里來避免調用writev。

       下面我們將做個實際的代碼測試來結束討論。這個例子很簡單,客戶端發送一行數據到服務器,服務器簡單地將這行數據返回??蛻舳税l送的時候可以選擇分兩次發,還是一次發送。分兩次發就是write-write-read,一次發就是write-read-write-read,可以看看兩種形式下延遲的差異。注意,在windows上測試下面的代碼,客戶端和服務器必須分在兩臺機器上,似乎winsock對loopback連接的處理不一樣。

        服務器源碼:
    package net.fnil.nagle;

    import java.io.BufferedReader;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.net.InetSocketAddress;
    import java.net.ServerSocket;
    import java.net.Socket;


    public class Server {
        
    public static void main(String[] args) throws Exception {
            ServerSocket serverSocket 
    = new ServerSocket();
            serverSocket.bind(
    new InetSocketAddress(8000));
            System.out.println(
    "Server startup at 8000");
            
    for (;;) {
                Socket socket 
    = serverSocket.accept();
                InputStream in 
    = socket.getInputStream();
                OutputStream out 
    = socket.getOutputStream();

                
    while (true) {
                    
    try {
                        BufferedReader reader 
    = new BufferedReader(new InputStreamReader(in));
                        String line 
    = reader.readLine();
                        out.write((line 
    + "\r\n").getBytes());
                    }
                    
    catch (Exception e) {
                        
    break;
                    }
                }
            }
        }
    }

    服務端綁定到本地8000端口,并監聽連接,連上來的時候就阻塞讀取一行數據,并將數據返回給客戶端。

    客戶端代碼:
    package net.fnil.nagle;

    import java.io.BufferedReader;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.net.InetSocketAddress;
    import java.net.Socket;


    public class Client {

        
    public static void main(String[] args) throws Exception {
            
    // 是否分開寫head和body
            boolean writeSplit = false;
            String host 
    = "localhost";
            
    if (args.length >= 1) {
                host 
    = args[0];
            }
            
    if (args.length >= 2) {
                writeSplit 
    = Boolean.valueOf(args[1]);
            }

            System.out.println(
    "WriteSplit:" + writeSplit);

            Socket socket 
    = new Socket();

            socket.connect(
    new InetSocketAddress(host, 8000));
            InputStream in 
    = socket.getInputStream();
            OutputStream out 
    = socket.getOutputStream();

            BufferedReader reader 
    = new BufferedReader(new InputStreamReader(in));

            String head 
    = "hello ";
            String body 
    = "world\r\n";
            
    for (int i = 0; i < 10; i++) {
                
    long label = System.currentTimeMillis();
                
    if (writeSplit) {
                    out.write(head.getBytes());
                    out.write(body.getBytes());
                }
                
    else {
                    out.write((head 
    + body).getBytes());
                }
                String line 
    = reader.readLine();
                System.out.println(
    "RTT:" + (System.currentTimeMillis() - label) + " ,receive:" + line);
            }
            in.close();
            out.close();
            socket.close();
        }

    }


       客戶端通過一個writeSplit變量來控制是否分開寫head和body,如果為true,則先寫head再寫body,否則將head加上body一次寫入。客戶端的邏輯也很簡單,連上服務器,發送一行,等待應答并打印RTT,循環10次最后關閉連接。

       首先,我們將writeSplit設置為true,也就是分兩次寫入一行,在我本機測試的結果,我的機器是ubuntu 11.10:
    WriteSplit:true
    RTT:
    8 ,receive:hello world
    RTT:
    40 ,receive:hello world
    RTT:
    40 ,receive:hello world
    RTT:
    40 ,receive:hello world
    RTT:
    39 ,receive:hello world
    RTT:
    40 ,receive:hello world
    RTT:
    40 ,receive:hello world
    RTT:
    40 ,receive:hello world
    RTT:
    40 ,receive:hello world
    RTT:
    40 ,receive:hello world

        可以看到,每次請求到應答的時間間隔都在40ms,除了第一次。linux的delayed ack是40ms,而不是原來以為的200ms。第一次立即ACK,似乎跟linux的quickack mode有關,這里我不是特別清楚,有比較清楚的同學請指教。

         接下來,我們還是將writeSplit設置為true,但是客戶端禁用nagle算法,也就是客戶端代碼在connect之前加上一行:
            Socket socket = new Socket();
            socket.setTcpNoDelay(
    true);
            socket.connect(
    new InetSocketAddress(host, 8000));

        再跑下測試:
    WriteSplit:true
    RTT:
    0 ,receive:hello world
    RTT:
    0 ,receive:hello world
    RTT:
    0 ,receive:hello world
    RTT:
    0 ,receive:hello world
    RTT:
    1 ,receive:hello world
    RTT:
    0 ,receive:hello world
    RTT:
    0 ,receive:hello world
    RTT:
    0 ,receive:hello world
    RTT:
    0 ,receive:hello world
    RTT:
    0 ,receive:hello world

       這時候就正常多了,大部分RTT時間都在1毫秒以下。果然禁用Nagle算法可以解決延遲問題。
       如果我們不禁用nagle算法,而將writeSplit設置為false,也就是將head和body一次寫入,再次運行測試(記的將setTcpNoDelay這行刪除):
    WriteSplit:false
    RTT:
    7 ,receive:hello world
    RTT:
    1 ,receive:hello world
    RTT:
    0 ,receive:hello world
    RTT:
    0 ,receive:hello world
    RTT:
    0 ,receive:hello world
    RTT:
    0 ,receive:hello world
    RTT:
    0 ,receive:hello world
    RTT:
    0 ,receive:hello world
    RTT:
    0 ,receive:hello world
    RTT:
    0 ,receive:hello world

       結果跟禁用nagle算法的效果類似。既然這樣,我們還有什么理由一定要禁用nagle算法呢?通過我在xmemcached的壓測中的測試,啟用nagle算法在小數據的存取上甚至有一定的效率優勢,memcached協議本身就是個連續的請求應答的模型。上面的測試如果在windows上跑,會發現RTT最大會在200ms以上,可見winsock的delayed ack超時是200ms。

       最后一個問題,什么情況下才應該禁用nagle算法?當你的應用不是這種連續的請求——應答模型,而是需要實時地單向發送很多小數據的時候或者請求是有間隔的,則應該禁用nagle算法來提高響應性。一個最明顯是例子是telnet應用,你總是希望敲入一行數據后能立即發送給服務器,然后馬上看到應答,而不是說我要連續敲入很多命令或者等待200ms才能看到應答。

       上面是我對nagle算法和delayed ack的理解和測試,有錯誤的地方請不吝賜教。

       轉載請注明出處:http://www.tkk7.com/killme2008/archive/2011/06/30/353441.html
      
      

    評論

    # re: 網絡編程中Nagle算法和Delayed ACK的測試  回復  更多評論   

    2011-06-30 18:44 by sky3380
    好文章,第一次聽說 Nagle算法,又學到新東西了

    # re: 網絡編程中Nagle算法和Delayed ACK的測試  回復  更多評論   

    2011-06-30 20:32 by nnb
    博主的最后說的意思是不是像 telnet之類的應用 應該禁用 nagle算法?

    # re: 網絡編程中Nagle算法和Delayed ACK的測試  回復  更多評論   

    2011-08-05 17:03 by air
    寫了N多網絡編程從未深究過,慚愧

    # re: 網絡編程中Nagle算法和Delayed ACK的測試[未登錄]  回復  更多評論   

    2011-10-25 15:16 by blueswind
    樓主只是對客戶端小包發送進行了討論,也就是說假設一個包可以放下head+body所有的數據的情況下,write-read-write-read的形式確實應該是和禁用nagle算法性能上差不多的。但是如果客戶端發送的是比較大的包(比如超過1460byte的包),TCP將分為多個包進行發送,此時是不是會造成服務端delayed ack超時呢(因為服務端的應用層是在等待所有的數據都讀完再寫的)?
    也就是說,在內網通信并且發送的包比較大的情況下,還是應該通過禁用nagle算法來提高性能的吧?沒有實際測試過,請拍磚!

    # re: 網絡編程中Nagle算法和Delayed ACK的測試[未登錄]  回復  更多評論   

    2015-08-27 11:28 by HK
    寫的很好~
    主站蜘蛛池模板: 亚洲精品乱码久久久久久按摩 | 亚洲永久中文字幕在线| 免费少妇a级毛片| 久久狠狠高潮亚洲精品| 四虎影视永久免费视频观看| 精品熟女少妇aⅴ免费久久| 亚洲熟妇成人精品一区| 亚洲国产av无码精品| 日韩精品无码区免费专区| 免费大片av手机看片高清| 亚洲综合色丁香婷婷六月图片| 亚洲精品视频在线观看你懂的| 久久国产乱子伦精品免费看| 亚洲中文字幕久久久一区| 亚洲电影在线免费观看| 亚洲成a人片在线观看日本| 18禁无遮挡无码网站免费| 99热这里只有精品免费播放| a在线观看免费视频| 亚洲欧美日韩综合久久久久| 国产成人高清亚洲| 国产卡一卡二卡三免费入口| 十八禁的黄污污免费网站| 亚洲精品亚洲人成在线麻豆| 亚洲国产另类久久久精品| 亚洲中文字幕无码一区二区三区| 国产va免费精品观看精品| 99久久精品免费精品国产| 青青草原1769久久免费播放| 国产一精品一AV一免费| 二区久久国产乱子伦免费精品| 一个人看的在线免费视频| 欧洲精品码一区二区三区免费看| 婷婷亚洲综合五月天小说在线| 亚洲人成无码网站在线观看| 亚洲精品无码久久久久A片苍井空| 亚洲熟妇AV一区二区三区宅男| 亚洲精品理论电影在线观看| 亚洲韩国在线一卡二卡| 日日噜噜噜噜夜夜爽亚洲精品| 亚洲精品无码AV中文字幕电影网站|