Java 为什么我的纯NIO selectKey仍然选择了事件

Java 为什么我的纯NIO selectKey仍然选择了事件,java,nio,Java,Nio,我是NIO初学者 假设我有一个NIO服务器,如: package org.example.nio.selectordemo2; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.Iterator; import java.util.Set; public class NIOServer { public static v

我是NIO初学者

假设我有一个NIO服务器,如:

package org.example.nio.selectordemo2;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {
    public static void main(String[] args) throws Exception{
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        Selector selector = Selector.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        int loopCount = 0;
        while (true) {
            if (++loopCount == 20) break;
            if(selector.select(2000) == 0) {
                continue;
            }
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                if(key.isAcceptable()) {
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if(key.isReadable()) {
                    SocketChannel channel = (SocketChannel)key.channel();
                    ByteBuffer buffer = (ByteBuffer)key.attachment();
                    channel.read(buffer);
                    System.out.println("Receive message:" + new String(buffer.array()));
                }
                keyIterator.remove();
            }
        }
    }
}
然而,让我感到奇怪的是,结果是:

显然,问题出在我们身上

Set<SelectionKey> selectionKeys = selector.selectedKeys();
Set selectionKeys=selector.selectedKeys();
但是我不知道为什么,或者我应该如何处理。您需要阅读,这是ByteBuffer的超类。您完全忽略了所有缓冲区中最重要的属性之一:缓冲区

每个缓冲区(包括ByteBuffers)保持一个位置。位置是ByteBuffer中的一个索引,用于确定将数据添加到缓冲区的位置,以及从缓冲区中读取数据的位置

调用
channel.read(缓冲区)时,它从通道读取字节,并将它们添加到字节缓冲区的当前位置添加最后一个字节后,位置将更新为索引。

这是您第一次呼叫channel.read后的ByteBuffer状态:

Sending A Message....␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀
          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123456789
                     ↑
                     position = 21
Sending A Message....Sending A Message....␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀
          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123456789
                                          ↑
                                          position = 42
Sending A Message....Sending A Message....Sending A Message....␀␀␀␀␀␀␀
          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123456789
                                                               ↑
                                                               position = 63
Sending A Message....␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀
          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123456789
                     ↑
                     position = 21
这是您第二次呼叫channel.read后ByteBuffer的状态:

Sending A Message....␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀
          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123456789
                     ↑
                     position = 21
Sending A Message....Sending A Message....␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀
          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123456789
                                          ↑
                                          position = 42
Sending A Message....Sending A Message....Sending A Message....␀␀␀␀␀␀␀
          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123456789
                                                               ↑
                                                               position = 63
Sending A Message....␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀
          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123456789
                     ↑
                     position = 21
这是您第三次呼叫channel后的ByteBuffer状态。请阅读:

Sending A Message....␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀
          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123456789
                     ↑
                     position = 21
Sending A Message....Sending A Message....␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀
          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123456789
                                          ↑
                                          position = 42
Sending A Message....Sending A Message....Sending A Message....␀␀␀␀␀␀␀
          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123456789
                                                               ↑
                                                               position = 63
Sending A Message....␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀
          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123456789
                     ↑
                     position = 21
如您所见,数组始终包含原始消息不要使用buffer.array();它忽略缓冲区的位置

从通道读入ByteBuffer后,获取刚刚放置在ByteBuffer中的数据的正确方法是读取它。对于字符数据,则需要对其进行修改

flip()
将把字节缓冲区的位置移动到字节缓冲区的开头,因此下次从该字节缓冲区读取时,您将读取刚刚通过channel.read放入其中的数据

在channel.read之后:

Sending A Message....␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀
          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123456789
                     ↑
                     position = 21
Sending A Message....Sending A Message....␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀
          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123456789
                                          ↑
                                          position = 42
Sending A Message....Sending A Message....Sending A Message....␀␀␀␀␀␀␀
          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123456789
                                                               ↑
                                                               position = 63
Sending A Message....␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀␀
          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123456789
                     ↑
                     position = 21
在buffer.flip()之后

如果在调用flip()之后,您要调用buffer.get(),它将读取字节
'S'
,并将字节缓冲区的位置提前1。如果不调用flip(),下次尝试读取字节时将返回0,因为这是位置21处的字节值

但是你不想直接从ByteBuffer阅读;您希望将其内容转换为字符串。目前,您正在将字节传递给字符串构造函数,这是不明智的,因为它将使用默认字符集。如果服务器运行在Windows上,而客户端未运行Windows(反之亦然),则任何非ASCII字符都将损坏

在字节和字符之间转换的正确方法是使用a,特别是它的方法。Java在类中定义了几个常见的字符集。大多数时候,这是最好的选择

因此,使用标准字符集的
decode
方法如下所示:

channel.clear();
channel.read(buffer);
channel.flip();
String message = StandardCharsets.UTF_8.decode(buffer).toString();

您需要在下次读取时将ByteBuffer设置回零,因为您总是希望下一个channel.read操作将字节放在ByteBuffer中的位置0。最简单的方法是使用ByteBuffer的方法,正如您在上面的代码中看到的那样。clear()还将设置缓冲区当前容量的限制(即允许的最大位置)。

您没有正确使用ByteBuffer。在将其传递到channel.read之前,您需要先读取它,然后再将其传递到字符串,而不是读取底层数组。您好@VGR,感谢您的命令,据我所知,socketChannel.write()只接受bytebuffer参数,因此在将其传递到channel read之前,我无法清除它。所以把它解码成一个字符串是没有意义的。或者你会相应地写下你的代码供我参考吗?socketChannel.write()与此无关。我说的是做
buffer.clear()channel.read(缓冲区)前的code>。第一次执行channel.read时,缓冲区的位置将被移动;在此之后,除非在后续读取之前清除缓冲区,否则缓冲区中可能没有空间。是的,缓冲区可以相应地解码。但是,它仍然无法解决这个问题。由于OP_READ事件未清除,因此无休止循环无法中断,因此选择器始终在那里获取事件。你可以试试你的本地电脑,我试过了。服务器收到一条消息,然后收到几条空消息,然后退出。嗨,VGR,请记住停止客户端进程。然后您将看到一些空消息。关键是,我假设在使用客户机之后,选择器不应该再拥有该事件。我的意思是,在消耗之后,selector.select(2000)应该等于0。(因为现在没有更多的客户端连接)From:“如果选择器检测到相应的通道已准备好读取,已到达流的末尾,已远程关闭以进行进一步读取,或有一个错误挂起,则它将向密钥的就绪操作集添加
OP_READ
。”