如何在线程中继续尝试获取Java DatagramSocket?

时间:2016-01-28 15:43:22

标签: java multithreading udp

我正在研究Java UDP应用程序。应用程序中有一个线程,其唯一的工作是侦听特定端口上的服务器。

我在错误的假设下写了这个应用程序,我正在听的服务器总是在运行;然而,这是一个不好的假设。

如果我的应用程序在服务器运行后启动,那么一切正常。如果我的应用程序在服务器启动之前启动,或者在我的应用程序运行时重新启动服务器,则我的应用程序会中断。

MainClass.java

public class MainClass {
    public static void main(String[] args){
        ListeningClass myListeningClass = new ListeningClass();
        Thread listenerThread = new Thread(myListeningClass);
        listenerThread.setName("My Listening Thread");
        listenerThread.start();
    }
}

ListeningClass.java

public class ListeningClass implements Runnable {
    private volatile boolean run = true;
    private byte[] receiveBuffer;
    private int receiveBufferSize;
    private DatagramSocket myDatagramSocket;
    private DatagramPacket myDatagramPacket;

    @Override
    public void run(){
        try {
            myDatagramSocket = new DatagramSocket(null);
            InetSocketAddress myInetSocketAddress = new InetSocketAddress(15347);
            myDatagramSocket.bind(myInetSocketAddress);

            receiveBuffer = new byte[2047];
            myDatagramPacket = new DatagramPacket(receiveBuffer, 2047);

            while(run){
                myDatagramSocket.receive(myDatagramPacket);
                byte[] data = myDatagramPacket.getData();
                receiveBufferSize = myDatagramPacket.getLength();
                // process the data received here
            }
        } catch (SocketException se){
            // do stuff
        } catch (IOException ioe){
            // do stuff
        }
    }

    public boolean isRun(){
        return run;
    }

    public void setRun(boolean run){
        this.run = run;
    }
}

就像我说的,如果我的应用程序在服务器运行后启动,一切都运行正常,就像预期的那样。但是,如果我的应用程序在服务器运行之前启动,则无效。显然,是因为线程尝试打开连接一次,如果失败,那么它永远不会再次尝试。

我将DatagramSocket开放代码移动到while块中,但这并不漂亮。我收到了一堆“已绑定端口”的错误。

那么,我该如何重建它以使其正常工作?

2 个答案:

答案 0 :(得分:1)

这不是真正的并发问题。您只需要检查receive上抛出的异常并进行适当处理。在这种情况下,重新绑定套接字。查看receive的{​​{3}}。

例如:

...
while(run) {
    try {
        myDatagramSocket.receive(myDatagramPacket);
        byte[] data = myDatagramPacket.getData();
        receiveBufferSize = myDatagramPacket.getLength();
        // process the data received here
    } catch (IOException ioe) {
        // Perhaps use PortUnreachableException but not guaranteed
        rebind(myDatagramSocket, myInetSocketAddress);
    }
}

private void rebind(DatagramSocket s, InetSocketAddress addr) {
    s.bind(addr);
}

我认为这应该足够了。关键是,如果您的receive表明服务器存在I / O问题,您只想重新绑定。如果你将绑定放在循环中,你就可以为每个接收绑定 - 这在你的快乐路径情况下是正常的。

答案 1 :(得分:1)

这里要注意的重要事项是程序失败的精确点以及您给出的异常类型。

据推测,它在第myDatagramSocket.receive(myDatagramPacket);行上失败了,但是在你的异常上用stacktrace仔细检查。要检查的第二件事是异常的类型。它是SocketException还是SocketException的子类?是否有特定的错误代码?尝试尽可能具体。

此时,您应该围绕在 while循环中自己的try catch 中失败的代码部分。你希望能够说如果它失败了,你的线程将会休眠并在中断后重试(不要用请求轰炸服务器)。而对于更简单的事情,我会用自己的方法对代码进行分区,所以你会期望像:

public class ListeningClass implements Runnable {
    private static final int MAX_RETRIES = 30;
    private static final int RETRY_SLEEPTIME = 30000;

    private volatile boolean run = true;
    private InetSocketAddress myInetSocketAddress;

    @Override
    public void run(){
        try {
            DatagramSocket myDatagramSocket = new DatagramSocket(null);
            myInetSocketAddress = new InetSocketAddress(15347);
            myDatagramSocket.bind(myInetSocketAddress);

            byte[] receiveBuffer = new byte[2047];
            DatagramPacket myDatagramPacket = new DatagramPacket(receiveBuffer, 2047);

            awaitRequests(myDatagramSocket, myDatagramPacket)
        } catch (SocketException se){
            // do stuff
        } catch (IOException ioe){
            // do stuff
        }
    }

    private void awaitRequests(DatagramSocket myDatagramSocket, DatagramPacket myDatagramPacket) throws SocketException, IOException {
            int maxRetries = MAX_RETRIES;
            while(run){
                try {
                    myDatagramSocket.receive(myDatagramPacket);
                    byte[] data = myDatagramPacket.getData();

                    // Packet received correctly, reset retry attempts
                    maxRetries = MAX_RETRIES;

                    process(myDatagramPacket);

                } catch (SocketException e) {
                     maxRetries--;
                     // Good place to write to log of some kind
                     if(maxRetries == 0) {
                         throw e;
                     }
                     Thread.currentThread().sleep(RETRY_SLEEPTIME);

                     // Lets attempt to restablish the connection
                     reconnect(myDatagramSocket);
                }
            }
    }

    private void process(DatagramPacket myDatagramPacket) {
        int receiveBufferSize = myDatagramPacket.getLength();
        // process the data received here
    }

    private void reconnect(DatagramSocket myDatagramSocket) {
        myDatagramSocket.bind(myInetSocketAddress);
    }

    public boolean isRun(){
        return run;
    }

    public void setRun(boolean run){
        this.run = run;
    }
}

注意几件事。我只捕获了SocketException,因为我假设你得到的异常类型是SocketException。如果你得到某种IOException,那么你应该检查一下。如果您指定该异常的子类型,那就更好了。原因是:您不希望一揽子处理所有错误,而只是处理与服务器关闭的错误。如果程序缺少打开套接字的身份验证,您可能希望立即失败,而不是不断重试。

第二件事是我已经用自己的方法分离了数据包的处理,因为我认为在这些情况下这是正确的做法。