【问题标题】:Why doesn't Java see an incoming UDP packet that tcpdump shows is received?为什么 Java 看不到 tcpdump 显示的传入 UDP 数据包已收到?
【发布时间】: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


【解决方案1】:

(抱歉花了这么长时间才回到这个问题。我不久前找到了答案。)

我觉得自己很愚蠢。事实证明,传入的 UDP 数据包有防火墙规则。如果“回复”数据包相对于出站“请求”数据包“足够快”进入,那么一切都很好。但是一旦延迟太长,防火墙就会认为回复与请求无关,因此将其视为一些随机传入的数据包,从而阻止它。

【讨论】:

    【解决方案2】:

    如果接收方的套接字接收缓冲区已满,就会发生这种情况。

    有点跑题了,但是这个:

    datagram.setSocketAddress(datagram.getSocketAddress());
    datagram.setPort(datagram.getPort());
    

    显然是多余的。您只需更改其数据即可将传入的数据报重用于回复。通过构造,它已经具有返回地址和端口。

    【讨论】:

      【解决方案3】:

      如果可能,也许检查 tcpdump 捕获中数据包的 IPv4 TTL(生存时间)。

      在 48 秒后,由于 TTL,数据包可能会被接收方的 IP 堆栈接收但丢弃。

      另请参阅有关更改 TTL 的答案(表示它仅适用于多播套接字,但无论如何都被接受):Java control IP TTL?

      【讨论】:

      • TTL是指跳数,不是时间值。
      • 请注意,TTL 并不是几十年来的“时间”——它实际上是一个跳数。发送数据包之间的延迟不会影响数据包最初发送时设置的 TTL。 /proc/sys/net/ipv4/ip_default_ttl(我系统上的64)中的默认值确实应该适用于大多数网络。
      • 我的 TTL 是 64。这不是问题,因为机器之间没有跃点(在同一子网上,它们之间没有路由器)。
      猜你喜欢
      • 2018-10-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-03
      • 1970-01-01
      • 2016-10-18
      相关资源
      最近更新 更多