当前位置: 首页 > 工具软件 > open-selector > 使用案例 >

NIO 之Selector.open() 机制解析

薛烨霖
2023-12-01
最近在学习使用mina这个基于NIO的框架,写了个客户端程序用来测试服务器,大致代码如下:
for(int i=0;i<1000;i++){
IoConnector connector=new NioSocketConnector();
connector.setConnectTimeoutMillis(30000);
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new SmsCodecFactory(Charset.forName("utf-8"))));

connector.setHandler(new SmsClientHandler());
connector.connect(new InetSocketAddress("localhost", 9123));
}

循环还没跑结束就抛出了如下的异常:
Exception in thread "main" org.apache.mina.core.RuntimeIoException: Failed to create a new instance of org.apache.mina.transport.socket.nio.NioProcessor:null
at org.apache.mina.core.service.SimpleIoProcessorPool.<init>(SimpleIoProcessorPool.java:200)
at org.apache.mina.core.service.SimpleIoProcessorPool.<init>(SimpleIoProcessorPool.java:114)
at org.apache.mina.core.polling.AbstractPollingIoConnector.<init>(AbstractPollingIoConnector.java:95)
at org.apache.mina.transport.socket.nio.NioSocketConnector.<init>(NioSocketConnector.java:55)
at com.lijun.sms.client.SmsFakeClient.main(SmsFakeClient.java:26)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.GeneratedConstructorAccessor1.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at org.apache.mina.core.service.SimpleIoProcessorPool.<init>(SimpleIoProcessorPool.java:182)
... 4 more
Caused by: org.apache.mina.core.RuntimeIoException: Failed to open a selector.
at org.apache.mina.transport.socket.nio.NioProcessor.<init>(NioProcessor.java:61)
... 8 more
Caused by: java.io.IOException: Unable to establish loopback connection
at sun.nio.ch.PipeImpl$Initializer.run(PipeImpl.java:106)
at java.security.AccessController.doPrivileged(Native Method)
at sun.nio.ch.PipeImpl.<init>(PipeImpl.java:122)
at sun.nio.ch.SelectorProviderImpl.openPipe(SelectorProviderImpl.java:27)
at java.nio.channels.Pipe.open(Pipe.java:133)
at sun.nio.ch.WindowsSelectorImpl.<init>(WindowsSelectorImpl.java:104)
at sun.nio.ch.WindowsSelectorProvider.openSelector(WindowsSelectorProvider.java:26)
at java.nio.channels.Selector.open(Selector.java:209)
at org.apache.mina.transport.socket.nio.NioProcessor.<init>(NioProcessor.java:59)
... 8 more
Caused by: java.net.SocketException: No buffer space available (maximum connections reached?): connect
at sun.nio.ch.Net.connect(Native Method)
at sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:500)
at java.nio.channels.SocketChannel.open(SocketChannel.java:146)
at sun.nio.ch.PipeImpl$Initializer.run(PipeImpl.java:78)
... 16 more

看了下异常栈,看来是NIO 的Selector.open()方法调用的时候出现了问题,接下来就是google看别人是不是也遇到过这样的问题了,果然已经有很多人遇到过,具体的解释就如下文了:
原因是每个Selector都会打开一个loopback接连(就是自己连接到自己).在win上是TCP,在linux是pipe.loopback资源有限,打开的Selector一多自然就报错了.

如果要追述具体原因,这和Sun实现Selector的wakeup方法有关.JDK API里面这么写着的:

public abstract Selector wakeup()使尚未返回的第一个选择操作立即返回。
如果另一个线程目前正阻塞在 select() 或 select(long) 方法的调用中,则该调用将立即返回。如果当前未进行选择操作,那么在没有同时调用 selectNow() 方法的情况下,对上述方法的下一次调用将立即返回。在任一情况下,该调用返回的值可能是非零的。如果未同时再次调用此方法,则照常阻塞 select() 或 select(long) 方法的后续调用。

在两个连续的选择操作之间多次调用此方法与只调用一次的效果相同。


返回:
此选择器


当我们调用select()方法的时候是阻塞的.被阻塞的线程有三个方法被唤醒:

1) 有数据可读/写,或出现异常。

2) 阻塞时间到,即time out。

3) 收到一个non-block的信号。可由kill或pthread_kill发出。



所以,Selector.wakeup()要唤醒阻塞的select,那么也只能通过这三种方法,其中:

1)第二种方法可以排除,因为select一旦阻塞,应无法修改其time out时间。

2)而第三种看来只能在Linux上实现,Windows上没有这种信号通知的机制。

  所以,看来只有第一种方法了。再回想到为什么每个Selector.open(),在Windows会建立一对自己和自己的loopback的TCP连接;在Linux上会开一对pipe(pipe在Linux下一般都是成对打开),估计我们能够猜得出来——那就是如果想要唤醒select,只需要朝着自己的这个loopback连接发点数据过去,于是,就可以唤醒阻塞在select上的线程了。

具体到我的case, 每次我通过IoConnector.connect()建立和server的连接时,都会调用Selector.open()方法,这样一旦次数达到一定的数目,相应的资源就被消耗完了。
 类似资料: