<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://m.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
    寫的很好~
    主站蜘蛛池模板: av在线亚洲欧洲日产一区二区| 曰皮全部过程视频免费国产30分钟| 2022中文字字幕久亚洲| 黄网站色视频免费看无下截 | 狠狠亚洲婷婷综合色香五月排名 | 日韩在线一区二区三区免费视频| 国产伦一区二区三区免费| 色窝窝亚洲AV网在线观看| 国产精品无码一二区免费 | 久久WWW免费人成—看片| 中文字幕亚洲日本岛国片| A国产一区二区免费入口| 亚洲人成网7777777国产| 成人精品一区二区三区不卡免费看| 亚洲av无码一区二区三区不卡| 午夜网站在线观看免费完整高清观看 | 亚洲综合一区二区精品导航| 色片在线免费观看| 亚洲综合在线一区二区三区 | 在线免费观看一级片| 成a人片亚洲日本久久| 在线亚洲97se亚洲综合在线 | 毛色毛片免费观看| 亚洲av无码日韩av无码网站冲| 内射无码专区久久亚洲| 香蕉视频在线免费看| 精品日韩亚洲AV无码一区二区三区 | 成人免费视频一区二区三区| 久久亚洲精品高潮综合色a片| 国产亚洲精品资在线| 亚洲免费电影网站| 激情无码亚洲一区二区三区| 亚洲午夜国产精品无码老牛影视 | 暖暖免费高清日本一区二区三区| 未满十八私人高清免费影院| 国产成人亚洲综合色影视| 国产1024精品视频专区免费| 成人精品综合免费视频| 亚洲明星合成图综合区在线| 亚洲AV无码一区二区三区在线观看 | 亚洲乱码在线视频|