TcpNoDelay=false,爲啓用nagle算法,也是默認值。 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,悲劇的無以言語。
這種時候只有等待一端超時併發送數據才能繼續往下走。
代碼:
發送端代碼
package socket.nagle;
import java.io.*;
import java.net.*;
import org.apache.log4j.Logger;
publicclass Client {
privatestatic Logger logger = Logger.getLogger(Client.class);
publicstaticvoid main(String[] args) throws Exception {
// 是否分開寫head和body
boolean writeSplit = true;
String host = "localhost";
logger.debug("WriteSplit:" + writeSplit);
Socket socket = new Socket();
socket.setTcpNoDelay(false);
socket.connect(new InetSocketAddress(host, 10000));
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();
logger.debug("RTT:" + (System.currentTimeMillis() - label) + ", receive: " + line);
}
in.close();
out.close();
socket.close();
}
}
package socket.nagle;
import java.io.*;
import java.net.*;
import org.apache.log4j.Logger;
publicclass Server {
privatestatic Logger logger = Logger.getLogger(Server.class);
publicstaticvoid main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(10000));
logger.debug(serverSocket);
logger.debug("Server startup at 10000");
while (true) {
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();
logger.debug(line);
out.write((line + "\r\n").getBytes());
} catch (Exception e) {
break;
}
}
}
}
}
[test5@cent4 ~]$ java socket.nagle.Server
1 [main] DEBUG socket.nagle.Server - ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=10000]
6 [main] DEBUG socket.nagle.Server - Server startup at 10000
4012 [main] DEBUG socket.nagle.Server - hello world
4062 [main] DEBUG socket.nagle.Server - hello world
4105 [main] DEBUG socket.nagle.Server - hello world
4146 [main] DEBUG socket.nagle.Server - hello world
4187 [main] DEBUG socket.nagle.Server - hello world
4228 [main] DEBUG socket.nagle.Server - hello world
4269 [main] DEBUG socket.nagle.Server - hello world
4310 [main] DEBUG socket.nagle.Server - hello world
4350 [main] DEBUG socket.nagle.Server - hello world
4390 [main] DEBUG socket.nagle.Server - hello world
4392 [main] DEBUG socket.nagle.Server -
4392 [main] DEBUG socket.nagle.Server -
[test5@cent4 ~]$ java socket.nagle.Client
0 [main] DEBUG socket.nagle.Client - WriteSplit:true
52 [main] DEBUG socket.nagle.Client - RTT:12, receive: hello world
95 [main] DEBUG socket.nagle.Client - RTT:42, receive: hello world
137 [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world
178 [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world
218 [main] DEBUG socket.nagle.Client - RTT:40, receive: hello world
259 [main] DEBUG socket.nagle.Client - RTT:40, receive: hello world
300 [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world
341 [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world
382 [main] DEBUG socket.nagle.Client - RTT:41, receive: hello world
422 [main] DEBUG socket.nagle.Client - RTT:40, receive: hello world
其實問題不是出在nagle算法身上的,問題是出在write-write-read這種應用編程上。禁用nagle算法可以暫時解決問題,但是禁用 nagle算法也帶來很大壞處,網絡中充塞着小封包,網絡的利用率上不去,在極端情況下,大量小封包導致網絡擁塞甚至崩潰。在這種情況下,其實你只要避免write-write-read形式的調用就可以避免延遲現象,如下面這種情況發送的數據不要再分割成兩部分。
實驗2:當WriteSplit=false and TcpNoDelay=false 啓用nagle算法
[test5@cent4 ~]$ java socket.nagle.Client
0 [main] DEBUG socket.nagle.Client - WriteSplit:false
27 [main] DEBUG socket.nagle.Client - RTT:4, receive: hello world
31 [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world
34 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world
38 [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world
42 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world
44 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world
47 [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world
50 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world
53 [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world
54 [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world
[test5@cent4 ~]$ java socket.nagle.Client
0 [main] DEBUG socket.nagle.Client - WriteSplit:true
25 [main] DEBUG socket.nagle.Client - RTT:6, receive: hello world
28 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world
31 [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world
33 [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world
35 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world
41 [main] DEBUG socket.nagle.Client - RTT:6, receive: hello world
49 [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world
52 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world
56 [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world
59 [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world
[test5@cent4 ~]$ java socket.nagle.Client
0 [main] DEBUG socket.nagle.Client - WriteSplit:false
21 [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world
23 [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world
27 [main] DEBUG socket.nagle.Client - RTT:3, receive: hello world
30 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world
32 [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world
35 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world
38 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world
41 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world
43 [main] DEBUG socket.nagle.Client - RTT:1, receive: hello world
46 [main] DEBUG socket.nagle.Client - RTT:2, receive: hello world
注意:以上實驗在windows上測試下面的代碼,客戶端和服務器必須分在兩臺機器上,似乎winsock對loopback連接的處理不一樣。下面的我的做法是:服務端與客戶端都在一臺Linux機上。
轉載自:http://blog.csdn.net/huang_xw/article/details/7340241