一、阻塞与非阻塞
1、传统的IO流是阻塞式的。当一个线程调用read或write时,线程会被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。因此,在网络通信进行IO操作时,服务器不得不为每个客户端提供一个独立的线程来进行处理。当服务器需要处理大量的客户端时,性能会急剧下降。
2、NIO流是非阻塞式的。当线程从某个通道进行读写操作时,若没有数据可用,该线程可以执行其他任务。线程通常将非阻塞IO的空闲时间用于其他通道上执行IO操作。因此,NIO可以使服务端使用一个或者有限个来处理连接到服务端的所有客户端。
二、选择器
非阻塞IO的核心是选择器。选择器(Selector)可以同时监控多个通道。
1、通过Selector.open()来创建一个选择器。并通过通道的register()方法将通道注册到选择器上。
2、选择器可以监听的事件:
读:SelectionKey.OP_READ
写: SelectionKey.OP_WRITE
连接:SelectionKey.OP_CONNECT
接收:SelectionKey.OP_ACCEPT
当不止监听一个事件时,可以用位或 | 连接。
3、例子
客户端:
//客户端
public class Client {
public static void main(String[]args) {
Scanner sc=new Scanner(System.in);
//1、获取socketChannel通道
try(SocketChannel socketChannel= SocketChannel.open(new InetSocketAddress("localhost",6666))) {
//2、设置为非阻塞模式
socketChannel.configureBlocking(false);
//3、获取缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (sc.hasNext()) {
buffer.put((sc.nextLine() + "\t" + new Date().toString()+"\n").getBytes());
buffer.flip(); //转为读模式
socketChannel.write(buffer); //将缓冲区的内容写进通道
buffer.clear(); //清空缓冲区
}
socketChannel.shutdownOutput();
}catch(Exception e){
e.printStackTrace();
}
}
}
服务端:
//服务端
public class Server {
public static void main(String[] args) throws IOException {
Scanner sc=new Scanner(System.in);
ByteBuffer buffer=null;
//1、获取ServerSocketChannel
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
//2、绑定IP、端口
serverSocketChannel.bind(new InetSocketAddress("localhost",6666));
//3、将serverSocketChannel转为非阻塞式
serverSocketChannel.configureBlocking(false);
//4、获取选择器
Selector selector=Selector.open();
//5、将serverSocketChannel注册到选择器中,并添加选择键
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//6、轮询反复的判断已经准备就绪的事件是否大于0
while(selector.select()>0){
Iterator<SelectionKey> it=selector.selectedKeys().iterator(); //获取迭代器
while(it.hasNext()){
SelectionKey sKey=it.next(); //获取SelectionKey
//7、判断准备就绪的是否是“接受事件”
if(sKey.isAcceptable()){
//8、获取socketChannel
SocketChannel socketChannel=serverSocketChannel.accept();
//9、将socketChannel转为非阻塞模式
socketChannel.configureBlocking(false);
//10、将socketChannel添加到选择器中
socketChannel.register(selector,SelectionKey.OP_READ);
}
//判断准备就绪的是否是“写事件”
else if(sKey.isReadable()){
buffer=ByteBuffer.allocate(1024); //获取缓冲区
SocketChannel socketChannel=(SocketChannel) sKey.channel(); //获取socketChannel
int size=-1;
while((size=socketChannel.read(buffer))!=-1){
buffer.flip();
System.out.print(new String(buffer.array(),0,size));
buffer.clear();
}
}
it.remove(); //关闭
}
}
}