打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Java中BIO,NIO和AIO使用样例

上文中分析了阻塞,非阻塞,同步和异步概念上的区别以及各种IO模型的操作流程,本篇文章将主要介绍Java中BIO,NIO和AIO三种IO模型如何使用。需要注意的是,本文中所提到的所有样例都是在一个server对应一个client的情况下工作的,如果你想扩展为一个server服务多个client,那么代码需要做相应的修改才能使用。另外,本文只会讲解server端如何处理,客户端的操作流程可以仿照服务端进行编程,大同小异。文章最后给出了源码的下载地址。

BIO(Blocking I/O)

在Java中,BIO是基于流的,这个流包括字节流或者字符流,但是细心的同学可能会发现基本上所有的流都是单向的,要么只读,要么只写。在实际上编程时,在对IO操作之前,要先获取输入流或输出流,然后对输入流读或对输出流写即完成实际的IO读写操作。首先需要新建一个ServerSocket对象监听特定端口,然后当有客户端的连接请求到来时,在服务器端获取一个Socket对象,用来进行实际的通信。

ServerSocket serverSocket = new ServerSocket(PORT);  Socket socket = serverSocket.accept();  

获取到Socket对象后,通过这个Socket对象拿到输入流和输出流就可以进行相应的读写操作了。

DataInputStream in = new DataInputStream(socket.getInputStream());  DataOutputStream out = new DataOutputStream(socket.getOutputStream());  

由于BIO的编程的模型比较简单,这里就写这么多,需要下载源代码的可以到文章末尾。

NIO(New I/O, or Nonblocking I/O)

BIO的编程模型简单易行,但是缺点也很明显。由于采用的是同步阻塞IO的模式,所以server端要为每一个连接创建一个线程,一方面,线程之间在进行上下文切换的时候会造成比较大的开销,另一方面,当连接数过多时,可能会造成服务器崩溃的现象产生。

为了解决这个问题,在JDK 1.4的时候,引入了NIO(New IO)的概念。NIO主要由三个部分组成,即ChannelBufferSelectorChannel可以跟BIO中的Stream类比,不同的是Channel是可读可写的。当和Channel进行交互的时候需要Buffer的支持,数据可以从Buffer写到Channel中,也可以从Channel中读到Buffer中,他们的关系如下图。

以SocketChannel为例,Channel和Buffer交互的例子如下。ByteBuffer是Buffer的一种实现,在使用ByteBuffer之前,需要为其分配空间,然后调用Channel的read方法将数据写入Buffer中,在完成后,在使用Buffer中的数据之前需要调用Buffer的flip方法。Buffer中有个position常量,记录当前操作数据的位置,当向Buffer中写数据时,position会记录当前写的位置,当写操作完成后,flip会把position至为0,这样读取Buffer中的数据时,就会从0开始了。另外需要注意的是处理完Buffer中的数据后需要调用clear方法将Buffer清空。向Channel中写数据的操作比较简单,这里不再赘述。

// Read data from channel to bufferSocketChannel socketChannel = (SocketChannel) selectionKey.channel();  ByteBuffer byteBuffer = ByteBuffer.allocate(1024);  while (socketChannel.read(byteBuffer) > 0) {      byteBuffer.flip();    while(byteBuffer.hasRemaining()){        System.out.print((char) byteBuffer.get());    }    byteBuffer.clear();}// Write data to channel from buffersocketChannel.write(ByteBuffer.wrap(msg.getBytes()));  

NIO中另一个重要的组件是Selector,Selector可以用来检查一个或多个Channel是否有新的数据到来,这种方式可以实现在一个线程中管理多个Channel的目的,示意图如下。

在使用selector之前,一定要注意把对应的Channel配置为非阻塞。否则在注册的时候会抛异常。

serverSocketChannel.configureBlocking(false);  

然后调用select函数,select是个阻塞函数,它会阻塞直到某一个操作被激活。这个时候可以获取一系列的SelectionKey,通过这个SelectionKey可以判断其对应的Channel可进行的操作(可读,可写或者可接受连接),然后进行相应的操作即可。这里还要注意一个问题就是在判断完可执行的操作后,需要将这个SelectionKey从集合中移除

selector.select();Set<SelectionKey> selectionKeys = selector.selectedKeys();  Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()) {      SelectionKey selectionKey = iterator.next();    if (!selectionKey.isValid())        continue;    if (selectionKey.isAcceptable()) {        // ready for accepting            } else if (selectionKey.isReadable()) {        // ready for reading                       } else if (selectionKey.isWritable()) {        // ready for writing    }    iterator.remove();}

NIO这里最后一个问题是,什么时候Channel可写,这个问题困扰了我很久,经过从网上查资料最后得出的结论是,只要这个Channel处于空闲状态,都是可写的。这个我也从实际的程序中论证了。

AIO(Asynchronous I/O)

在JDK 1.7时,Java引入了AIO的概念,AIO还是基于Channel和Buffer的,不同的是它是异步的。用户线程把实际的IO操作以及数据拷贝全部委托给内核来做,用户只要传递给内核一个用于存储数据的地址空间即可。内核处理的结果通过两种方式返回给用户线程。一是通过Future对象,另外一种是通过回调函数的方式,回调函数需要实现CompletionHandler接口。这里只给出通过回调方式处理数据的样例,其中关键的步骤已经在程序中添加了注释。

// 创建AsynchronousServerSocketChannel监听特定端口,并设置回调AcceptCompletionHandlerAsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(PORT));  serverSocketChannel.accept(serverSocketChannel, new AcceptCompletionHandler());// 监听回调,当用连接时会触发该回调private static class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, AsynchronousServerSocketChannel> {      @Override    public void completed(AsynchronousSocketChannel result, AsynchronousServerSocketChannel attachment) {        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);        // 注册read请求以及回调ReadCompletionHandler        result.read(byteBuffer, result, new ReadCompletionHandler(byteBuffer, "client"));        // 递归监听        attachment.accept(attachment, this);    }    @Override    public void failed(Throwable exc, AsynchronousServerSocketChannel attachment) {        // 递归监听        attachment.accept(attachment, this);    }}// 读取数据回调,当有数据可读时触发该回调public class ReadCompletionHandler  implements CompletionHandler<Integer, AsynchronousSocketChannel> {      private ByteBuffer byteBuffer;    private String remoteName;    public ReadCompletionHandler(ByteBuffer byteBuffer, String remoteName) {        this.byteBuffer = byteBuffer;        this.remoteName = remoteName;    }    @Override    public void completed(Integer result, AsynchronousSocketChannel attachment) {        if (result <= 0)            return;        byteBuffer.flip();        System.out.println("[" + this.remoteName + "] " + new String(byteBuffer.array()));        byteBuffer.clear();        // 递归监听数据        attachment.read(byteBuffer, attachment, this);    }    @Override    public void failed(Throwable exc, AsynchronousSocketChannel attachment) {        byteBuffer.clear();        // 递归监听数据        attachment.read(byteBuffer, attachment, this);    }}

上面给出了BIO,NIO以及AIO在Java中的使用的部分程序,并且分析了其中关键步骤的使用及其需要注意的事项。

需要源码的同学可以到这里下载

参考

Java NIO Tutorial

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
聊聊BIO、NIO与AIO的区别
「每日分享」网络编程-NIO、BIO、AIO详解
高并发Java(8):NIO和AIO(下)
使用Java NIO编写高性能的服务器
Java NIO API详解
Java NIO与IO的区别和比较
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服