BIO与NIO的区别
BIO是同步阻塞的,比如当serverSocket监听是否有客户端请求连接时,若该对象没有监听到客户端连接,则一直将阻塞在那里,直到有客户端连接进来。
NIO是同步非阻塞的,比如当serverSocket监听时,若该对象没有监听到客户端连接,则直接执行接下来的程序,不会阻塞在那里。
由以上的区别,我们又可以知道BIO必须是一个线程一个连接,无法让我们的程序在空闲时间做点别的事情。所以我们需要启用多个线程,才能管理多个连接。
而NIO则可以通过一个线程管理多个连接。
在介绍第二个区别之前,先介绍我们需要知道的几个角色。
缓冲区Buffer:一块内存区域,用来存放缓冲数据,支持双向操作。
通道Channel:对应OutputStream、InputStream(为什么它既能对应BIO中的输入流,还能对应BIO中的输出流?答:Channel本身就支持这种双向操作,它既能读,也能写)。
ok,第二个区别就是BIO是面向流的,客户端与服务端之间就像有两条管道,对于由客户端的输入流获得的数据一定是另一端的服务端的输出流发送出来的,反之亦然。
而NIO是面向缓冲区的,如客户端有一个缓冲区,那么服务端就必须有一个对应的缓冲区。
客户端:我们将要发送至服务端的数据先存入客户端的缓冲区中,再由客户端Channel发送(write
)。
服务端:我通过服务端的Channel将数据读取(read
)至服务端的缓冲区。
因此从宏观上来看,似乎只有一个缓冲区,如客户端往缓冲区放数据,而服务端从中取数据。反之亦然。当然实际上用到了两个缓冲区。
ok,接下来再介绍另外两个角色:
选择器Selector:基本上一个线程对应一个Selector,而一个Selector可以管理多个Channel(当然,我们需要先将Channel注册在Selector)
选择键SelectionKey:当在Selector中注册Channel时,还必须选一个选择键策略(我们也可以称之为事件消息,而事件消息有:读、写、连接、接收)绑定Channel。
图示服务端使用NIO的整体架构(取自网络)
服务端案例实现
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;
public class NIOSever {
public static void main(String[] args) throws IOException {
//打开监听套接字Channel,用来监听客户端连接
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//绑定端口号
serverSocketChannel.bind(new InetSocketAddress(9999));
//设置为非阻塞(这是最重要的)
serverSocketChannel.configureBlocking(false);
//创建选择器
Selector selector = Selector.open();
//注册serverSocketChannel在selector中,
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
if (selector.select(3000) == 0) {
//若当前时间段(前3秒)没有一个事件发生,打印"没有客户端连接"
System.out.println("没有客户端连接");
}else{
//获取与当前时间段(前3秒)有事件发生的Channel绑定的SelectionKey
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey s = iterator.next();
//判断事件是否为监听事件(与其对应的是客户端的Connect事件)
if (s.isAcceptable()) {
//获取监听到的套接字连接
SocketChannel client = serverSocketChannel.accept();
//设置非阻塞
client.configureBlocking(false);
//注册,绑定的事件是读事件,还需在分配一个缓冲区给Channel
client.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
System.out.println("客户端(" + client.hashCode() + ")连接");
}
//判断当前事件是否为读取事件(与其对应的是客户端的写入事件)
if (s.isReadable()) {
SocketChannel client = (SocketChannel) s.channel();
//将队列资源放入缓冲区
ByteBuffer content = (ByteBuffer) s.attachment();
client.read(content);
//切换读写模式
content.flip();
while (content.hasRemaining()) {
System.out.print((char) content.get());
}
//清空数据(不是真的清空,只是把position=0,limit=capacity)
content.clear();
}
iterator.remove();
}
}
}
}
}