Java NIO - Selector
Selector能够检测多个注册的通道上是否有事件发生(多个channel以事件的方式可以注册到同一个selector)。如果有事件发生,便获取事件,然后对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求
Selector类
Selector类是一个抽象类,说明如下:
public abstract class Selector implements Closeable
一些方法:
1.public static Selector open()
- 得到一个选择器对象
2.public abstract int select(long timeout)
- 监控所有注册的通道,当其中有IO操作可以进行时,将对应的SelectionKey
加入到内部集合中并返回,参数用来设置超时时间
其方法说明如下:
Selects a set of keys whose corresponding channels are ready for I/O operations.
This method performs a blocking selection operation. It returns only after at least one channel is selected, this selector’s wakeup method is invoked, the current thread is interrupted, or the given timeout period expires, whichever comes first.
This method does not offer real-time guarantees: It schedules the timeout as if by invoking the Object.wait(long) method.
Params:
timeout – If positive, block for up to timeout milliseconds, more or less, while waiting for a channel to become ready; if zero, block indefinitely; must not be negative
Returns:
The number of keys, possibly zero, whose ready-operation sets were updated
Throws:
IOException – If an I/O error occurs
ClosedSelectorException – If this selector is closed
IllegalArgumentException – If the value of the timeout argument is negative
该方法执行一个blocking selection操作,直至至少一个channel被selected
而public abstract int selectNow()
则是非阻塞的
总结下:
selector.select()
- 阻塞selector.select(1000)
- //阻塞1000毫秒,在1000毫秒后返回selector.wakeup()
- 唤醒selectorselector.selectNow()
- 不阻塞,立马返还
3.public abstract Set<SelectionKey> selectedKeys()
- 从内部集合中得到所有的SelectionKey
如何理解呢?
Selector是与Thread关联的,Selector调用select
方法,这个方法就会返回一个集合(一个SelectionKey的集合),通过SelectionKey知道那个事件发生了,通过SelectionKey取到对应的channel来操作
NIO中的ServerSocketChannel
功能类似于ServerSocket
,SocketChannel
功能类似于Socket
NIO非阻塞网络编程原理分析图
- 当客户端连接时,会通过
ServerSocketChannel
得到对应的SocketChannel
- 将得到的
SocketChannel
注册到Selector
上,一个Selector
可以注册多个SocketChannel
- 注册后返回一个
SelectionKey
,会和该Selector
关联 Selector
进行监听(select方法),会返回有事件发生的通道的个数- 进一步得到各个
SelectionKey
(有事件发生的) - 再通过
SelectionKey
反向获取注册的SocketChannel
- 可以通过的channel,完成业务处理
SelectionKey
SelectionKey表示Selector和网络通道的注册关系,共四种:
public static final int OP_READ = 1 << 0; //代表读操作
public static final int OP_WRITE = 1 << 2; //代表写操作
public static final int OP_CONNECT = 1 << 3; //代表连接已经建立
public static final int OP_ACCEPT = 1 << 4; //有新的网络连接可以accept
一些方法:
public abstract Selector selector()
- 得到与之关联的Selector对象public abstract SelectableChannel channel()
- 得到与之关联的通道public final Object attachment()
- 得到与之关联的共享数据public abstract SelectionKey interestOps(int ops)
- 设置或改变监听事件public final boolean isAcceptable()
- 是否可以acceptpublic final boolean isReadable()
- 是否可以读public final boolean isWritable()
- 是否可以写
ServerSocketChannel
ServerSocketChannel在服务端监听新的客户端Socket连接
相关方法:
1.public static ServerSocketChannel open()
- 得到一个ServerSocketChannel通道
2.public final ServerSocketChannel bind(SocketAddress local)
- 设置服务器端端口号
3.public final SelectableChannel configureBlocking(boolean block)
- 设置阻塞或非阻塞模式,取值false表示采用非阻塞模式
4.public abstract SocketChannel accept()
- 接受一个连接,返回代表这个连接的通道对象
5.public final SelectionKey register(Selector sel, int ops)
- 注册一个选择器并设置监听事件
SocketChannel
SocketChannel,网络IO通道,具体负责进行读写操作。NIO把缓冲区的数据写入通道,或者把通道里的数据读到缓冲区
1.public static SocketChannel open()
- 得到一个SocketChannel通道
2.public final SelectableChannel configureBlocking(boolean block)
- 设置阻塞或非阻塞模式,取值false表示采用非阻塞模式
3.public abstract boolean connect(SocketAddress remote)
- 连接服务器
4.public abstract boolean finishConnect()
- 如果上面的方法连接失败,接下来就要通过该方法完成连接操作
5.public abstract int write(ByteBuffer src)
- 往通道里写数据
6.public abstract int read(ByteBuffer dst)
- 从通道里读数据
7.public final SelectionKey register(Selector sel, int ops, Object att)
- 注册一个选择器并设置监听事件,最后一个参数可以设置共享数据
8.public final void close()
- 关闭通道
NIO非阻塞网络编程入门
按照上面的流程,实现一个服务端,一个客户端,如下:
服务端:
public class NIOServer {
public static void main(String[] args) throws Exception {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//得到一个Selector对象
Selector selector = Selector.open();
//绑定一个端口,在服务端监听
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
//设置为非阻塞监听
serverSocketChannel.configureBlocking(false);
//把serverSocketChannel注册到selector,关系事件为OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//循环等待客户端连接
while (true) {
//等待1s,如果没有事件发生
if (selector.select(1000) == 0) {//没有事件发生
System.out.println("服务器等待了1s,无连接");
continue;
}
//如果返回>0,就获取到相关的SelectionKey集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//通过selectionKeys 反向获取通道
//遍历
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()) {
//获取到selectionKey
SelectionKey key = keyIterator.next();
//根据key对应的通道做相应的处理
if (key.isAcceptable()) {//如果是OP_ACCEPT
//给该客户端生成一个SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
//将socketChannel设置为非阻塞
socketChannel.configureBlocking(false);
System.out.println("客户端连接成功 生成了一个 socketChannel " + socketChannel.hashCode());
//将socketChannel注册到selector,关注事件为SelectionKey.OP_READ,同时给socketChannel关联一个Buffer
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if (key.isReadable()) {//发生了OP_READ事件
//通过key 反向获取对应的Channel
SocketChannel channel = (SocketChannel)key.channel();
//获取到该channel关联的buffer
ByteBuffer buffer = (ByteBuffer)key.attachment();
channel.read(buffer);
System.out.println("from 客户端 " + new String(buffer.array()));
}
//手动从集合中移除当前的selectionKey,放置重复操作
keyIterator.remove();
}
}
}
}
客户端
public class NIOClient {
public static void main(String[] args) throws Exception{
//得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
//设置非阻塞模式
socketChannel.configureBlocking(false);
//提供服务器端的ip和端口
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
//连接服务器
if (!socketChannel.connect(inetSocketAddress)) {
while (!socketChannel.finishConnect()) {
System.out.println("客户端连接需要时间,客户端不会阻塞,可以做其他工作");
}
}
//如果连接成功,就发送字符串
String str = "Hello, I am client";
//Wraps a byte array into a buffer.
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
//发送数据,将buffer数据写入channel
socketChannel.write(byteBuffer);
//代码停在这儿
System.in.read();
}
}
客户端向服务端发送数据: