我已经编写了这个简单的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();
}
}
}
答案 0 :(得分:4)
您不能对同一个适配器和端口号进行两次不同的代码调用。这是TCP / IP堆栈的工作方式。如果你这样做,堆栈将如何知道哪个进程获得连接? SO_REUSEADDR
与此无关。
来自What exactly does SO_REUSEADDR do?
此套接字选项告诉内核即使此端口忙 (在TIME_WAIT状态下),继续并重复使用它。如果是 很忙,但在另一个州,你仍然会得到一个地址 在使用错误。如果您的服务器已关闭,它会很有用 然后当套接字仍处于活动状态时立即重新启动 港口。您应该知道,如果有任何意外数据,它 可能会混淆您的服务器,但虽然这是可能的,但事实并非如此 可能的。
换句话说,如果您已关闭套接字但它仍在等待连接静默(接收FIN / ACK或超时),您可以立即再次获取它。您永远不能同时将两个进程连接到同一个端点。