选择器基础

选择器提供选择执行已经就绪的任务的能力。就绪选择和多元执行使得单线程能够有效率的同时管理多个I/O通道。

选择器:选择器管理着一个被注册的通道集合的信息和他们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。

可选择通道:这个抽象类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。

选择键:选择键封装了特定的通道与特定的选择器的注册关系。

调用可选择通道的register()方法会将它注册到一个选择器上,但是这个通道必须是开启的并且处于非阻塞状态的。一个给定的通道可以注册到多于一个的选择器上。选择器是提供选择功能的对象,选择器对象对注册到它之上的通道进行就绪选择,并管理选择键。

创建选择器并将通道注册到选择器的方法:

1
2
3
4
5
6
7
//创建一个选择器
Selector selector=Selector.open();
channel1.register(selector,SelectionKey.OP_READ);
channel2.register(selector,SelectionKey.OP_WRITE);
channel3.register(selector,SelectionKey.OP_READ|SelectionKey.OP_WRITE);
//等待十秒直到其中一个通道就绪或者十秒钟过去则结束线程休眠
int select = selector.select(10000);

值得注意的是register方法的第二个参数是指选择器所关注的通道的操作。它有四种可选择的操作,分别是读(read)、写(write)、连接(connnet)和接受(accept)。不是所有的通道都支持这些操作,可以使用validOps()方法来获取指定通道所支持的操作的集合。
还有一种注册方法SelectionKey register(Selector sel, int ops, Object att) 它会返回一个选择键,通道调用选择键的readyOps()方法来后去相应的通道已经就绪的操作。并且这种注册方法允许在键上放置一个附件对象,使得这个附件对象和键关联。它等价于selectionKey.attack(Object)
选择器使用完毕后可以调用close()方法来释放它。

使用选择器

选择器维护着注册过的通道的集合,并且将这些注册关系封装到SelectionKey对象中。每一个selector对象维护着三个集合。分别是已注册的键的集合,已选择的键的集合,以取消的键的集合。

  1. wakeup()
    wakeup()方法。使用该方法可以使选择器上的第一个还没有返回的选择操作立即返回。如果当前没有进行选择,那么下一次对select()方法的调用将会立即返回。为了避免调用wakeup方法后可能影响下一次select()方法的调用,可以使用selectNow()方法来绕过这个问题。
  2. close()
    调用该方法会唤醒所有的被阻塞的线程,并注销所有与选择器关联的通道。
  3. interrupt()
    如果睡眠中的线程调用该方法,它的返回状态

//一个简单的服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public static void main(String[] args) throws IOException {
init();
}

private static void init() throws IOException {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(1234));
//设置为非阻塞模式
ssc.configureBlocking(false);
//将其注册到选择器
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
//轮询选择器根据就绪状态进行处理
while (true) {
//本次查询处于就绪状态的通道数
int n = selector.select(10000);
System.out.println(n);

if (n == 0) {
continue;
}
Iterator iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = (SelectionKey) iterator.next();
if (key.isAcceptable()) {
//有可连接的通道进入,就建立通道进行连接,并监听它的可读状态
System.out.println("可被连接的状态");
//如果处于可收取数据状态
ServerSocketChannel server = (ServerSocketChannel) key.channel();
//获取连接到此通道的连接的通道,以此进行数据的传输
SocketChannel channel = server.accept();
//设置为非阻塞模式
channel.configureBlocking(false);
//将该通道注册到选择器
channel.register(selector, SelectionKey.OP_READ);
//向客户端发送hello
String hello = "hello";
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(hello.getBytes());
buffer.flip();
channel.write(buffer);

}
if (key.isReadable()) {
//可读状态的通道准备就绪
System.out.println("处于可读的状态");
//如果处于可读状态
SocketChannel socketChannel = (SocketChannel) key.channel();
byte[] bytes = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap(bytes);
int count;
while ((count = socketChannel.read(buffer)) > 0) {
buffer.flip();
while (buffer.hasRemaining()) {
socketChannel.read(buffer);

}
System.out.println(new String(bytes).trim());
buffer.clear();
}
if (count < 0 ) {
socketChannel.close();
}

}
iterator.remove();
}

}
}