【发布时间】:2012-01-21 13:31:14
【问题描述】:
我看到一个问题(SLES11 上的 Java 6)正在将 UDP 数据包发送回客户端计算机(并且客户端计算机上的 tcpdump 显示它已被接收),但该数据包未被看到Java。
测试如下:
客户端生成一些随机数据,将其粘贴在 UDP 数据包中,将其发送到特定端口上的服务器并等待回显(SO_TIMEOUT 为 180000 毫秒)。当它收到它时,它会打印出它已经收到它以及它已经发送了多长时间。
服务器端侦听该端口上的 UDP 数据包。当它接收到它时,它会延迟一点,然后立即将其发送回去,不变。延迟从某个初始值(3000 毫秒)开始,然后在每次接收到数据包时加倍,直到达到最大值(90000 毫秒),之后延迟保持在最大值。
它工作正常,直到延迟达到 48000 毫秒。客户端上运行的tcpdump 显示正在发送的数据包,并显示回复数据包在 48000 毫秒后到达。但是,Java 程序从未看到该回复数据包,并且在 180000 毫秒后声称接收超时。即使客户端机器在 tcpdump 显示的仅 48000 毫秒后收到数据包。
会发生什么?
下面是程序和tcpdump的输出,然后是客户端和服务器程序的源代码。
在客户端机器上,测试程序输出为:
rich-ova3:~/UDPTest2 # java -cp . Client
Properties:
-- listing properties --
ServerPort=20000
Timeout=180000
Server=rich-ova5.teak.eng
Loops=8
DatagramLength=333
UDP socket timeout is 180000 ms
Sending packet 1 of 8
Received packet 1 of 8
Round-trip time: 3001 ms
Sending packet 2 of 8
Received packet 2 of 8
Round-trip time: 6001 ms
Sending packet 3 of 8
Received packet 3 of 8
Round-trip time: 12001 ms
Sending packet 4 of 8
Received packet 4 of 8
Round-trip time: 24000 ms
Sending packet 5 of 8
Error receiving: Receive timed out
java.net.SocketTimeoutException: Receive timed out
at java.net.PlainDatagramSocketImpl.receive0(Native Method)
at java.net.PlainDatagramSocketImpl.receive(Unknown Source)
at java.net.DatagramSocket.receive(Unknown Source)
at Client.go(Client.java:80)
at Client.main(Client.java:39)
Round-trip time: 180096 ms
Sending packet 6 of 8
Error receiving: Receive timed out
java.net.SocketTimeoutException: Receive timed out
at java.net.PlainDatagramSocketImpl.receive0(Native Method)
at java.net.PlainDatagramSocketImpl.receive(Unknown Source)
at java.net.DatagramSocket.receive(Unknown Source)
at Client.go(Client.java:80)
at Client.main(Client.java:39)
Round-trip time: 180087 ms
Sending packet 7 of 8
Error receiving: Receive timed out
java.net.SocketTimeoutException: Receive timed out
at java.net.PlainDatagramSocketImpl.receive0(Native Method)
at java.net.PlainDatagramSocketImpl.receive(Unknown Source)
at java.net.DatagramSocket.receive(Unknown Source)
at Client.go(Client.java:80)
at Client.main(Client.java:39)
Round-trip time: 180093 ms
Sending packet 8 of 8
Error receiving: Receive timed out
java.net.SocketTimeoutException: Receive timed out
at java.net.PlainDatagramSocketImpl.receive0(Native Method)
at java.net.PlainDatagramSocketImpl.receive(Unknown Source)
at java.net.DatagramSocket.receive(Unknown Source)
at Client.go(Client.java:80)
at Client.main(Client.java:39)
Round-trip time: 180078 ms
Send successes: 8
Send errors: 0
Receive successes: 4
Receive errors: 4
客户端机器上的tcpdump输出(我的cmets内联)是:
16:03:42.438252 IP 172.16.20.113.16362 > 172.16.20.115.20000: UDP, length 333
16:03:45.439322 IP 172.16.20.115.20000 > 172.16.20.113.16362: UDP, length 333
(a 3011 ms delay)
16:03:45.440315 IP 172.16.20.113.25559 > 172.16.20.115.20000: UDP, length 333
16:03:51.441394 IP 172.16.20.115.20000 > 172.16.20.113.25559: UDP, length 333
(a 6001 ms delay)
16:03:51.441938 IP 172.16.20.113.30457 > 172.16.20.115.20000: UDP, length 333
16:04:03.442564 IP 172.16.20.115.20000 > 172.16.20.113.30457: UDP, length 333
(a 12000 ms delay)
16:04:03.443095 IP 172.16.20.113.46143 > 172.16.20.115.20000: UDP, length 333
16:04:27.443572 IP 172.16.20.115.20000 > 172.16.20.113.46143: UDP, length 333
(a 24001ms delay)
16:04:27.444109 IP 172.16.20.113.31747 > 172.16.20.115.20000: UDP, length 333
16:05:15.444688 IP 172.16.20.115.20000 > 172.16.20.113.31747: UDP, length 333
(a 48001 ms delay)
16:07:27.540689 IP 172.16.20.113.38357 > 172.16.20.115.20000: UDP, length 333
16:08:57.541312 IP 172.16.20.115.20000 > 172.16.20.113.38357: UDP, length 333
(a 90000 ms delay)
16:10:27.627411 IP 172.16.20.113.33915 > 172.16.20.115.20000: UDP, length 333
16:11:57.631436 IP 172.16.20.115.20000 > 172.16.20.113.33915: UDP, length 333
(a 90004 ms delay)
16:13:27.720668 IP 172.16.20.113.36494 > 172.16.20.115.20000: UDP, length 333
16:14:57.722353 IP 172.16.20.115.20000 > 172.16.20.113.36494: UDP, length 333
(a 90001 ms delay)
在服务器机器上,测试程序输出为:
rich-ova5:~/UDPTest2 # java -cp . Server
Properties:
-- listing properties --
MinSleepTime=1000
DatagramLength=333
MaxSleepTime=90000
Port=20000
Received packet.
Delaying for 3000 ms
Sending packet.
Received packet.
Delaying for 6000 ms
Sending packet.
Received packet.
Delaying for 12000 ms
Sending packet.
Received packet.
Delaying for 24000 ms
Sending packet.
Received packet.
Delaying for 48000 ms
Sending packet.
Received packet.
Delaying for 90000 ms
Sending packet.
Received packet.
Delaying for 90000 ms
Sending packet.
Received packet.
Delaying for 90000 ms
Sending packet.
这里是客户端来源:
import java.io.FileInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Properties;
import java.util.Random;
public class Client {
private static final String CONFIG_FILE = "client.conf";
private int datagramLength;
private boolean debug;
private InetAddress server;
private int serverPort;
private int timeout;
private int loops;
public static void main(String[] arg) throws Exception {
Client c = new Client();
c.go();
}
private void go() throws Exception {
readClientConfig();
Random rand = new Random(System.currentTimeMillis());
System.out.println("UDP socket timeout is " + timeout + " ms");
byte[] referenceData = new byte[datagramLength];
byte[] sentData = new byte[referenceData.length];
int receiveErrors = 0;
int receiveSuccess = 0;
int sendErrors = 0;
int sendSuccess = 0;
for (int i = 0; i < loops; i++) {
long sendTime = 0;
long receiveTime = 0;
rand.nextBytes(referenceData);
System.arraycopy(referenceData, 0, sentData, 0, referenceData.length);
DatagramSocket serverSock = new DatagramSocket();
serverSock.setSoTimeout(timeout);
DatagramPacket datagram = new DatagramPacket(sentData, sentData.length, server, serverPort);
try {
System.out.println("Sending packet " + (i + 1) + " of " + loops);
serverSock.send(datagram);
sendSuccess++;
} catch (Exception e) {
System.out.println("Error sending: " + e.getMessage());
e.printStackTrace();
sendErrors++;
continue;
}
sendTime = System.currentTimeMillis();
try {
serverSock.receive(datagram);
System.out.println("Received packet " + (i + 1) + " of " + loops);
receiveSuccess++;
} catch (Exception e) {
receiveErrors++;
System.out.println("Error receiving: " + e.getMessage());
e.printStackTrace();
}
serverSock.close();
receiveTime = System.currentTimeMillis();
System.out.println("Round-trip time: " + (receiveTime - sendTime) + " ms");
byte[] receivedData = datagram.getData();
if (receivedData.length != referenceData.length) {
System.out.println("Mismatched packet length.");
sendErrors++;
}
for (int j = 0; j < datagramLength; j++) {
if (referenceData[j] != receivedData[j]) {
System.out.println("Mismatched packet contents.");
sendErrors++;
}
}
System.out.println();
}
System.out.println("Send successes: " + sendSuccess);
System.out.println("Send errors: " + sendErrors);
System.out.println();
System.out.println("Receive successes: " + receiveSuccess);
System.out.println("Receive errors: " + receiveErrors);
}
private void readClientConfig() throws IOException {
boolean hasError = false;
String val;
Properties prop = new Properties();
FileInputStream fis = new FileInputStream(CONFIG_FILE);
prop.load(fis);
val = prop.getProperty("Server");
if (val == null) {
System.out.println("Error reading 'Server': missing value");
hasError = true;
} else {
try {
server = InetAddress.getByName(val);
} catch (UnknownHostException e) {
System.out.println("Error reading 'Server': " + e.getMessage());
hasError = true;
}
}
val = prop.getProperty("ServerPort", "3000");
try {
serverPort = Integer.parseInt(val);
} catch (NumberFormatException e) {
System.out.println("Error reading 'ServerPort': " + e.getMessage());
hasError = true;
}
val = prop.getProperty("DatagramLength", "200");
try {
datagramLength = Integer.parseInt(val);
} catch (NumberFormatException e) {
System.out.println("Error reading 'DatagramLength': " + e.getMessage());
hasError = true;
}
// Time out in milliseconds.
val = prop.getProperty("Timeout", "3000");
try {
timeout = Integer.parseInt(val);
} catch (NumberFormatException e) {
System.out.println("Error reading 'Timeout' value.");
hasError = true;
}
// Number of loops to perform.
val = prop.getProperty("Loops", "1");
try {
loops = Integer.parseInt(val);
} catch (NumberFormatException nfe) {
System.out.println("Error reading 'Loops' value.");
hasError = true;
}
val = prop.getProperty("EnableDebug", "yes");
debug = val.equalsIgnoreCase("yes");
System.out.println("Properties:");
prop.list(System.out);
System.out.println();
if (hasError) {
System.out.println("There are errors - fix them and restart the server.");
System.exit(1);
}
}
}
以及服务器来源:
import java.io.FileInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Properties;
public class Server {
private static final String CONFIG_FILE = "server.conf";
private int datagramLength;
private boolean debug;
private int port;
private long initialDelay;
private long maxDelay;
public static void main(String[] arg) throws Exception {
Server s = new Server();
s.go();
}
private void go() throws Exception {
readServerConfig();
DatagramSocket sock = new DatagramSocket(port);
sock.setSoTimeout(0);
byte[] in = new byte[datagramLength];
DatagramPacket datagram = new DatagramPacket(in, datagramLength);
// Loop with exponentially-increasing delays between receive
// and reply (up to the specified maximum delay).
long delay = initialDelay;
while (true) {
sock.receive(datagram);
System.out.println("Received packet.");
System.out.println("Delaying for " + delay + " ms");
Thread.sleep(delay);
System.out.println("Sending packet.");
datagram.setSocketAddress(datagram.getSocketAddress());
datagram.setPort(datagram.getPort());
sock.send(datagram);
System.out.println();
delay *= 2;
if (delay > maxDelay) {
delay = maxDelay;
}
}
}
private void readServerConfig() throws IOException {
boolean hasError = false;
String val;
Properties prop = new Properties();
FileInputStream fis = new FileInputStream(CONFIG_FILE);
prop.load(fis);
val = prop.getProperty("Port", "3000");
try {
port = Integer.parseInt(val);
} catch (NumberFormatException e) {
System.out.println("Error reading 'Port' value: " + e.getMessage());
hasError = true;
}
val = prop.getProperty("DatagramLength", "200");
try {
datagramLength = Integer.parseInt(val);
} catch (NumberFormatException e) {
System.out.println("Error reading 'DatagramLength' value: " + e.getMessage());
hasError = true;
}
val = prop.getProperty("StartSleepTime", "3000");
try {
initialDelay = Long.parseLong(val);
} catch (NumberFormatException e) {
System.out.println("Error reading 'StartSleepTime' value: " + e.getMessage());
hasError = true;
}
val = prop.getProperty("MaxSleepTime", "60000");
try {
maxDelay = Long.parseLong(val);
} catch (NumberFormatException e) {
System.out.println("Error reading 'MaxSleepTime' value: " + e.getMessage());
hasError = true;
}
val = prop.getProperty("EnableDebug", "yes");
debug = val.equalsIgnoreCase("yes");
System.out.println("Properties:");
prop.list(System.out);
System.out.println();
if (hasError) {
System.out.println("There are errors - fix them and restart the server.");
System.exit(1);
}
}
}
和client.conf:
# Server address
Server = rich-ova5.teak.eng
# Server port number.
ServerPort = 20000
# This is the length of the data to send.
# It must match the server's size.
DatagramLength = 333
# Number of times to run the client.
Loops = 8
# Client time out period in milliseconds.
Timeout = 180000
最后,server.conf:
# Server port number.
Port=20000
# Length of the datagram.
# It must match the client's size.
DatagramLength = 333
# Initial sleep time.
MinSleepTime = 1000
# Maximu sleep time. Goes from the MinSleepTime to the
# maximum sleep time.
MaxSleepTime = 90000
【问题讨论】:
-
正如我在问题开头所说的,SLES11。
-
这是一个错字(我已经修正)。从问题中的实际客户端和服务器输出中可以看出,这两个程序都使用端口 20000。
-
在
localhost上运行客户端和服务器运行良好——完全没有错误。 -
两台机器是不是在一个NAT盒子的不同边?如果是这样,可能会在“忘记”伪连接的转换点发生超时。
-
不,他们不是。两者都在同一个子网上。而且它们之间没有任何类型的路由器。
标签: java networking udp