Java 非阻塞I/O使用方法

 更新时间:2017年09月28日 11:33:40   作者:swt369  
这篇文章主要介绍了Java 非阻塞I/O使用方法,文中涉及非阻塞I/O的简介,同时向大家展示了利用非阻塞I/O实现客户端的方法,需要的朋友可以参考下。

绝大部分知识与实例来自O'REILLY的《Java网络编程》(Java Network Programming,Fourth Edition,by Elliotte Rusty Harold(O'REILLY))。

非阻塞I/O简介

非阻塞I/O(NIO)是处理高并发的一种手段。在高并发的情况下,创建和回收线程以及在线程间切换的开销变得不容忽视,此时就可以使用非阻塞I/O技术。这种技术的核心思想是每次选取一个准备好的连接,尽快地填充这个连接所能管理的尽可能多的数据,然后转向下一个准备好的连接。

利用非阻塞I/O实现的客户端

一般情况下,客户端不会需要处理很高数量的并发连接。事实上,非阻塞I/O主要是为服务器设计的,但它也可以用在客户端上。由于客户端的设计相比服务器容易,因此下面先用客户端来进行简单演示。

首先介绍通道(channel)和缓冲区。非阻塞I/O中使用SocketChannel类创建连接。要获取SocketChannel对象,需要将一个SocketAddress对象(通常会使用它的子类InetSocketAddress)传入它的静态工厂方法open()中。下面为一个示例:

SocketAddress address = new InetSocketAddress("127.0.0.1", 19);
SocketChannel client = SocketChannel.open(address);

open()方法是阻塞的,因此这之后的代码在连接建立之前不会执行。如果连接无法建立,会抛出一个IOException异常。

连接建立之后就需要获取输入和输出。不同于传统的getInputStream()与getOutputStream(),利用通道,你可以直接写入通道本身。不是写入字节数组,而是要写入一个ByteBuffer对象。ByteBuffer对象通过ByteBuffer.allocate(int capacity)获取,capacity为缓冲区大小,单位为字节:

ByteBuffer buffer = ByteBuffer.allocate(74);

获得ByteBuffer对象后,将其传递给SocketChannel对象的read()方法,SocketChannel对象会用从Socket读取的数据填充这个缓冲区。read()方法返回成功读取并储存在缓冲区中的字节数。默认情况下,它会至少读取一个字节,或者返回-1指示数据结束,没有字节可用时阻塞。这与InputStream的行为大致相同。但如果设置成非阻塞模式,没有字节可用时它会立即返回0,不会阻塞。

现在假定缓冲区内已经有了一些数据,之后就需要将它们提取出来。可以使用传统的方式,先将数据写入一个字节数组,之后再写入一个输出流中。这里介绍一种完全基于通道的方法:利用Channels工具类将输出流封装到一个通道中:

WritableByteChannel out = Channels.newChannel(System.out);

上面的代码将System.out封装入一个通道中。这之后就可以进行输出了。ByteBuffer对象在每次输出之前,需要调用一下它的flip()方法,使得通道从开头开始读。在读写完毕后,还需要调用它的clear()方法,重置缓冲区的状态。下面是进行一次数据输出的代码:

buffer.flip();
out.write(buffer);
buffer.clear();

实例1:利用非阻塞I/O实现的CharGenerator(字符生成器)客户端

服务器代码:

public static void createCharGeneratorServer(){
  try(ServerSocket server = new ServerSocket(19)){
    while(true){
      try(Socket connection = server.accept()){
        OutputStream out = connection.getOutputStream();
        int firstPrintableCharacter = 33;
        int numberOfPrintableCharacter = 94;
        int numberOfCharactersPerLine = 72;
        int start = firstPrintableCharacter;
        while(true){
          for(int i = start ;
              i < start + numberOfCharactersPerLine ; i++){
            out.write
            (firstPrintableCharacter + (i - firstPrintableCharacter) % numberOfPrintableCharacter);
          }
          out.write('\r');
          out.write('\n');
          start = firstPrintableCharacter + (start + 1 - firstPrintableCharacter) % numberOfPrintableCharacter;
        }
      }catch (IOException e) {
        e.printStackTrace();
      }
    }
  } catch (IOException e) {
    e.printStackTrace();
  }
}

客户端代码:

try {
  SocketAddress address = new InetSocketAddress("127.0.0.1", 19);
  SocketChannel client = SocketChannel.open(address);
  ByteBuffer buffer = ByteBuffer.allocate(74);
    WritableByteChannel out = Channels.newChannel(System.out);
  while(client.read(buffer) != -1){
    buffer.flip();
    out.write(buffer);
    buffer.clear();
  }
} catch (IOException e) {
  e.printStackTrace();
}
输出(无限循环):
]^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEF
^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFG
_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGH
`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHI
abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJ
bcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJK

启用非阻塞模式

上面的程序和使用输入/输出流的传统方式并没有太大差别。不过,可以调用ServerSocket的configureBlocking(false)方法将其设置为非阻塞模式。这个模式下,如果没有可用的数据,read()方法会立即返回,这让客户端可以去做其他事情。不过,由于read()方法在读不到数据时会返回0,读取数据的循环需要做一些改动:

while(true){
  //这里可以写每次循环都要做的事,无论有没有读到数据
  int n = client.read(buffer);
  if(n > 0){
    buffer.flip();
    out.write(buffer);
    buffer.clear();
  }else if (n == -1) {
    //除非服务器故障,否则不会发生
    break;
  }
}

总结

以上就是本文关于Java 非阻塞I/O使用方法的全部内容,希望对大家有所帮助。欢迎各位参阅:Java网络编程基础篇之单向通信,  Java使用代理进行网络连接方法示例等,有什么问题可以随时留言,小编会及时回复大家。感谢大家对本站的支持!

相关文章

  • 如何让java只根据数据库表名自动生成实体类

    如何让java只根据数据库表名自动生成实体类

    今天给大家带来的知识是关于Java的,文章围绕着如何让java只根据数据库表名自动生成实体类展开,文中有非常详细的介绍,需要的朋友可以参考下
    2021-06-06
  • mybatis interceptor 处理查询参数及查询结果的实例代码

    mybatis interceptor 处理查询参数及查询结果的实例代码

    这篇文章主要介绍了mybatis interceptor 处理查询参数及查询结果,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2017-01-01
  • java拓展集合工具类CollectionUtils

    java拓展集合工具类CollectionUtils

    这篇文章主要为大家详细介绍了java拓展集合工具类CollectionUtils,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-03-03
  • 详解Java中Thread 和Runnable区别

    详解Java中Thread 和Runnable区别

    这篇文章主要介绍了Java中Thread 和Runnable的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • Spring AOP定义Before增加实战案例详解

    Spring AOP定义Before增加实战案例详解

    这篇文章主要介绍了Spring AOP定义Before增加,结合实例形式详细分析了Spring面向切面AOP定义Before增加相关定义与使用技巧,需要的朋友可以参考下
    2020-01-01
  • 详解spring boot中使用JdbcTemplate

    详解spring boot中使用JdbcTemplate

    JdbcTemplate 是在JDBC API基础上提供了更抽象的封装,并提供了基于方法注解的事务管理能力。 通过使用SpringBoot自动配置功能并代替我们自动配置beans,下面给大家介绍spring boot中使用JdbcTemplate相关知识,一起看看吧
    2017-04-04
  • SpringBoot 使用jwt进行身份验证的方法示例

    SpringBoot 使用jwt进行身份验证的方法示例

    这篇文章主要介绍了SpringBoot 使用jwt进行身份验证的方法示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-12-12
  • java持久层框架mybatis防止sql注入的方法

    java持久层框架mybatis防止sql注入的方法

    下面小编就为大家带来一篇java持久层框架mybatis防止sql注入的方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-10-10
  • hibernate测试时遇到的几个异常及解决方法汇总

    hibernate测试时遇到的几个异常及解决方法汇总

    今天小编就为大家分享一篇关于hibernate测试时遇到的几个异常及解决方法汇总,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • 如何用Java来进行文件切割和简单的内容过滤的实现

    如何用Java来进行文件切割和简单的内容过滤的实现

    这篇文章主要介绍了如何用Java来进行文件切割和简单的内容过滤的实现,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-01-01

最新评论