Ответ 1
Поскольку селектор никогда этого не делает, он только добавляет к набору, поэтому, если вы этого не сделаете, вы будете перерабатывать событие самостоятельно в следующий раз, когда Selector вернется.
Я нашел пример кода 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);
}
}
Когда он получает выбранные ключи, он удаляет ключ в цикле. Почему мы должны это делать?
UPDATE
Благодаря ответам EJP и user270349, я думаю, что сейчас понимаю, позвольте мне объяснить это подробно.
В селекторе есть 2 таблицы:
регистрационная таблица: при вызове channel.register
в нее будет добавлен новый элемент (ключ). Только если мы назовем key.cancel()
, он будет удален из этой таблицы.
готов к таблице выбора: когда мы вызываем selector.select()
, селектор будет искать таблицу регистрации, найти доступные ключи, скопировать ссылки на них в эту таблицу выбора. Элементы этой таблицы не будут очищаться с помощью селектора (это означает, что даже если мы снова назовем selector.select()
, он не очистит существующие элементы)
Вот почему мы должны вызывать iter.remove()
, когда мы получили ключ из таблицы выбора. Если нет, мы снова и снова получаем ключ selector.selectedKeys()
, даже если он не готов к использованию.
Поскольку селектор никогда этого не делает, он только добавляет к набору, поэтому, если вы этого не сделаете, вы будете перерабатывать событие самостоятельно в следующий раз, когда Selector вернется.
Потому что пока вы это сделаете, вы не сможете обнаружить новые повторения событий.
Ваш вопрос касается использования Iterator
:
https://www.tutorialspoint.com/java/java_using_iterator.htm
О Selector
и возвращенный метод Set<SelectionKey>
by selectedKeys()
:
[...] Набор выбранных клавиш - это набор клавиш, так что каждый канал канала было обнаружено, что оно готово для по меньшей мере одной из идентифицированных операций в ключевом вопросе, установленном во время предыдущей операции выбора [...]
https://docs.oracle.com/javase/7/docs/api/java/nio/channels/Selector.html
Вы можете использовать следующий код для перебора "готовых" каналов:
while (true) {
selectorRead.select();
for (SelectionKey key : selectorRead.selectedKeys()) {
DatagramChannel dc = (DatagramChannel) key.channel();
}
}
Поскольку удаление ключа из выбранного набора, так как оно обрабатывается, оно будет ждать следующего события выбора.
выбранный набор содержит ключи готовых каналов.
selector.select(); //This may block for a long time. Upon returning, the elected set contains keys of the ready channels.
Получить итератор по набору выбранных ключей и выполнить бизнес-информацию
Iterator it = selector.selectedKeys().iterator( );
Наконец, удалите ключ из выбранного набора; он был обработан
it.remove( );
Ключи могут быть удалены непосредственно из этого набора, но не добавлены. Попытка добавить к выбранному набору ключей вызывает java.lang.UnsupportedOperationException.
если вы не вызываете метод удаления, вы обнаружите, что он также работает нормально.
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();
}
}
просто распечатайте и протестируйте его. Вы обнаружите, что даже вы не удаляете, используйте key.isAcceptable() может фильтровать результат в виде корня