为什么Java看不到tcpdump显示的传入UDP数据包?

时间:2011-12-15 21:50:24

标签: java networking udp

我看到一个问题(SLES11上的Java 6),其中UDP数据包被发送回客户端计算机(并且客户端计算机上的tcpdump显示正在接收),但数据包不是见过Java。

测试的工作原理如下:

客户端生成一些随机数据,将其粘贴到UDP数据包中,将其发送到特定端口上的服务器,并等待它回显(SO_TIMEOUT为180000毫秒)。当它收到它时,会打印出它收到它以及它被发送后多久。

服务器端侦听该端口上的UDP数据包。当它收到它时,它会延迟一点,然后将其向后发送,不变。延迟从某个初始值开始(3000 ms),然后每次收到一个数据包时加倍,直到达到最大值(90000 ms),之后延迟才会保持最大值。

它工作正常,直到延迟达到48000毫秒。客户端上运行的tcpdump显示正在发送的数据包,并显示48000毫秒后到达的回复数据包。但是,Java程序从未看到该回复数据包,并且在180000毫秒之后声称接收超时。即使客户端计算机在tcpdump显示的仅48000 ms之后收到数据包。

可能会发生什么?

以下是程序的输出和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输出(我的评论是内联的)是:

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

3 个答案:

答案 0 :(得分:0)

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

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

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

答案 1 :(得分:0)

如果接收器的套接字接收缓冲区已满,则可能发生这种情况。

稍微偏离主题,但是这个:

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

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

答案 2 :(得分:0)

(很抱歉花了很长时间才回到这一点。我刚才找到了答案。)

我觉得很愚蠢。事实证明,传入的UDP数据包上存在防火墙规则。如果“回复”数据包相对于出站“请求”数据包“很快”进来,一切都很顺利。但是一旦延迟时间太长,防火墙就认为答复与请求无关,因此将其视为一些随机传入的数据包,从而将其阻止。