先来回顾一下BIO的特点:阻塞,主要体现以下点:
1.ServerSocket.accept()
2.InputStream.read(), OutputStream.write()
3.无法在同一个线程里处理多个I/O
有没有一种方法可以在一个线程内实现对多个IO的处理呢?
1.使用Channel代替Stream
2.使用Selector监控多条Channel
3.可以在一个线程里处理多个Channel I/O
Channel
在BIO模型中,Stream是单向的,有InputStream和OutputStream,NIO模型中的Channel是双向的,Channel的读/写是通过Buffer来实现的
下面是Channel的文档注释
大致意思就是,Channel就是I/O操作的纽带,表现在与实体的连接,例如硬件设备,文件,网络Socket或者程序组件(能够执行一种或多种不同的I/O操作,像读和写操作)。Channel有打开和关闭两种状态,在创建之后是open状态,Channel关闭之后就是closed状态,文末还特意提到Channel是线程安全的。
Buffer
Buffer就是内存里面一个数据的缓冲区,针对Channel的操作都离不开Buffer
文档第一句就说明Buffer是特定原始类型数据的容器,buffer是特定原始类型元素的线性有限序列。 除了内容之外,buffer的基本属性是其容量(capacity),限制(limit)和位置(position)
下面是buffer缓冲区的一些结构图和操作模式
进入读取模式读取数据时候,这里分为两种情况:①数据全部读取完毕 ②读取部分数据时转换为写模式
Channel之间可以互相传递数据
Channel说到底还是读写数据的通道,那现在还是没有解决根本问题,一个线程处理多个I/O请求
Selector
Channel注册在Selector上,Selector监控着所有的Channel
注册完一个Channel之后,就会得到一个与Channel对应的SelectionKey
而每一个SelectionKey对应都有一下几个方法
interestOps:当前Channel有哪些感兴趣的状态(一个或多个)
readyOps:目前有哪些状态可操作
channel:返回SelectionKey所指代的注册的Channel对象
selector:在哪个Selector对象上面完成注册
attachment: 返回Channel附属上的对象
下面用NIO来写一个服务端的聊天
class ServerNIO {
public static void main(String[] args) throws IOException {
//创建ServerSocket服务
ServerSocketChannel serverSocket = ServerSocketChannel.open();
//设置为非阻塞
serverSocket.configureBlocking(false);
//绑定端口
serverSocket.socket().bind(new InetSocketAddress(7000));
//打开一个通道管理器
Selector selector = Selector.open();
//将ServerSocket注册到selector中,给它一个SelectionKey.OP_ACCEPT
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
while (true){
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext())
{
SelectionKey key = iterator.next();
if(key.isAcceptable()){
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
System.out.println("有新的连接进来");
socketChannel.register(selector, SelectionKey.OP_READ);
iterator.remove();
}else if(key.isReadable()){
SocketChannel channel = (SocketChannel) key.channel();
//处理业务
handle(channel);
}
}
}
}
private static void handle(SocketChannel channel) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int read = channel.read(byteBuffer);
if(read > 0){
System.out.println(new String(byteBuffer.array(), 0, read));
channel.write(ByteBuffer.wrap("success\n".getBytes()));
}
}
}
可以看到使用NIO之后即使没有使用多线程,单线程也可以处理两个客户端的请求