本篇内容主要讲解“Java的BIO, NIO, AIO怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java的BIO, NIO, AIO怎么实现”吧!
BIO, NIO, AIO以Java的角度理解:
BIO,同步阻塞式IO,简单理解:一个连接一个线程
NIO,同步非阻塞IO,简单理解:一个请求一个线程
AIO,异步非阻塞IO,简单理解:一个有效请求一个线程
在JDK1.4之前,用Java编写网络请求,都是建立一个ServerSocket,然后,客户端建立Socket时就会询问是否有线程可以处理,如果没有,要么等待,要么被拒绝。即:一个连接,要求Server对应一个处理线程。
public class PlainEchoServer { public void serve(int port) throws IOException { final ServerSocket socket = new ServerSocket(port); //Bind server to port try { while (true) { //Block until new client connection is accepted final Socket clientSocket = socket.accept(); System.out.println("Accepted connection from " + clientSocket); //Create new thread to handle client connection new Thread(new Runnable() { @Override public void run() { try { BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); PrintWriter writer = new PrintWriter(clientSocket.getOutputStream(), true); //Read data from client and write it back while (true) { writer.println(reader.readLine()); writer.flush(); } } catch (IOException e) { e.printStackTrace(); try { clientSocket.close(); } catch (IOException ex) { // ignore on close } } } }).start(); //Start thread } } catch (IOException e) { e.printStackTrace(); } } }
在Java里的由来,在JDK1.4及以后版本中提供了一套API来专门操作非阻塞I/O,我们可以在java.nio包及其子包中找到相关的类和接口。由于这套API是JDK新提供的I/O API,因此,也叫New I/O,这就是包名nio的由来。这套API由三个主要的部分组成:缓冲区(Buffers)、通道(Channels)和非阻塞I/O的核心类组成。在理解NIO的时候,需要区分,说的是New I/O还是非阻塞IO,New I/O是Java的包,NIO是非阻塞IO概念。这里讲的是后面一种。
NIO本身是基于事件驱动思想来完成的,其主要想解决的是BIO的大并发问题:在使用同步I/O的网络应用中,如果要同时处理多个客户端请求,或是在客户端要同时和多个服务器进行通讯,就必须使用多线程来处理。也就是说,将每一个客户端请求分配给一个线程来单独处理。这样做虽然可以达到我们的要求,但同时又会带来另外一个问题。由于每创建一个线程,就要为这个线程分配一定的内存空间(也叫工作存储器),而且操作系统本身也对线程的总数有一定的限制。如果客户端的请求过多,服务端程序可能会因为不堪重负而拒绝客户端的请求,甚至服务器可能会因此而瘫痪。 NIO基于Selector,当socket有流可读或可写入socket时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。
public class PlainNioEchoServer { public void serve(int port) throws IOException { System.out.println("Listening for connections on port " + port); ServerSocketChannel serverChannel = ServerSocketChannel.open(); ServerSocket ss = serverChannel.socket(); InetSocketAddress address = new InetSocketAddress(port); //Bind server to port ss.bind(address); serverChannel.configureBlocking(false); Selector selector = Selector.open(); //Register the channel with the selector to be interested in new Client connections that get accepted serverChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { try { //Block until something is selected selector.select(); } catch (IOException ex) { ex.printStackTrace(); //handle in a proper way break; } //Get all SelectedKey instances Set<SelectionKey> readyKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = readyKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = (SelectionKey) iterator.next(); //Remove the SelectedKey from the iterator iterator.remove(); try { if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); //Accept the client connection SocketChannel client = server.accept(); System.out.println("Accepted connection from " + client); client.configureBlocking(false); //Register connection to selector and set ByteBuffer client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, ByteBuffer.allocate(100)); } //Check for SelectedKey for read if (key.isReadable()) { SocketChannel client = (SocketChannel) key.channel(); ByteBuffer output = (ByteBuffer) key.attachment(); //Read data to ByteBuffer client.read(output); } //Check for SelectedKey for write if (key.isWritable()) { SocketChannel client = (SocketChannel) key.channel(); ByteBuffer output = (ByteBuffer) key.attachment(); output.flip(); //Write data from ByteBuffer to channel client.write(output); output.compact(); } } catch (IOException ex) { key.cancel(); try { key.channel().close(); } catch (IOException cex) { } } } } } }
与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。
即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。
在JDK1.7中,这部分内容被称作NIO.2,主要在java.nio.channels包下增加了下面四个异步通道:
AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel
其中的read/write方法,会返回一个带回调函数的对象,当执行完读取/写入操作后,直接调用回调函数。
public class PlainNio2EchoServer { public void serve(int port) throws IOException { System.out.println("Listening for connections on port " + port); final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open(); InetSocketAddress address = new InetSocketAddress(port); // Bind Server to port serverChannel.bind(address); final CountDownLatch latch = new CountDownLatch(1); // Start to accept new Client connections. Once one is accepted the CompletionHandler will get called. serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() { @Override public void completed(final AsynchronousSocketChannel channel, Object attachment) { // Again accept new Client connections serverChannel.accept(null, this); ByteBuffer buffer = ByteBuffer.allocate(100); // Trigger a read operation on the Channel, the given CompletionHandler will be notified once something was read channel.read(buffer, buffer, new EchoCompletionHandler(channel)); } @Override public void failed(Throwable throwable, Object attachment) { try { // Close the socket on error serverChannel.close(); } catch (IOException e) { // ingnore on close } finally { latch.countDown(); } } }); try { latch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } private final class EchoCompletionHandler implements CompletionHandler<Integer, ByteBuffer> { private final AsynchronousSocketChannel channel; EchoCompletionHandler(AsynchronousSocketChannel channel) { this.channel = channel; } @Override public void completed(Integer result, ByteBuffer buffer) { buffer.flip(); // Trigger a write operation on the Channel, the given CompletionHandler will be notified once something was written channel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result, ByteBuffer buffer) { if (buffer.hasRemaining()) { // Trigger again a write operation if something is left in the ByteBuffer channel.write(buffer, buffer, this); } else { buffer.compact(); // Trigger a read operation on the Channel, the given CompletionHandler will be notified once something was read channel.read(buffer, buffer, EchoCompletionHandler.this); } } @Override public void failed(Throwable exc, ByteBuffer attachment) { try { channel.close(); } catch (IOException e) { // ingnore on close } } }); } @Override public void failed(Throwable exc, ByteBuffer attachment) { try { channel.close(); } catch (IOException e) { // ingnore on close } } } }
说道实现原理,还要从操作系统的IO模型上了解 按照《Unix网络编程》的划分,IO模型可以分为:阻塞IO、非阻塞IO、IO复用、信号驱动IO和异步IO,按照POSIX标准来划分只分为两类:同步IO和异步IO。 如何区分呢?首先一个IO操作其实分成了两个步骤:发起IO请求和实际的IO操作,同步IO和异步IO的区别就在于第二个步骤是否阻塞,如果实际的IO读写阻塞请求进程,那么就是同步IO,因此阻塞IO、非阻塞IO、IO复用、信号驱动IO都是同步IO,如果不阻塞,而是操作系统帮你做完IO操作再将结果返回给你,那么就是异步IO。阻塞IO和非阻塞IO的区别在于第一步,发起IO请求是否会被阻塞,如果阻塞直到完成那么就是传统的阻塞IO,如果不阻塞,那么就是非阻塞IO。
收到操作系统的IO模型,又不得不提select/poll/epoll/iocp。 可以理解的说明是:在Linux 2.6以后,java NIO的实现,是通过epoll来实现的,这点可以通过jdk的源代码发现。而AIO,在windows上是通过IOCP实现的,在linux上还是通过epoll来实现的。 这里强调一点:AIO,这是I/O处理模式,而epoll等都是实现AIO的一种编程模型;换句话说,AIO是一种接口标准,各家操作系统可以实现也可以不实现。在不同操作系统上在高并发情况下最好都采用操作系统推荐的方式。Linux上还没有真正实现网络方式的AIO。
在windows上,AIO的实现是通过IOCP来完成的,看JDK的源代码,可以发现
WindowsAsynchronousSocketChannelImpl
看实现接口:
implements Iocp.OverlappedChannel
再看实现方法:里面的read0/write0方法是native方法,调用的jvm底层实现。
在linux上,AIO的实现是通过epoll来完成的,看JDK源码,可以发现,实现源码是:
UnixAsynchronousSocketChannelImpl
看实现接口:
implements Port.PollableChannel
这是与windows最大的区别,poll的实现,在linux2.6后,默认使用epoll。
到此,相信大家对“Java的BIO, NIO, AIO怎么实现”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。