Seletor选择器是NIO技术中的核心技术,可以将通道注册进选择器,其主要作用就是使用1个线程来对多个通道中已就绪通道进行选择,然后就可以对选择的通道进行数据处理,属于一对多的关系,也就是1个线程来操作多个通道,大大减少了线程数量。这种机制在NIO技术中称“I/O多路复用”。他的优势是可以节省CPU资源,因为只有一个线程,cpu不需要在不同的线程间进行上下文切换。线程的上下文切换是一个非常耗时的动作,减少切换对设计高性能服务器具有很重要的意义。与普通IO相比,NIO不用未每个进程设置死循环来等待数据到达,它是建立再在数据已经送达的基础上,我们只需要对选择器进行轮询检测是否有可用通道即可。
核心类Selector,SelectionKey和SelectableChannel的关系:
- AbstractSelectableChannel是可选择通道的基本实现类,此类定义了处理通道注册,注销和关闭机制的各种方法。
- 相同的通道可以注册到不同的选择器,返回的SelectionKey不是同一个对象,相同的通道注册不同的事件(比如读和写)返回相同SelectionKey。
- SelectableChannel在多线程并发环境是安全的。
- 在与选择器结合使用的时候,需要先调用SelectableChannel对象的register()方法在选择器注册SelectableChannel,register()方法返回一个新的SelectionKey对象,SelectionKey表示该通道已被注册。
- 如果想取消通道,不能直接注销,只能通过取消该通道在选择器中对应的SelectionKey,之后在下一次选择select()操作时会注销该通道,如果该通道已关闭,那么该通道所有的SelectionKey都会取消。
下面代码实现了文件的读取。
服务器代码:
package serverSocket;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
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;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocketChannel channel1 = ServerSocketChannel.open();//抽象类
channel1.configureBlocking(false);//设置成非堵塞模式
Selector selector = Selector.open(); //打开选择器
channel1.register(selector, SelectionKey.OP_ACCEPT); //将通道注册到选择器
boolean isRun = true;
while(isRun == true){
selector.select(); //选择已就绪通道
Set<SelectionKey> set = selector.selectedKeys();
Iterator<SelectionKey> iterator = set.iterator();
while(iterator.hasNext()){
SelectionKey key = iterator.next();
iterator.remove(); //从就绪通道中移除该通道,防止重复消费
if(key.isAcceptable()){
SocketChannel socketChannel = channel1.accept(); //接受客户端请求
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_WRITE);//变成写
}
if(key.isWritable()){
SocketChannel socketChannel = (SocketChannel) key.channel();
FileInputStream file = new FileInputStream("D:\\a.txt");
FileChannel fileChannel = file.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100);
//将文件内容读取到缓冲区
while(fileChannel.position() < fileChannel.size()) {
fileChannel.read(byteBuffer);
byteBuffer.flip();
//再将缓冲区里内容写到客户端服务器通道
while(byteBuffer.hasRemaining()) {
socketChannel.write(byteBuffer);
}
byteBuffer.clear();
}
socketChannel.close();
}
}
}
channel1.close();
}
}
客户端代码:
package serverSocket;
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.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class Client {
public static void main(String[] args) throws IOException {
SocketChannel channel1 = SocketChannel.open();
channel1.configureBlocking(false);
channel1.connect(new InetSocketAddress("localhost", 8088));
Selector selector = Selector.open();
channel1.register(selector, SelectionKey.OP_CONNECT);
boolean isRun = true;
while(isRun = true) {
if(channel1.isOpen() == true) {
selector.select();
Set<SelectionKey> set = selector.selectedKeys();
Iterator<SelectionKey> iterator = set.iterator();
while(iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if(key.isConnectable()) {
while(!channel1.finishConnect()) {
}
channel1.register(selector, SelectionKey.OP_READ);//变成读
}
if(key.isReadable()) {
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
int readLength = channel1.read(byteBuffer);
byteBuffer.flip();
long count = 0;
while(readLength != -1) {
count = count + readLength;
readLength = channel1.read(byteBuffer);
byteBuffer.clear();
}
channel1.close();
}
}
}
}
}
}
总结:
NIO之所以称为同步非堵塞就是因为当线程遇到未连接或者数据未送达的情况下,线程会去干其它的事情,通常我们可以用线程池来进行并发执行事件(单线程指的是IO),但是该线程会不断询问条件是否就绪,如果满足条件就重新执行,我们称为线程重用。除了NIO,还有BIO(是同步堵塞),以及JDK7新增的AIO(异步非堵塞)