简述
BIO为同步阻塞BlockingIO,BIO适用于连接数小,结构固定的场景。
NIO为同步非阻塞NoBlockingIO,NIO适用于连接数大,但是任务小的场景。
AIO(NIO.2)为异步非阻塞,AIO适用于连接数大,且时间长的场景。
实际上,主要的分类还是BIO与NIO。
BIO及其经典写法
BIO就是传统IO,用流的方式处理IO。
所有的IO都被视为单个字节的移动,stream对象一次移动一个字节,流IO先把流转换为字节,再转换为对象。
BIO的经典写法如下:
读取输入流的写法
URL url=new URL("http://...");
InputStream is=url.openStream();//获取字节流
InputStreamReader ir=new InputStreamReader(is,"UTF-8");//转为字符流
BufferReader br=new BufferReader(ir);
String data=br.readLine();
while(data!=null){
data=br.readLine();
}
br.close();
ir.close();
is.close();
Socket的客户端写法
Socket socket=new Socket(ip,port);//TCP握手建立连接
OutpurStream os=socket.getOutputStream();//向服务端的输出流
PrintWriter pw=new PrintWriter(os);//包装输出流
pw.write("message");
pw.flush();
socket.shutdownOutput();
//输入流
InputStream is=socket.getInputStream();
BufferReader br=new BufferReader(new InputStreamReader(is));
String data=br.readLine();
while(data!=null){
data=br.readLine();
}
br.close();
is.close();
pw.close();
os.close();
socket.close();
Socket的服务端写法
ServerSocket serverSocket=new ServerSocket(port);
Socket socket=serverSocket.accept();//开始监听,阻塞等待客户端的连接,TCP三次握手后,才能完成
InputStream is=socket.getInputStream();
InputStreamReader isr=new InputStreamReader(is);
BufferReader br=new BufferReader(isr);
String data=br.readLine();
while(data!=null){
data=br.readLine();
}
socket.shutdownInput();
//用输出流向客户端返回信息
OutputStream os=socket.getOutputStream();
PrintWriter pw=new PrintWriter(os);
pw.write("response");
pw.flush();
pw.close();
os.close();
br.close();
isr.close();
is.close();
socket.close();
NIO及其经典写法
NIO不再是流的概念,而是以块的方式处理IO。
不需要一点点地移动字节,这样每个线程只需要处理IO拿到的数据块,不需要等待IO,所以可以复用线程,更可以避免线程切换带来的上下文切换,提升效率。
NIO需要有一个专门的线程处理所有的IO事件,是事件驱动的。
以SocketIO为例,NIO的客户端写法如下:
创建通道和管理器
SocketChannel channel=SocketChannel.open();//通道
channel.configBlocking(false);//非阻塞
this.selector=Selector.open();//通道管理器
channel.connect(new InetSocketAddress(ip,port));//需要通过channel.finishConnect才能完成连接
channel.register(selector,SelectionKey.OP_CONNECT);//通道管理器监听
轮询事件处理
public void listen() throws IOException{
while(true){
Iterator itr=this.selector.selectKeys().iterator();
while(itr.hasNext()){
SelectionKey key=(SelectionKey)itr.next();
itr.remove();
if(key.isConnectable()){
SocketChannel channel=(SelectionKey)key.channel();
if(channel.isConnectionPending){
channel.finishConnect();
}
channel.configBlocking(false);
channel.write(ByteBuffer.wrap(new String("Message content'").getBytes()));
channel.register(this.selector,SelectionKey.OP_READ);
}else if(key.isReadable()){
...
}
}
}
}
NIO的服务端写法如下:
创建通道和管理器
ServerSocketChannel channel=ServerSocketChannel.open();
channel.configBlocking(false);
this.selector=Selector.open();
channel.socket().bind(new InetSocketAddress(port));//server端用bind,client端用connect
channel.register(selector,SelectionKey.OP_ACCEPT);//注册感兴趣的事件
处理事件
public void listen() throws IOException{
while(true){
selector.select();//如果没有感兴趣事件,会一直阻塞
Iterator itr=this.selector.selectKeys().iterator();
while(itr.hasNext()){
SelectionKey key=(SelectionKey)itr.next();
itr.remove();
if(key.isConnectable()){
SocketChannel channel=(SelectionKey)key.channel();
if(channel.isConnectionPending){
channel.finishConnect();
}
channel.configBlocking(false);
channel.write(ByteBuffer.wrap(new String("Message content'").getBytes()));
channel.register(this.selector,SelectionKey.OP_READ);
}else if(key.isReadable()){
...
}
}
}
}
NIO详解
NIO有三个核心对象Buffer、Channel、Selector,其中Channel代替了流,Buffer是流的容器对象,写入Channel需要先经过Buffer,读取Channel也需要先读进Buffer。
Channel是直接读写数据的对象,但它不是流,Channel是双向的(操作系统底层通常都是双向的,所以Channel比流更贴近真实情况),可以异步读写,Channel不允许应用层直接操作。
NIO中的Channel包括:
1.SocketChannel TCP网络IO
2.ServerSocketChannel 监听TCP连接
3.DatagramChannel UDP网络IO
4.FileChannel 文件IO
Buffer是应用层的操作对象,应用层不能直接操作Channel层,只能操作Buffer层,Buffer是个中转池,实质是个数组,能对IO数据结构化访问,而且可以跟踪系统的读写进程。
Buffer的读写功能包括:
1.写入到Buffer
2.从Buffer读出
3.用filp切换读写模式
4.用clear清空buffer
5.用compact压紧,就是清除buffer中已经读过的数据,未读过的数据挪到开头。
Buffer的flip,clear,compact等操作,实质都是设置数组的position、limit和capacity,position代表从哪里开始处理,limit代表处理到哪里,capacity代表容量长度。
Selector是NIO的核心实现,是管理channel通道的对象,每个线程通过1个Selector管理多个Channel对象,Selector注册并监听多个Channel,根据监听事件决定Channel的读写。
使用Selector,首先要打开Selector
Selector selector=Selector.open();
然后要注册到Channel
channel.configueBlocking(false);//设置为异步模式
SelectionKey key=channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
SelectionKey有OP_CONNECT、OP_ACCEPT、OP_READ、OP_WRITE四种。分别是成功连接,可以接收连接,可以读,可以写。
SelectionKey可以提供Channel和Selector,可以继续附加对象,在selector中监控的所有Channel,都可以通过SelectionKey来间接得到,通过Set
处理完channel后,我们需要自己手动把selectionKey从数据集中remove挪出来。
epoll
epoll是底层操作系统对NIO的支持机制,epoll用红黑树管理被监控的socket句柄文件,如果socket的读写中断到了,就放进一个准备就绪list链表,仅当链表有数据时,才返回,没有数据就继续等待。
NIO比BIO的优势
BIO是给每个连接分配一个线程,所以在需要大量连接时,就需要大量的线程,线程管理会带来额外开销。
NIO是采用了通知机制等待连接返回数据,只有活动的IO才会占用线程,线程不会被IO阻塞,所以不需要大量的线程,相应的线程创建、销毁、切换等开销也可以省略,资源可以更加集中在业务处理上。NIO适合高并发场景。
BIO的流以字节为单位,一次输入流产生一个字节,一次输出流消费1个自己,缺点是处理速度慢,优点是在字节上为流做过滤器简单方便。
NIO的块以块为单位,每步操作产生或消费一个块,优点是处理速度快,缺点是在块上不能像流一样做简单方便的处理。
BIO和NIO的Socket客户端与服务端写法都不同,BIO客户端是直接Socket socket=new Socket(ip,port),服务端是先建立ServerSocket serverSocket=new ServerSocket(port),然后Socket socket=serverSocket.accept();NIO客户端是channel.connect(new InetSocketAddress(ip,port)),服务端是channel.socket.bind(new InetSocketAddress(port));
BIO和NIO在文件处理上的写法
BIO和NIO不仅有网络IO操作,也有文件IO操作,不过文件IO操作不支持异步,无法设置connect.configBlocking(false)。
//BIO文件拷贝
public static void fileCopy(String source, String target) throws IOException {
try (InputStream in = new FileInputStream(source)) {
try (OutputStream out = new FileOutputStream(target)) {
byte[] buffer = new byte[4096];
int bytesToRead;
while((bytesToRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesToRead);
}
}
}
}
//NIO文件拷贝
public static void fileCopyNIO(String source, String target) throws IOException {
//声明源文件和目标文件
FileInputStream fi=new FileInputStream(new File(src));
FileOutputStream fo=new FileOutputStream(new File(dst));
//获得传输通道channel
FileChannel inChannel=fi.getChannel();
FileChannel outChannel=fo.getChannel();
//获得容器buffer
ByteBuffer buffer=ByteBuffer.allocate(1024);
while(true){
//判断是否读完文件
int eof =inChannel.read(buffer);
if(eof==-1){
break;
}
//重设一下buffer的position=0,limit=position
buffer.flip();
//开始写
outChannel.write(buffer);
//写完要重置buffer,重设position=0,limit=capacity
buffer.clear();
}
inChannel.close();
outChannel.close();
fi.close();
fo.close();
}
引用
深入浅出NIO Socket实现机制
Java NIO 详解(一)
Java NIO 详解(二)
java IO 流Stream 序列化Serializable 文件File