Selector
selector作用
Selector中可以注册对应的事件(如接收请求、接收数据)的处理;
Selector类可以避免使用非阻塞式客户端中浪费资源忙等待,提高效率;
Selector可以管理多个Channel,监听多个Channel中的事件;
在没有引入Selector的情况下要进行一系列空循环非空判断,如果引入了selector后selector中有select方法可以进行阻塞;
Selector常识
将多个Channel注册到Selector上
将管道设置成非阻塞的,注册到Selector上,这个管道必须是SelectableChannel类型,所以FileChannel是不能注册到Selector上的;
Channel注册到Selector上,Selector会有关心的就绪事件,如果有多个关心的事件可以使用位运算符(|)把你关心的操作连接起来:
//SelectionKey.OP_CONNECT, 指某个通道连接到服务器
//SelectionKey.OP_ACCEPT, 只有SeverSocktChannel有这个事件,查看是否有新的连接
//SelectionKey.OP_READ, 是否有可读的通道就绪
//SelectionKey.OP_WRITE, 写数据的通道是否就绪
注册完会返回一个SelectionKey对象,这个选择键表示一个通道与一个选择器之间的注册关系;可以通过SelectionKey获得对应的Channel和Selector,也可以解除Channel和Selector的对应关系,判断两者是否有对应关系,也可以返回你关心的操作(用来判断是连接还是读写);
Selector 选择器维护着注册过的通道集合,维护着三个集合
1、已注册的键的集合: keys()方法返回这个已注册的键的集合, 这个集合不能修改
2、没有移除的键的集合:selectedKeys()方法返回, 该集合中的每个成员都是相关的通道被选择器判断已经准备好的, 并且包含了键的 interest 集合中的操作, 键可以从集合中移除,不能添加
3、已取消的键的集合:这个集合包含了调用过 cancel()方法的键
Selector使用
首先创建一个Selector,创建一个Channel设置成非阻塞,将Channel注册到Selector上,注册完判断是否有就绪的通道(如果没有就绪通道会阻塞),如果有则根据返回的SelectionKey 根据对应事件进行相应处理;
package com.lizhiyu.com;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
public class Server {
public static void main(String[] args) throws IOException {
//创建ServerSocketChannel
ServerSocketChannel ssc=ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(8888));
//设置成阻塞,如果不设置成阻塞则会抛异常
ssc.configureBlocking(false);
//创建Selector
Selector selector = Selector.open();
//Register()方法将Channel注册到选择器中
//第一个参数表示通道注册的选择器
//第二个参数表示关心通道的哪个操作
//第二个参数值含义
// SelectionKey.OP_CONNECT, 指某个通道连接到服务器
// SelectionKey.OP_ACCEPT, 只有SeverSocktChannel有这个事件,查看是否有新的连接
// SelectionKey.OP_READ, 是否有可读的通道就绪
// SelectionKey.OP_WRITE, 写数据的通道是否就绪
SelectionKey selectionKey = ssc.register(selector, SelectionKey.OP_ACCEPT);
//使用位运算符把你关心的操作连接起来
//SelectionKey selectionKey2 = ssc.register( selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
//可以用来判断是否有就绪通道
//这个selector如果没有接收到就绪通道就会阻塞
int select = selector.select();
System.out.println(select);
}
}
SelectionKey 使用
向 Selector 注册一个 Channel 通道时,就会返回一个 SelectionKey 选择键对象, 这个选择键表示一个通道与一个选择器之间的注册关系
SelectionKey 相关的方法:
channel()方法, 返回与该键对应的通道
selector()方法, 返回通道注册的选择器
cancel()方法,终结这种特定的注册关系
isValid()方法判断注册关系是否有效
interestOps()方法返回你关心的操作, 是以整数的形式进行编码的比特掩码, 可以使用
位运算检查所关心的操作,如:
Boolean isAccept = interestops & SelectionKey.OP_ACCEPT == SelectionKey.OP_ACCEPT
Boolean isConnect = interestops & SelectionKey.OP_CONNECT == interestops & SelectionKey.OP_CONNECT
还可以使用isReadable(), isWritable(), isConnectable(), isAccetable()等方法检测 这些些比特值, 上面一行检测 write 就绪的操作可以使用以面一行代替
if ( selctionKey.isWritable() ){ }
再谈selector.select()方法
Selector 类的核心就是 select()选择, 该方法调用时,执行以下步骤:
- 检查已取消键的集合, 如果该集合非空, 就把该集合中的键从另外的两个集合中移除, 注销相关的通道, 这个步骤结束后, 已取消键的集合应该是空的;
- 检查已注册键的集合中所有键的 interest 集合, 确定每个通道所关心的操作是否已经就绪;
- Select()方法返回的是从上一次调用 select()方法后进入 就绪状态的通道的数量;
如果客户端已经关闭,服务端没有将接收的SocketChannel关闭,会进行isReadable方法空循环
Selector的select方法在没有准备好的IO操作时,一直处于阻塞状态;
模拟NIO使用
服务端
package com.lizhiyu.com;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
/**
* @author lizhiyu
*/
public class NIOServer {
private ServerSocketChannel server;
private ByteBuffer sendBuffer;
private ByteBuffer recvBuffer;
private Selector selector;
private int port = 8888;
// 初始化服务器
NIOServer(int port) {
this.port = port;
try {
recvBuffer = ByteBuffer.allocate(1024);
sendBuffer = ByteBuffer.allocate(1024);
server = ServerSocketChannel.open();
server.socket().bind(new InetSocketAddress(port));
server.configureBlocking(false);
selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
}
NIOServer() {
this(8888);
}
public void start() {
try {
listener();
} catch (IOException e) {
System.out.println("监听端口IO异常");
}
}
public void listener() throws IOException {
while (true) {
// System.out.println("-----------------------------------------------------");
// System.out.println("1.selectedKeys的值:"+selector.selectedKeys().size());
// System.out.println("1.registe的值:"+ selector.keys().size());
//如果没有链接select会一直阻塞
int n = selector.select();
// System.out.println("----------------------fengexian1---------------------");
// System.out.println("2.select返回值:"+n);
// System.out.println("2.selectedKeys的值:{}"+ selector.selectedKeys().size());
// System.out.println("2.registe的值:"+selector.keys().size());
// System.out.println("-------------------------------------------------------");
if (n == 0) {
continue;
}
Set<SelectionKey> eventKeys = selector.selectedKeys();
Iterator<SelectionKey> it = eventKeys.iterator();
while (it.hasNext()) {
SelectionKey eventKey = it.next();
it.remove();
// 处理SelectionKey绑定的channel的连接,读,写等;
handleKey(eventKey);
}
}
}
// 处理IO口连接,读写等函数
public void handleKey(SelectionKey eventKey) throws IOException {
//接收连接将接收的Channel注册到Selector上
int interestOps = eventKey.interestOps();
Boolean isAccept =(interestOps & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
if(isAccept){
//相同的判断等效果
//if (eventKey.isAcceptable()) {
SocketChannel sc = server.accept();
System.out.println("新的客户端已经连接成功");
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
}
//Boolean isRead =(interestOps & SelectionKey.OP_READ) == SelectionKey.OP_READ;
else if (eventKey.isReadable()) {
//相同的判断方法
//if(isRead){
SocketChannel sc = (SocketChannel) eventKey.channel();
System.out.println("进行读操作");
String content = "";
int n;
recvBuffer.clear();
try {
//将数据写入到缓冲区中
//这里缓冲区的大小有限制
while ((n = sc.read(recvBuffer)) > 0) {
content = content + new String(recvBuffer.array(), 0, n);
}
} catch (IOException e) {
eventKey.cancel();
sc.close();
return;
}
//如果接收不到数据可能是客户端已经挂了,要将对应的Channel和SelectionKey解除绑定,比如客户端只连接不发送数据后直接关闭连接,服务端是不知道客户端没有连接的只能通过空循环isReadable来判断客户端是否有还要发送数据
// if (n == -1) {
// eventKey.channel().close();
// eventKey.cancel();
// System.out.println("客户端已经关闭:"+ sc.socket().getRemoteSocketAddress());
// return;
// }
System.out.println("receive client input Stirng : "+ content);
if (content.length() > 0) {
//清空发送缓冲区
sendBuffer.clear();
//将接收的数据放到发送缓冲区
sendBuffer.put(content.getBytes());
//缓冲区切换成读模式
sendBuffer.flip();
//将缓冲去中的数据写入到SocketChannel
sc.write(sendBuffer);
}
//要进行关闭这个ServerChannel在传输完数据后,如果不关闭会一直进行isReadable方法中无限循环
eventKey.channel().close();
eventKey.cancel();
}
// if (eventKey.isWritable()) {
// System.out.println("sendBuffer可写");
// SocketChannel sc = (SocketChannel) eventKey.channel();
// //将缓冲区中的数据写入到Channel中
// sc.write(sendBuffer);
// }
}
public static void main(String[] args) {
new NIOServer().start();
}
}
客户端
package com.lizhiyu.com;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
public class Client {
public static void main(String[] args) throws IOException {
SocketChannel sc = SocketChannel.open();
boolean connect = sc.connect(new InetSocketAddress(8888));
//向服务器发送消息
ByteBuffer buffer = ByteBuffer.wrap("hello, I am from client socket".getBytes());
while( buffer.hasRemaining()){
sc.write(buffer);
}
//获得服务器发送给客户端的消息
InputStream inputStream = sc.socket().getInputStream();
ReadableByteChannel newChannel = Channels.newChannel(inputStream);
buffer.clear();
newChannel.read(buffer);
buffer.flip();
CharBuffer decode = Charset.defaultCharset().decode(buffer);
System.out.println(decode);
sc.close();
}
}