【问题标题】:"java.net.BindException: Address already in use" despite setting SO_REUSEADDR“java.net.BindException:地址已在使用中”尽管设置了 SO_REUSEADDR
【发布时间】:2014-01-19 08:22:30
【问题描述】:

我已经编写了这个简单的 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)

我在调用绑定之前设置了 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();
        }
    }
}

【问题讨论】:

  • 您是在尝试同时运行多台服务器(即“一个接一个地启动,然后一起运行”),还是在第一个服务器退出后立即启动另一台服务器? SO_REUSEADDR 在第一种情况下没有帮助。
  • 你不需要调用它两次。

标签: java nio


【解决方案1】:

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

来自What exactly does SO_REUSEADDR do?

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-30
    • 2014-08-11
    • 2013-09-08
    • 2012-04-14
    • 1970-01-01
    • 1970-01-01
    • 2018-07-10
    相关资源
    最近更新 更多