网络编程之NIO(二)

淡淡的烟草味﹌ 2022-02-25 13:16 376阅读 0赞

一.NIO定义

  1. Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。学习NIO首先要知道其有两个核心,一个是ByteBuffer字节缓冲区,一个是Selector通道选择器

二.ByteBuffer字节缓冲区

1.ByteBuffer设计原理

在这里插入图片描述
2.demo测试

  1. import java.nio.ByteBuffer;
  2. /**
  3. * Created by ${ligh} on 2019/2/22 下午4:54
  4. *
  5. * 图解参考 https://www.processon.com/
  6. */
  7. public class TestBufferDemo {
  8. public static void main(String[] args) {
  9. //1.创建ByteBuffer
  10. byte[] bytes = new byte[5];
  11. ByteBuffer buffer = ByteBuffer.wrap(bytes);
  12. showMetircs("初始化状态",buffer); //打印结果: 0 5 5
  13. //写入字节
  14. buffer.put((byte) 'a');
  15. buffer.put((byte) 'b');
  16. showMetircs("写入两个字节之后",buffer); //打印结果: 2 5 5
  17. //遍历读取数据
  18. /* while (buffer.hasRemaining()){
  19. System.out.print(buffer.get()+" "); //读取到的是 0 0 0
  20. }
  21. System.out.println();*/
  22. //为了读取到添加进去的a,b两字节,调用flip()方法
  23. buffer.flip();
  24. //遍历读取数据
  25. while (buffer.hasRemaining()){
  26. System.out.print(buffer.get()+" "); //读取到的是 97 98 此时position和limit的值都是2,不能再存储数据了,因为没有缓冲区了
  27. }
  28. System.out.println();
  29. //查看此时buffer中属性的值
  30. showMetircs("读完a,b两字节之后",buffer); //打印结果 2 2 5 表示pos和limit的之间没有存储空间了
  31. //调用clear()方法,然后添加字节c
  32. buffer.clear();
  33. buffer.put((byte)'c');
  34. showMetircs("写完c字节之后",buffer); //打印结果 1 5 5
  35. //遍历添加完c字节之后的缓冲区buffer
  36. while (buffer.hasRemaining()) {
  37. System.out.print(buffer.get() + " "); //读取到的数据是 98 0 0 0
  38. }
  39. //如果想读到新添加的字节c也就是98只需要调用flip即可
  40. }
  41. public static void showMetircs(String state,ByteBuffer buffer){
  42. System.out.println(state+ " pos: "+buffer.position()+" ,limit: "+buffer.limit()+" ,capacity: "+buffer.capacity());
  43. }
  44. }

三.Selector通道选择器

1.定义:

  1. Selector会自动把注册队列中的附件封装成一个个的SelectionKey放在事件队列中,然后一直遍历事件队列获得SelectionKey对应的通道类型或者附件,然后执行相应的IO操作类型是accept还是read或者write,最后执行完成之后把事件队列中的SelectionKey移除,注册队列不进行操作,继续监控.作用就是辅助检查Channel状态,将就绪的IO提供给线程。

2.设计原理
在这里插入图片描述
三.NIO编程套路

1.服务端:

  1. 创建ServerSocketChannel
  2. 绑定监听端口
  3. 设置通道非阻塞
  4. 创建通道选择器
  5. 注册事件类型
  6. 迭代遍历事件列表

2.客户端:

  1. 创建客户端实例 Socket s = new Socket();
  2. 连接服务器 s.connect(new InetSocketAddress(host,port));
  3. 发送请求 OutputStream out = s.getOutputStream();
  4. 获取响应 InputStream result = s.getInputStream();
  5. 关闭资源 s.close();

四.实战demo演练

1.服务端:

  1. import java.io.ByteArrayOutputStream;
  2. import java.net.InetSocketAddress;
  3. import java.nio.ByteBuffer;
  4. import java.nio.channels.SelectionKey;
  5. import java.nio.channels.Selector;
  6. import java.nio.channels.ServerSocketChannel;
  7. import java.nio.channels.SocketChannel;
  8. import java.util.Date;
  9. import java.util.Iterator;
  10. /**
  11. * 服务端
  12. *
  13. */
  14. public class NioServerBootstrap {
  15. public static void main(String[] args) throws Exception{
  16. //1.创建ServerSocketChannel
  17. ServerSocketChannel ssc = ServerSocketChannel.open();
  18. //2.绑定监听端口
  19. ssc.bind(new InetSocketAddress(9999));
  20. //3.设置通道非阻塞
  21. ssc.configureBlocking(false);
  22. //4.创建通道选择器
  23. Selector selector = Selector.open();
  24. //5.注册事件类型
  25. ssc.register(selector, SelectionKey.OP_ACCEPT);
  26. //6.遍历事件列表
  27. while (true){
  28. System.out.println("------我在9999-------");
  29. int n = selector.select();
  30. //判断是否阻塞 0表示阻塞
  31. if(n>0){ //n大于0表示有事件需要处理
  32. Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
  33. while (iterator.hasNext()){
  34. //遍历获取每一个key
  35. SelectionKey key = iterator.next();
  36. //处理对应的IO事件
  37. if(key.isAcceptable()){
  38. System.out.println("------isAcceptable------");
  39. //获取通道
  40. ServerSocketChannel channel = (ServerSocketChannel) key.channel();
  41. //调用channel的转发
  42. SocketChannel socketChannel = channel.accept();//立即返回一个非null的SocketChannel
  43. //设置通道非阻塞
  44. socketChannel.configureBlocking(false);
  45. //注册到通道选择器注册队列
  46. socketChannel.register(selector,SelectionKey.OP_READ);
  47. }else if(key.isReadable()){
  48. System.out.println("------isReadable------");
  49. //读就绪的情况下进入
  50. SocketChannel channel = (SocketChannel) key.channel();
  51. //读事件
  52. //用户意图
  53. ByteBuffer buffer = ByteBuffer.allocate(1024);
  54. ByteArrayOutputStream req = new ByteArrayOutputStream();
  55. while (true){
  56. buffer.clear();
  57. int num = channel.read(buffer);
  58. if(num == -1) break;
  59. buffer.flip();
  60. req.write(buffer.array(),0,num);
  61. }
  62. channel.register(selector,SelectionKey.OP_WRITE,req);
  63. }else if(key.isWritable()){
  64. System.out.println("------isWritable-----");
  65. //写事件
  66. //获取附件
  67. ByteArrayOutputStream req = (ByteArrayOutputStream) key.attachment();
  68. ByteBuffer buffer = ByteBuffer.wrap((new Date().toLocaleString()).getBytes());
  69. SocketChannel channel = (SocketChannel) key.channel();
  70. channel.write(buffer);
  71. //告诉通道写结束
  72. channel.shutdownOutput();
  73. //关闭通道
  74. channel.close();
  75. }
  76. //移除已经处理的事件
  77. iterator.remove();
  78. }
  79. }else {
  80. continue;
  81. }
  82. }
  83. }
  84. }

2.客户端:

  1. import java.io.*;
  2. import java.net.InetSocketAddress;
  3. import java.net.Socket;
  4. /**
  5. * Created by ${ligh} on 2019/2/22 上午10:08
  6. *
  7. * 在NIO中服务端不一样,但是客户端几乎都是差不多的
  8. *
  9. * 客户端
  10. */
  11. public class Clientbootstrap {
  12. public static void main(String[] args) throws Exception{
  13. //1. 创建客户端实例
  14. Socket s = new Socket();
  15. //2. 连接服务器
  16. s.connect(new InetSocketAddress("127.0.0.1",9999));
  17. //3. 发送请求
  18. OutputStream out = s.getOutputStream();
  19. PrintWriter pw = new PrintWriter(out);
  20. pw.println("你好,我是客户端...");
  21. pw.flush();
  22. //告诉服务器 写结束
  23. s.shutdownOutput();
  24. //4. 获取响应
  25. InputStream result = s.getInputStream();
  26. InputStreamReader isr= new InputStreamReader(result);
  27. BufferedReader reader= new BufferedReader(isr);
  28. //读取响应
  29. String line = null;
  30. StringBuffer sb = new StringBuffer();
  31. while ((line = reader.readLine())!=null){
  32. sb.append(line);
  33. }
  34. System.out.println("客户端收到: "+sb.toString());
  35. //5. 关闭资源
  36. s.close();
  37. }
  38. }

五.拓展
通过NIO实现文件的拷贝,具体demo如下:

  1. import java.io.FileInputStream;
  2. import java.io.FileOutputStream;
  3. import java.nio.ByteBuffer;
  4. import java.nio.channels.FileChannel;
  5. /**
  6. * Created by ${ligh} on 2019/2/22 下午5:18
  7. *
  8. */
  9. public class TestFileCopy {
  10. public static void main(String[] args) throws Exception{
  11. // testWrite();
  12. // testRead();
  13. testCopy();
  14. }
  15. /**
  16. * 写文件
  17. *
  18. * 特点:
  19. * 应用 --> ByteBuffer --> 通道 --> 磁盘 应用将数据传到ByteBuffer中,然后通过通道将数据写到磁盘中
  20. *
  21. * @throws Exception
  22. */
  23. public static void testWrite() throws Exception{
  24. //所写入文件中的位置
  25. FileOutputStream fos = new FileOutputStream("aa.txt");
  26. //创建缓冲区
  27. ByteBuffer buffer = ByteBuffer.wrap(new byte[1024]);
  28. //创建通道
  29. FileChannel channel = fos.getChannel();
  30. //向缓冲区中存储数据
  31. buffer.put("hello world!".getBytes());
  32. //读到刚存进缓冲区中的数据
  33. buffer.flip();
  34. //写入到通道中
  35. channel.write(buffer);
  36. //关闭资源
  37. channel.close();
  38. fos.close();
  39. }
  40. /**
  41. * 读文件
  42. *
  43. * 特点:
  44. * 磁盘 ---> 通道 ---> ByteBuffer中 ---> 应用 磁盘数据通过通道写入到ByteBuffer中,然后应用从ByteBuffer中拿读取数据
  45. * @throws Exception
  46. */
  47. public static void testRead() throws Exception{
  48. //要读取的文件
  49. FileInputStream fis = new FileInputStream("aa.txt");
  50. //创建通道
  51. FileChannel inchannel = fis.getChannel();
  52. //创建缓冲区
  53. ByteBuffer buffer = ByteBuffer.wrap(new byte[1024]);
  54. //通道读取数据到buffer中,相当于存储到其中
  55. int i = inchannel.read(buffer);
  56. buffer.flip();
  57. //获取buffer中的数据
  58. System.out.println(new String(buffer.array(),0,i));
  59. //关闭资源
  60. inchannel.close();
  61. fis.close();
  62. }
  63. /**
  64. * 文件的拷贝
  65. *
  66. * 实际就是边读边写
  67. *
  68. * @throws Exception
  69. */
  70. public static void testCopy()throws Exception{
  71. FileInputStream fis = new FileInputStream("aa.txt");
  72. FileOutputStream fos = new FileOutputStream("bb.txt");
  73. //创建通道
  74. FileChannel inchannel = fis.getChannel();
  75. FileChannel outchannel = fos.getChannel();
  76. //创建ByteBuffer
  77. ByteBuffer buffer = ByteBuffer.wrap(new byte[1024]);
  78. //开始读取文件,直到读完为止
  79. while (true){
  80. //每次读之前首先要调用clear方法
  81. buffer.clear();
  82. int i = inchannel.read(buffer);
  83. //如果i等于-1就表示已经读完,否则就继续读
  84. if(i == -1) break;
  85. buffer.flip();
  86. //写出到文件中
  87. outchannel.write(buffer);
  88. }
  89. //关闭资源
  90. inchannel.close();
  91. outchannel.close();
  92. fis.close();
  93. fos.close();
  94. }
  95. }

总结
原来的BIO是一种多线程阻塞的IO,非常消耗资源,由此产生了NIO,这个单线程阻塞,多线程畅通的IO,单线程阻塞情况是通道选择器中的事件队列为空的情况下,所以不会消耗很多资源,因此是一种理想化的IO处理方式,但是,为了开发简单快捷,还是不建议使用,因此由此产生了NIO的框架Netty/Mina,一种便捷式的开发框架,具体介绍请看下一篇。

发表评论

表情:
评论列表 (有 0 条评论,376人围观)

还没有评论,来说两句吧...

相关阅读

    相关 网络编程NIO()

    一.NIO定义     Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线