Search code examples
javanio

"java.net.BindException: Address already in use" despite setting SO_REUSEADDR


I have written this simple NIO server but when running multiple times, one right after another I get this exception:

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)

I have set setReuseAddress(true) before a call to bind. I have also tried to call setOption(StandardSocketOptions.SO_REUSEADDR, true) on ServerSocketChannel but it is still the same.

Can someone point out why it happens?

Here is the code:

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();
        }
    }
}

Solution

  • You cannot have two different invocations of your code listening on the same adapter and port number. This is the way the TCP/IP stack works. If you did, how would the stack know which process gets the connection? SO_REUSEADDR has nothing to do with this.

    From What exactly does SO_REUSEADDR do?

    This socket option tells the kernel that even if this port is busy (in the TIME_WAIT state), go ahead and reuse it anyway. If it is busy, but with another state, you will still get an address already in use error. It is useful if your server has been shut down, and then restarted right away while sockets are still active on its port. You should be aware that if any unexpected data comes in, it may confuse your server, but while this is possible, it is not likely.

    In other words, if you've closed the socket but it's still waiting for the connection to quiesce (receive the FIN/ACK or timeout) you can immediately grab it again. You can never have two processes connected to the same endpoint at the same time.