Selector选择器介绍
其实,在Nio中,有三位大哥,他们分别是 选择器,通道,缓冲区。这三位是重量级人物,前面我们已经讲了通道,缓冲区这两位大哥。现在说说选择器。
JAVA NIO和JAVA BIO最大的区别就是BIO一个线程对应一个客户端,而NIO则可以对应多个。处理多个客户端的链接,就要用到大哥 Selector。
这位大哥的牛逼之处在于可以同时管理多个通道,怎么管理的,通过事件。如果有事件发生,那么就会去处理相应事件。一个线程足以搞定多个通道。也就是管理多个连接和请求。
只有在通道真正有事件发生的时候才去处理。
Selector图解
多路复用,同时可以处理成千上万的链接。
当前Socket处于空闲状态,线程可以处理其他的连接。
Selector类描述
open() // 得到一个监视器对象
select(long timeout) // 监控所有的通道
Set<SelectionKey> selectedKeys() // 获得所有的selectedKey
Selector wakeup(); // 唤醒Selector
int selectNow(); // 不阻塞 立刻返回
Nio网络编程原理
相关的Selecor、SelectorKey、ServerSocketChannel和SocketChannel关系图。
当有客户端来了,会通过ServerSocketChannel得到SocketChannel
Selector的select方法可以获得通道个数(有事件发生的通道)
将SocketChannel注册到Selector上,有专门注册的方法
注册完事之后会返回一个SelectionKey,和这个Selector联合
通过这个key,可以获得SocketChannel。(channel方法)
放代码
事实上,学习任何知识,动手出真章。所以我决定动手写一个服务器端和客户端的通讯来磨练一下理论。打磨打磨。
Server端:
public class NioServer {
static ServerSocketChannel serverSocketChannel;
static Selector selector;
public static void main(String[] args) throws Exception {
// 创建一个ServerSocketChannel对象
serverSocketChannel = ServerSocketChannel.open();
// 得到一个Selector对象
selector = Selector.open();
// 绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
// 设置非阻塞 默认是阻塞
serverSocketChannel.configureBlocking(false);
// 把serverSocketChannel注册到selector 事件为OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true){
if(selector.select() == 0){ // 无事件发生
continue;
}
// 获得所有的selectionKeys
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> var2 = selectionKeys.iterator();
while (var2.hasNext()){
SelectionKey key = var2.next();
if(key.isAcceptable()){
// 客户端连接 生成了一个SocketChannel
SocketChannel accept = serverSocketChannel.accept();
accept.configureBlocking(false); // 设置非阻塞
// 关联到Selector 事件为读 并且关联一个Buffer
accept.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if(key.isReadable()){
// 获得SocketChannel
SocketChannel channel = (SocketChannel) key.channel();
channel.read((ByteBuffer) key.attachment());
System.out.println("来自于客户端:" + new String(((ByteBuffer) key.attachment()).array()));
}
// 完事记得删除
var2.remove();
}
}
}
}
客户端
public class NioClient {
static SocketChannel channel;
public static void main(String[] args) throws Exception {
channel = SocketChannel.open();
// 非阻塞
channel.configureBlocking(false);
// 连接到服务端地址
InetSocketAddress interfaceAddress = new InetSocketAddress("127.0.0.1",9999);
if(!channel.connect(interfaceAddress)){
while (!channel.finishConnect()){
System.out.println("------正在玩命连接中------");
}
String str = "欢迎您,尊敬的召唤师!";
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
channel.write(byteBuffer);
}
TimeUnit.SECONDS.sleep(300);
}
}
SelectorKey
表示和Selector的链接关系
OP_ACCEPT 表示有新的链接可以ACCEPT 16
OP_CONNECT 表示链接已经建立了 8
OP_READ 表示读 1
OP_WRITE 表示写 4
public abstract class SelectionKey {
// 得到关联的通道
protected SelectionKey() { }
// 得到关联的Selector
public abstract Selector selector();
/**
* Tells whether or not this key is valid.
*
* <p> A key is valid upon creation and remains so until it is cancelled,
* its channel is closed, or its selector is closed. </p>
*
* @return <tt>true</tt> if, and only if, this key is valid
*/
public abstract boolean isValid();
/**
* Requests that the registration of this key's channel with its selector
* be cancelled. Upon return the key will be invalid and will have been
* added to its selector's cancelled-key set. The key will be removed from
* all of the selector's key sets during the next selection operation.
*
* <p> If this key has already been cancelled then invoking this method has
* no effect. Once cancelled, a key remains forever invalid. </p>
*
* <p> This method may be invoked at any time. It synchronizes on the
* selector's cancelled-key set, and therefore may block briefly if invoked
* concurrently with a cancellation or selection operation involving the
* same selector. </p>
*/
public abstract void cancel();
// -- Operation-set accessors --
/**
* Retrieves this key's interest set.
*
* <p> It is guaranteed that the returned set will only contain operation
* bits that are valid for this key's channel.
*
* <p> This method may be invoked at any time. Whether or not it blocks,
* and for how long, is implementation-dependent. </p>
*
* @return This key's interest set
*
* @throws CancelledKeyException
* If this key has been cancelled
*/
public abstract int interestOps();
/**
* Sets this key's interest set to the given value.
*
* <p> This method may be invoked at any time. Whether or not it blocks,
* and for how long, is implementation-dependent. </p>
*
* @param ops The new interest set
*
* @return This selection key
*
* @throws IllegalArgumentException
* If a bit in the set does not correspond to an operation that
* is supported by this key's channel, that is, if
* {@code (ops & ~channel().validOps()) != 0}
*
* @throws CancelledKeyException
* If this key has been cancelled
*/
public abstract SelectionKey interestOps(int ops);
/**
* Retrieves this key's ready-operation set.
*
* <p> It is guaranteed that the returned set will only contain operation
* bits that are valid for this key's channel. </p>
*
* @return This key's ready-operation set
*
* @throws CancelledKeyException
* If this key has been cancelled
*/
public abstract int readyOps();
// 得到关联的数据
public final Object attachment() {
return attachment;
}
ServerSocketChannel
服务器端监听新的客户端连接
// 接受一个连接 返回代表连接的对象
public abstract SocketChannel accept() throws IOException;
SocketChannel就和通道读写,没什么可啰嗦的。
结束语
其实很简单。
ServerSocketChannel是甲方老大。所有的SocketChannel是厂商的工具人。工具人向甲方注册,甲方给个狗牌(SelectionKey)。每个工具人都应该有狗牌,通过你的狗牌,我就可以找到你。工具人和甲方老大对接。甲方可以知道工具人的数量,并可以一个个遍历处理,工具人干完事了,就移出去。就这逻辑,没别哒。
再见再见再见再见再见。