SelectionKey类的使用
SelectionKey类标识SelectableChannel在选择器中的注册标记。
在每次向选择器注册通道时,就会创建一个 选择键(SelectionKey)。通过调用某个键的cancel()
方法、关闭其通道,或者通过关闭其选择器取消该键之前,通道一直保持有效。取消某个键不会立即从其选择器中移除它,而是将该键添加到选择器的已取消键集,以便在下一次进行select()方法操作时移除它。可通过调用某个键的isValid()
方法来测试其有效性。
选择键包含两个集,是表示为整数值的操作集,其中每一位都表示该键通道所支持的一类可选择操作:
interest集
:确定了下一次调用某个选择器的select()方法时,将测试哪类操作的准备就绪信息。创建该键时使用给定的值初始化interest集合,之后可通过interestOps(int)
方法对其进行更改。ready集
:标识了这样一类操作,即某个键的选择器检测到该键的通道已为此类操作准备就绪。在创建该键时,ready 集初始化为零,可以在之后的select()
方法操作中通过选择器对其进行更新,但不能直接更新它。
选择键的ready集指示,其通道对某个操作类别已准备就绪,该指示只是一个提示,并不保证线程可执行此类别中的操作而不发生线程阻塞。ready集很可能一完成选择操作就是准确的。ready集可能由于外部事件和在相应通道上调用的I/O操作而变得不准确。
SelectionKey类定义了所有已知的操作集位( operation-set bit),但是给定的通道具体支持哪些位则取决于该通道的类型。SelectableChannel 的每个子类都定义了validOps() 方法,该方法返回的集合恰好标识该通道支持的操作。试图设置或测试某个键的通道所不支持的操作集位将导致抛出相应的运行时异常。
通常必须将某个特定于应用程序的数据与某个选择键相关联,如表示高级协议状态的对象和为了实现该协议而处理准备就绪通知的对象。因此,选择键支持将单个任意对象附加到某个键的操作。可通过attach()
方法附加对象,然后通过attachment()
方法获取该对象。
多个并发线程可安全地使用选择键。一般情况下,读取和写人interest集的操作将与选择器的某些操作保持同步。具体如何执行该同步操作与实现有关:
- 在一般实现中,如果正在进行某个选择操作,那么读取或写入interest集可能会无限期地阻塞;
- 在高性能的实现中,可能只会暂时阻塞。无论在哪种情况下,
选择操作将始终使用该操作开始时的interest集值
。
选择器是线程安全的,而键集却不是!
判断是否允许连接SelectableChannel对象
public final boolean isAcceptable()
方法的作用是测试此键的通道是否已准备好接受新的套接字连接。调用此方法的形式为k.isAcceptable(),该调用与以下调用的作用完全相同: k.readyOps() & OP_ ACCEPT !=0
。如果此键的通道不支持套接字连接操作,则此方法始终返回false。返回值当且仅当readyOps() & OP_ ACCEPT
为非零值时才返回true。
public final boolean isConnectable()
方法的作用是测试此键的通道是否已完成其套接字连接操作。调用此方法的形式为k.isConnectable(),该调用与以下调用的作用完全相同: k.readyOps() & OP_ CONNECT != 0
。如果此键的通道不支持套接字连接操作,则此方法始终返回false。返回值当且仅当readyOps()&OP_CONNECT
为非零值时才返回true。
public abstract SelectableChannel channel()
方法的作用是返回为之创建此键的通道。即使已取消该键,此方法仍继续返回通道。
判断是否已准备好进行读取
public final boolean isReadable()
方法的作用是测试此键的通道是否已准备好进行读取。调用此方法的形式为k.isReadable(),该调用与以下调用的作用完全相同:k.readyOps()&OP_READ!=0
。如果此键的通道不支持读取操作,则此方法始终返回false。返回值当且仅当readyOps() & OP_ READ
为非零值时才返回true。
服务端:
public static void main(String[] args) {
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress("localhost", 8088));
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
SocketChannel socketChannel = null;
boolean isRun = true;
while (isRun) {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isAcceptable()) {
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
System.out.println("server isAcceptable()");
socketChannel = channel.accept();
socketChannel.configureBlocking(false);
//对socketChannel注册读的事件
socketChannel.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
System.out.println("server isReadable()");
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int readLength = socketChannel.read(byteBuffer);
while (readLength != -1) {
System.out.println(new String(byteBuffer.array(), 0, readLength));
readLength = socketChannel.read(byteBuffer);
}
socketChannel.close();
}
it.remove();
}
}
serverSocketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
客户端:
public static void main(String[] args) {
try {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress("localhost", 8088));
int keyCount = selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isConnectable()) {
//需要在此处使用finishConnect()方法完成连接,因为socketChannel是非阻塞模式
while (!socketChannel.finishConnect()) {
System.out.println("!socketChannel.finishConnect()-------------");
}
System.out.println("client isConnectable()");
SocketChannel channel = (SocketChannel) key.channel();
byte[] writeData = "我来自客户端,你好!服务器!".getBytes();
ByteBuffer byteBuffer = ByteBuffer.wrap(writeData);
channel.write(byteBuffer);
channel.close();
}
}
System.out.println("client end!");
} catch (IOException e) {
e.printStackTrace();
}
}
//服务端输出
server isAcceptable()
server isReadable()
我来自客户端,你好!服务器!
//客户端输出
client isConnectable()
client end!
判断是否已经准备好进行写入
public final boolean isWritable()
方法的作用是测试此键的通道是否已准备好进行写人。调用此方法的形式为k.isWritable(),该调用与以下调用的作用完全相同:k.readyOps()&OP_ WRITE !=0
。如果此键的通道不支持写人操作,则此方法始终返回false。 返回值当且readyOps()&OP_WRITE
为非零值时才返回true。
服务端与上面的一致。
客户端如下:
public static void main(String[] args) {
try {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress("localhost", 8088));
boolean isRun = true;
while (isRun) {
int keyCount = selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isConnectable()) {
System.out.println("client isConnectable()");
if (socketChannel.isConnectionPending()) {
while (!socketChannel.finishConnect()) {
System.out.println("!socketChannel.finishConnect()--------");
}
socketChannel.register(selector, SelectionKey.OP_WRITE);
}
}
if (key.isWritable()) {
System.out.println("client isWritable()");
ByteBuffer byteBuffer = ByteBuffer.wrap("我来自客户端,你好,服务端!".getBytes());
socketChannel.write(byteBuffer);
socketChannel.close();
}
}
}
System.out.println("client end!");
} catch (IOException e) {
e.printStackTrace();
}
}
//服务端输出
server isAcceptable()
server isReadable()
我来自客户端,你好,服务端!
//客户端输出
client isConnectable()
client isWritable()
返回SelectionKey关联的选择器
public abstract Selector selector()
方法的作用是返回SelectionKey关联的选择器。即使已取消该键,此方法仍将继续返回选择器。
在注册操作时传入attachment附件
SelectableChannel类中的public final SelectionKey register(Selector sel, int ops, Objectatt)
方法的作用是向给定的选择器注册此通道,返回一个选择键。如果当前已向给定的选择器注册了此通道,则返回表示该注册的选择键。该键的相关操作集将更改为ops,就像调用interestOps(int)方法一样。
- 如果att参数不为null,则将该键的附件设置为该值。如果已取消该键,则抛出CancelledKeyException异常。
- 如果尚未向给定的选择器注册此通道,则注册该通道并返回得到的新键。
该键的初始可用操作集是ops,并且其附件是att。可在任意时间调用此方法。如果调用此方法的同时正在进行另一个此方法或configureBlocking()方法的调用,则在另一个操作完成前将首先阻塞该调用。然后,此方法将在选择器的键集上实现同步
。因此,如果调用此方法时并发地调用了涉及同一选择器的另一个注册或选择操作,则可能阻塞此方法的调用。如果正在进行此操作时关闭了此通道,则此方法返回的键是已取消的,因此返回键无效。参数sel代表要向其注册此通道的选择器,ops代表所得键的可用操作集,att代表所得键的附件,attr参数可能为null。返回值表示此通道向给定选择器注册的键。
SelectionKey类中的public final Object attachment()
方法的作用是获取当前的附加对象。返回值代表当前已附加到此键的对象,如果没有附加对象,则返回null。
public static void main(String[] args) {
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress("localhost", 8888));
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
SocketChannel socketChannel = null;
boolean isRun = true;
while (isRun) {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isAcceptable()) {
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
System.out.println("server isAcceptable()");
socketChannel = channel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
System.out.println("server isReadable()");
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int readLength = socketChannel.read(byteBuffer);
while (readLength != -1) {
System.out.println(new String(byteBuffer.array(), 0, readLength));
readLength = socketChannel.read(byteBuffer);
}
socketChannel.close();
}
it.remove();
}
}
serverSocketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress("localhost", 8888));
boolean isRun = true;
while (isRun) {
int keyCount = selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isConnectable()) {
System.out.println("client isConnectable()");
if (socketChannel.isConnectionPending()) {
while (!socketChannel.finishConnect()) {
System.out.println("!socketChannel.finishConnect()----");
}
socketChannel.register(selector, SelectionKey.OP_WRITE, "我使用附件进行注册,我来自客户端,你好服务端");
}
}
if (key.isWritable()) {
System.out.println("client isWritable()");
ByteBuffer byteBuffer = ByteBuffer.wrap(((String)key.attachment()).getBytes());
socketChannel.write(byteBuffer);
socketChannel.close();
}
}
}
System.out.println("client end!");
} catch (IOException e) {
e.printStackTrace();
}
}
//服务端输出:
server isAcceptable()
server isReadable()
我使用附件进行注册,我来自客户端,你好服务端
//客户端输出:
client isConnectable()
client isWritable()
设置attachment附件
public final Object attach(Object ob)
方法的作用是将给定的对象附加到此键。之后可通过attachment()方法获取已附加的对象。一次只能附加一个对象。调用此方法会导致丟弃所有以前的附加对象。通过附加null可丢弃当前的附加对象。
参数ob代表要附加的对象,可以为null。返回值代表先前已附加的对象(如果有),否则返回null。
修改上面的客户端代码:
//服务端输出
server isAcceptable()
server isReadable()
使用attach(Object)进行注册,我来自客户端,你好服务端!
//客户端输出
client isConnectable()
client isWritable()
获取与设置此键的interest集合
public abstract int interestOps()
方法的作用是获取此键的interest集合。可保证返回的集合仅包含对于此键的通道而言有效的操作位。可在任意时间调用此方法。是否受阻塞,以及阻塞时间长短都是与实现相关的。返回值代表此键的interest集合。
public abstract SelectionKey interestOps(int ops)
方法的作用是将此键的interest集合设置为给定值。可在任意时间调用此方法。是否受阻塞,以及阻塞时间长短都是与实现相关的。参数ops代表新的interest 集合,返回值代表此选择键。
public static void main(String[] args) {
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
SocketChannel socketChannel1 = SocketChannel.open();
socketChannel1.configureBlocking(false);
SocketChannel socketChannel2 = SocketChannel.open();
socketChannel2.configureBlocking(false);
Selector selector = Selector.open();
SelectionKey key1 = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
SelectionKey key2 = socketChannel1.register(selector, SelectionKey.OP_CONNECT |
SelectionKey.OP_READ);
SelectionKey key3 = socketChannel2.register(selector, SelectionKey.OP_CONNECT |
SelectionKey.OP_READ | SelectionKey.OP_WRITE);
System.out.println(~key1.interestOps() & SelectionKey.OP_ACCEPT);
System.out.println(~key1.interestOps() & SelectionKey.OP_CONNECT);
System.out.println(~key1.interestOps() & SelectionKey.OP_READ);
System.out.println(~key1.interestOps() & SelectionKey.OP_WRITE);
System.out.println();
System.out.println(~key2.interestOps() & SelectionKey.OP_ACCEPT);
System.out.println(~key2.interestOps() & SelectionKey.OP_CONNECT);
System.out.println(~key2.interestOps() & SelectionKey.OP_READ);
System.out.println(~key2.interestOps() & SelectionKey.OP_WRITE);
System.out.println();
System.out.println(~key3.interestOps() & SelectionKey.OP_ACCEPT);
System.out.println(~key3.interestOps() & SelectionKey.OP_CONNECT);
System.out.println(~key3.interestOps() & SelectionKey.OP_READ);
System.out.println(~key3.interestOps() & SelectionKey.OP_WRITE);
System.out.println();
//使用interestOps(int ops)方法,重新定义感兴趣的事件
key3.interestOps(SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT);
System.out.println(~key3.interestOps() & SelectionKey.OP_ACCEPT);
System.out.println(~key3.interestOps() & SelectionKey.OP_CONNECT);
System.out.println(~key3.interestOps() & SelectionKey.OP_READ);
System.out.println(~key3.interestOps() & SelectionKey.OP_WRITE);
} catch (IOException e) {
e.printStackTrace();
}
}
0
8
1
4
16
0
0
4
16
0
0
0
16
0
1
0
判断此键是否有效
public abstract boolean isValid)()
方法的作用是告知此键是否有效。键在创建时是有效的,并在被取消、其通道已关闭或者其选择器已关闭之前保持有效。返回值当且仅当此键有效时才返回true。
public static void main(String[] args) {
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
SelectionKey key1 = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println(key1.isValid());
key1.cancel();
System.out.println(key1.isValid());
serverSocketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
获取此键的ready操作集合
public abstract int readyOps()
方法的作用是获取此键的ready操作集合,可保证返回的集合仅包含对于此键的通道而言有效的操作位,返回值代表此键的ready操作集合。
//服务端
public static void main(String[] args) {
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress("localhost", 8888));
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
SelectionKey selectionKey1 = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
SocketChannel socketChannel = null;
boolean isRun = true;
while (isRun) {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isAcceptable()) {
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
System.out.println("server isAcceptable() OP_ACCEPT result=" + (SelectionKey.OP_ACCEPT & ~key.readyOps()));
System.out.println("server isAcceptable() OP_CONNECT result=" + (SelectionKey.OP_CONNECT & ~key.readyOps()));
System.out.println("server isAcceptable() OP_READ result=" + (SelectionKey.OP_READ & ~key.readyOps()));
System.out.println("server isAcceptable() OP_WRITE result=" + (SelectionKey.OP_WRITE & ~key.readyOps()));
socketChannel = channel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
System.out.println("server isReadable() OP_ACCEPT result=" + (SelectionKey.OP_ACCEPT & ~key.readyOps()));
System.out.println("server isReadable() OP_CONNECT result=" + (SelectionKey.OP_CONNECT & ~key.readyOps()));
System.out.println("server isReadable() OP_READ result=" + (SelectionKey.OP_READ & ~key.readyOps()));
System.out.println("server isReadable() OP_WRITE result=" + (SelectionKey.OP_WRITE & ~key.readyOps()));
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int readLength = socketChannel.read(byteBuffer);
while (readLength != -1) {
System.out.println(new String(byteBuffer.array(), 0, readLength));
readLength = socketChannel.read(byteBuffer);
}
socketChannel.close();
}
it.remove();
}
}
serverSocketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//客户端
public static void main(String[] args) {
try {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress("localhost", 8888));
boolean isRun = true;
while (isRun) {
int keyCount = selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isConnectable()) {
System.out.println("server isConnectable() OP_ACCEPT result=" + (SelectionKey.OP_ACCEPT & ~key.readyOps()));
System.out.println("server isConnectable() OP_CONNECT result=" + (SelectionKey.OP_CONNECT & ~key.readyOps()));
System.out.println("server isConnectable() OP_READ result=" + (SelectionKey.OP_READ & ~key.readyOps()));
System.out.println("server isConnectable() OP_WRITE result=" + (SelectionKey.OP_WRITE & ~key.readyOps()));
if (socketChannel.isConnectionPending()) {
while (!socketChannel.finishConnect()) {
System.out.println("!socketChannel.finishConnect()-----");
}
socketChannel.register(selector, SelectionKey.OP_WRITE);
key.attach("我使用附件进行注册,我来自客户端,你好服务端!");
}
}
if (key.isWritable()) {
System.out.println("server isWritable() OP_ACCEPT result=" + (SelectionKey.OP_ACCEPT & ~key.readyOps()));
System.out.println("server isWritable() OP_CONNECT result=" + (SelectionKey.OP_CONNECT & ~key.readyOps()));
System.out.println("server isWritable() OP_READ result=" + (SelectionKey.OP_READ & ~key.readyOps()));
System.out.println("server isWritable() OP_WRITE result=" + (SelectionKey.OP_WRITE & ~key.readyOps()));
ByteBuffer byteBuffer = ByteBuffer.wrap(((String)key.attachment()).getBytes());
socketChannel.write(byteBuffer);
socketChannel.close();
key.cancel();
}
}
}
System.out.println("cllient end!");
} catch (IOException e) {
e.printStackTrace();
}
}
//服务端输出
server isAcceptable() OP_ACCEPT result=0
server isAcceptable() OP_CONNECT result=8
server isAcceptable() OP_READ result=1
server isAcceptable() OP_WRITE result=4
server isReadable() OP_ACCEPT result=16
server isReadable() OP_CONNECT result=8
server isReadable() OP_READ result=0
server isReadable() OP_WRITE result=4
我使用附件进行注册,我来自客户端,你好服务端!
//客户端输出
server isConnectable() OP_ACCEPT result=16
server isConnectable() OP_CONNECT result=0
server isConnectable() OP_READ result=1
server isConnectable() OP_WRITE result=4
server isWritable() OP_ACCEPT result=16
server isWritable() OP_CONNECT result=8
server isWritable() OP_READ result=1
server isWritable() OP_WRITE result=0
取消操作
public abstract void cancel()
方法的作用是请求取消此键的通道到其选择器的注册。一旦返回,该键就是无效的,并且将被添加到其选择器的已取消键集中。在进行下一次选择操作时,将从所有选择器的键集中移除该键。如果已取消了此键,则调用此方法无效。一旦取消某个键,SelectionKey.isValid) 方法返回false。可在任意时间调用cancel()方法。此方法与选择器的已取消键集保持同步,因此,如果通过涉及同一选择器的取消或选择操作并发调用它,则它可能会暂时受阻塞。