NIO ServerSocketChannel监听端口问题
今天晓风轻遇到了一个问题,nodejs和tomcat8同时监听同一个网卡的同一个端口,监听不会抛出异常,并且访问该端口,其中某个程序可以正常响应。关闭正则响应的程序,另外一个程序会接替被关闭的程序进行响应。
其实这是NIO监听端口的机制造成的。ServerSocketChannel监听端口时,如果该端口已经被监听,ServerSocketChannel不会抛出异常,而是继续执行。这样就导致了程序看上去正常启动了,但是实际上并没有成果监听。
重现问题
首先使用ServerSocketChannel编写一个Server,监听8989端口,如下:
public class NIOServer {
public static void main(String[] args) {
ServerSocketChannel serverSocketChannel = null;
try {
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 8989));
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
ByteBuffer buffer = ByteBuffer.allocate(64);
System.out.println("server started");
while (true) {
selector.select();
Iterator<SelectionKey> selectKeyIt = selector.selectedKeys().iterator();
... ... 省略 ... ...
NIOServer在进行selector循环之前,输出server started日志。
执行nc -l 8989,提前监听端口8989。
然后运行NIOServer,可以看到程序成果输出server started日志。
此时执行telnet 127.0.0.1 8989连接本地8989端口,可以发现连接上的是nc,如下 :关闭nc,再次telnet 127.0.0.1 8989,此时会连接NIOServer,如下:
综上,即重现了上面说的问题。
解决方法
使用Socket连接本地8989端口,如果发现连接成功,则说明端口已经被占用,抛出异常即可。
/**
* 检查端口是否已经被占用
*
* @param port
* @return
*/
private static boolean isPortBinded(int port) {
boolean isBinded = false;
Socket sc = new Socket();
try {
//连接本地端口,设置半秒超时
sc.connect(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), port), 500);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//判断是否成功连接
if (sc.isConnected()) {
isBinded = true;
}
try {
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return isBinded;
}
修改代码,添加检查
public class NIOServerWithBindCheck {
public static void main(String[] args) throws IOException {
int port = 8989;
if (isPortBinded(port)) {
throw new IOException("server port " + port + "is already binded");
}
ServerSocketChannel serverSocketChannel = null;
... ...省略 ... ...
效果如下:
完整代码:https://github.com/pkpk1234/ServerSocketChannel-bind-problem