“java.net.BindException:已经在使用的地址”尽管设置了SO_REUSEADDR

时间:2014-01-18 22:36:01

标签: java nio

我已经编写了这个简单的NIO服务器但是当多次运行时,一个接一个地我得到了这个例外:

Exception in thread "main" java.lang.IllegalStateException: java.net.BindException: Address already in use
    at test.Server.start(Server.java:38)
    at test.Server.main(Server.java:93)

我在调用bind之前设置了setReuseAddress(true)。 我还尝试在ServerSocketChannel上调用setOption(StandardSocketOptions.SO_REUSEADDR,true),但它仍然是相同的。

有人可以指出它为什么会发生吗?

以下是代码:

package test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Server {

    private ServerSocketChannel ssc;
    private ServerSocket serverSocket;
    private Selector accept;
    private ExecutorService executor = Executors.newSingleThreadExecutor();

    void start(final CountDownLatch cdl) {
        try {
            this.accept = Selector.open();

            ssc = ServerSocketChannel.open();
            ssc.configureBlocking(false);
            ssc.setOption(StandardSocketOptions.SO_REUSEADDR, true);

            InetSocketAddress isa = new InetSocketAddress("127.0.0.1", 9123);
            serverSocket = ssc.socket();
            serverSocket.setReuseAddress(true);
            serverSocket.bind(isa);
            ssc.register(accept, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
        executor.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    if (cdl != null) {
                        cdl.countDown();
                    }
                    while (true) {
                        accept.select();
                        if (Thread.currentThread().isInterrupted()) {
                            return;
                        }
                        Set<SelectionKey> readyKeys = accept.selectedKeys();
                        Iterator<SelectionKey> i = readyKeys.iterator();
                        while (i.hasNext()) {
                            SelectionKey sk = i.next();
                            if (sk.isValid() && sk.isAcceptable()) {
                                accept(sk);
                            }
                            i.remove();
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            private void accept(final SelectionKey sk) throws IOException {
                ServerSocketChannel ssc = (ServerSocketChannel) sk.channel();
                SocketChannel sc = ssc.accept();
                sc.configureBlocking(false);
                sc.register(accept, SelectionKey.OP_READ);
                System.out.println("Connection accepted from: "
                        + sc.getRemoteAddress());
            }
        });
    }

    void stop() {
        try {
            executor.shutdown();
            executor.awaitTermination(10, TimeUnit.SECONDS);
            serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Server s = new Server();
        CountDownLatch cdl = new CountDownLatch(1);
        s.start(cdl);
        cdl.await();
        Client.connect();
        s.stop();
    }
}

class Client {
    static void connect() {
        try {
            new Socket("127.0.0.1", 9123);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1 个答案:

答案 0 :(得分:4)

您不能对同一个适配器和端口号进行两次不同的代码调用。这是TCP / IP堆栈的工作方式。如果你这样做,堆栈将如何知道哪个进程获得连接? SO_REUSEADDR与此无关。

来自What exactly does SO_REUSEADDR do?

  

此套接字选项告诉内核即使此端口忙   (在TIME_WAIT状态下),继续并重复使用它。如果是   很忙,但在另一个州,你仍然会得到一个地址   在使用错误。如果您的服务器已关闭,它会很有用   然后当套接字仍处于活动状态时立即重新启动   港口。您应该知道,如果有任何意外数据,它   可能会混淆您的服务器,但虽然这是可能的,但事实并非如此   可能的。

换句话说,如果您已关闭套接字但它仍在等待连接静默(接收FIN / ACK或超时),您可以立即再次获取它。您永远不能同时将两个进程连接到同一个端点。