example - ¿Por qué debería eliminarse la clave en `selector.selectedKeys(). Iterator()` en java nio?
java io (4)
Encontré un código de muestra de java nio:
ServerSocketChannel server = ServerSocketChannel.open();
Selector selector = Selector.open();
server.socket().bind(new InetSocketAddress(8080));
server.configureBlocking(false);
server.register(selector, SelectionKey.OP_ACCEPT);
while(true) {
selector.select();
Iterator iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey key = (SelectionKey) iter.next();
iter.remove(); // Why remove it?
process(key);
}
}
Cuando obtiene las claves seleccionadas, elimina la clave en el bucle. ¿Por qué deberíamos hacer esto?
ACTUALIZAR
Gracias a las respuestas proporcionadas por EJP y user270349 , creo que lo entiendo ahora, déjeme explicarlo en detalle.
Hay 2 tablas en el selector:
tabla de registro: cuando llamamos a
channel.register
, habrá un nuevo elemento (clave) en él. Solo si llamamos akey.cancel()
, se eliminará de esta tabla.listo para la tabla de selección: cuando llamemos
selector.select()
, el selector buscará la tabla de registro, buscará las claves disponibles, copiará las referencias de ellas en esta tabla de selección. Los elementos de esta tabla no serán borrados por selector (eso significa que, incluso si llamamos aselector.select()
nuevamente, no borrará los elementos existentes)
Es por eso que tenemos que invocar iter.remove()
cuando obtuvimos la clave de la tabla de selección. Si no, obtendremos la clave una y otra vez por selector.selectedKeys()
incluso si no está listo para usar.
Debido a que al eliminar la clave del conjunto seleccionado, como se ha manejado, esperará el próximo evento de selección.
El conjunto seleccionado contiene claves de los canales listos.
selector.select(); //This may block for a long time. Upon returning, the elected set contains keys of the ready channels.
Obtenga un iterador sobre el conjunto de claves seleccionadas y realice la información comercial
Iterator it = selector.selectedKeys().iterator( );
Finalmente eliminar la clave del conjunto seleccionado; ha sido manejado
it.remove( );
Las claves se pueden eliminar directamente de este conjunto, pero no se pueden agregar. El intento de agregar al conjunto de claves seleccionado arroja java.lang.UnsupportedOperationException.
Debido a que el Selector nunca hace eso, solo se agrega al conjunto, por lo que si no lo hace, volverá a procesar el evento la próxima vez que el Selector regrese.
Porque hasta que lo hagas no podrás detectar nuevas repeticiones de eventos.
Si no invocas el método de eliminación, verás que también funciona bien.
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@SuppressWarnings({ "unchecked" })
public class NioServer {
ServerSocketChannel serverChannel;
ServerSocket serverSocket;
public final int port;
private Selector selector;
ByteBuffer buffer = ByteBuffer.allocate(1024);
NioServer(final int port) {
this.port = port;
}
void init() throws Exception {
// 创建 ServerSocketChannel、ServerSocket
serverChannel = ServerSocketChannel.open();
serverSocket = serverChannel.socket();
serverSocket.bind(new InetSocketAddress(port));
// 设置通道为非阻塞模式
serverChannel.configureBlocking(false);
// 开启通道选择器,并注册 ServerSocketChannel
selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
}
void go() throws Exception {
while (true) {
int num = selector.select();
if (num <= 0)
continue;
Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
System.out.println(selector.selectedKeys().size()); //等于线程数量
while (keyIter.hasNext()) {
final SelectionKey key = keyIter.next();
System.out.println("所有的key"+key);
// 接收一个Socket连接
// key.isAcceptable()如果为true,说明channnel支持accept(),也就是说明是一个ServerSocketChannel
if (key.isAcceptable()) {
System.out.println("可以连接的key:"+key);
SocketChannel clientChannel = serverChannel.accept();
if (clientChannel != null) {
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
}
// 如果isReadable()为true,说明是一个SocketChannel
if (key.isReadable()) {
String requestContent = read(key);
// 业务处理
// responseContent=doSomthing(requestContent);
write(key, "ok" /* responseContent */);
}
// keyIter.remove();
}
}
}
// 从通道读取数据
String read(SelectionKey key) throws Exception {
SocketChannel socketChannel = (SocketChannel) key.channel();
buffer.clear();// 这一步必须有
int len = 0;
StringBuffer str = new StringBuffer();
while ((len = socketChannel.read(buffer)) > 0) {
byte[] bs = buffer.array();
String block = new String(bs, 0, len);
System.out.println("Server read: " + block);
str.append(block);
}
buffer.clear();
return str.toString();
}
// 写数据到通道
void write(SelectionKey key, String str) throws Exception {
SocketChannel socketChannel = (SocketChannel) key.channel();
buffer.clear();
buffer.put(str.getBytes());
buffer.flip();// 这一步必须有
socketChannel.write(buffer);
}
public static void main(String[] args) throws Exception {
final int port = 10000;
NioServer server = new NioServer(port);
server.init();
/// ========================================================
// 接下来模拟3个Client并发访问服务器
int poolsize = 1;
ExecutorService pool = Executors.newFixedThreadPool(poolsize);
Collection<Callable> tasks = new ArrayList<Callable>(10);
final String clientname = "clientThread";
for (int i = 0; i < poolsize; i++) {
final int n = i;
// 若每一个Client都保持使用BIO方式发送数据到Server,并读取数据。
tasks.add(new Callable() {
@Override
public Object call() throws Exception {
Socket socket = new Socket("127.0.0.1", port);
final InputStream input = socket.getInputStream();
final OutputStream out = socket.getOutputStream();
final String clientname_n = clientname + "_" + n;
// BIO读取数据线程
new Thread(clientname_n + "_read") {
@Override
public void run() {
byte[] bs = new byte[1024];
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int len = 0;
try {
while ((len = input.read(bs)) != -1) {
System.out.println("Clinet thread " + Thread.currentThread().getName()
+ " read: " + new String(bs, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
// BIO写数据线程
new Thread(clientname_n + "_write") {
@Override
public void run() {
int a = 0;
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
String str = Thread.currentThread().getName() + " hello, " + a;
try {
out.write(str.getBytes());
a++;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
return null;
}
});
}
pool.invokeAll((Collection<? extends Callable<Object>>) tasks);
server.go();
}
}
simplemente imprímalo y pruébelo. Encontrará que incluso no lo elimina, use key.isAcceptable () puede filtrar el resultado correcto