当前位置: 首页 > news >正文

4.NIO的非阻塞式网络通信

/*阻塞 和 非阻塞 是对于 网络通信而言的*/

  /*原先IO通信在进行一些读写操作 或者 等待 客户机连接 这种,是阻塞的,必须要等到有数据被处理,当前线程才被释放*/

  /*NIO 通信 是将这个阻塞的过程 丢给了选择器,客户端和 服务器端 之间建立的通道,都会注册到 选择器上,然后用选择器 实时监控 我们这些通道上的状况*/

  /*当某一个通道上 某一个请求的事件 完全准备就绪时,那么选择器才会将 这个任务 分配到服务器上的一个 或多个线程中*/

 

/*阻塞 与 非阻塞*/

  传统的IO 流都是 阻塞式的。也就是说,当一个线程调用 read() 或 write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务

  因此,在完成网络通信进行IO操作时,由于线程会阻塞,所以 服务器必须为每个客户端提供一个独立的线程进行处理 (这也是原来使用IO通信的解决办法)

  但是,当服务器需要处理大量客户端时,性能急剧下降


Java NIO 是非阻塞式的。当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作,所以单独的线程 可以管理 多个 输入和 输出通道。

因此,NIO可以让服务器端使用一个或有限几个线程来同时处理连接到服务器的所有客户端

 

/*NIO通信非阻塞的原因在于 选择器 的存在,如果不使用选择器,同样会出现阻塞的现象*/

  关于NIO阻塞的演示:
  

 1 public class TestBlockingNIO2 {
 2 
 3     // 客户端
 4     @Test
 5     public void client() throws IOException {
 6         //1.获取通道
 7         SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
 8                 
 9         //2.分配指定大小的缓冲区
10         ByteBuffer buffer = ByteBuffer.allocate(1024);
11         
12         //3.读取本地文件,并使用SocketChannel发送到服务器
13         FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
14         while(inChannel.read(buffer) != -1) {
15             buffer.flip();
16             socketChannel.write(buffer);
17             buffer.clear();
18         }
19         
20         //在这里服务端不知道 客户端数据 发没发完,线程就一直处于阻塞状态
21         //通过shutdownOutput 来告知服务器 我不发送数据了
22         
23         //之所以上一个 程序 不用 shutdown 线程也能结束,可能是因为上一个程序只需要向服务端发送数据,而不需要接收数据,能够判断出是否发送完了数据
24         socketChannel.shutdownOutput();
25         
26         //4.接收服务端传来的反馈
27         int length = 0;  //这里指定一下 从 buffer 读取的长度,因为在这里buffer 中还带有了图片信息
28         while((length = socketChannel.read(buffer)) != -1) {
29             buffer.flip();
30             System.out.println(new String(buffer.array(),0,length));
31             buffer.clear();
32         }
33         
34         //4.关闭通道
35         inChannel.close();
36         socketChannel.close();
37     }
38 
39     // 服务端
40     @Test
41     public void server() throws IOException {
42         //1.获取通道
43         ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
44         
45         //2.绑定连接端口号
46         serverSocketChannel.bind(new InetSocketAddress(8888));
47         
48         //3.获取客户端连接的通道
49         SocketChannel socketChannel = serverSocketChannel.accept();
50     
51         //4.分配指定大小的缓冲区
52         ByteBuffer buffer = ByteBuffer.allocate(1024);
53         
54         //5.接收客户端发来的数据,并保存到本地
55         FileChannel outChannel = FileChannel.open(Paths.get("3.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
56         while(socketChannel.read(buffer) != -1) {
57             buffer.flip();
58             outChannel.write(buffer);
59             buffer.clear();
60         }
61         
62         //6.发送反馈给客户端
63         buffer.put("服务端接收数据成功".getBytes());
64         buffer.flip();
65         socketChannel.write(buffer);
66         
67         //6.关闭通道
68         outChannel.close();
69         socketChannel.close();
70         serverSocketChannel.close();
71         
72     }
73 
74 }

 

 

/*选择器 (Selector)*/

选择器(Selector)是 SelectableChannle 对象的多路复用器,

/*Selector 可以同时 监控多个SelectableChannel 的 IO 状况*/,也就是说,

利用 /*Selector 可使一个单独的线程管理多个 Channel */ selector 是 非阻塞的核心

 

 

 

/*选择器(Selector)的应用*/

  1.创建 Selector :通过调用Selector.open() 方法创建一个 Selector

  Selector selector = Selector.open();     //创建选择器

  2.向选择器注册通道:SelectableChannel.register(Selector sel,int ops)

    如:SelectionKey key = channel.register(selector,SelectionKey.OP_READ)

  当调用 register (Selector sel,int ops) 为通道 注册选择器时,选择器对通道的监听事件,需要通过第二个参数 ops 指定

 

  3.可以监听的事件类型(可使用 SelectionKey 的四个常量表示):

    读:SelectionKey.OP_READ (1)

    写:SelectionKey.OP_WRITE (4)

    连接:SelectionKey.OP_CONNECT (8)

    接收:SelectionKey.OP_ACCEPT (16)


  若注册时不止监听一个事件,则可以使用 “位或” 操作符 (|)连接      :  int interestSet = SelectionKey.OP_READ|SelectionKey.OP_WRITE

 

  /*SocketChannel*/

    Java NIO 中的 SocketChannel 是一个连接到TCP网络套接字的通道

    操作步骤:打开SocketChannel 读写数据 关闭SocketChannel

  /*ServerSocketChannel*/

    Java NIO 中的 ServerSocketChannel 是一个 可以监听新进来的TCP连接的通道,就像标准IO 中的 ServerSocket一样

  /*DatagramChannel*/

    Java NIO 中的 DatagramChannel 是一个能收发UDP包的通道

 

使用选择器完成NIO的非阻塞式通信:

  1 /*
  2  * 一:使用NIO完成网络通信的三个核心:
  3  *    
  4  *1.通道(Channel) :负责连接
  5  *    
  6  *
  7  *2.缓冲区(Buffer) :负责数据的存取
  8  *    
  9  *
 10  *3.选择器(Selector):监控SelectableChannel的IO状况
 11  *    
 12  * */
 13 /*可以开启多个客户端,访问服务端,客户端的数据传递给服务端是非阻塞式的
 14  * 最后的效果 类似于聊天室
 15  * */
 16 public class TestNonBlockingNIO {
 17     //客户端
 18     @Test
 19     public void client() throws IOException {
 20         //1.获取通道
 21         SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
 22         
 23         //2.切换非阻塞模式
 24         socketChannel.configureBlocking(false);
 25         
 26         //3.分配指定大小的缓冲区
 27         ByteBuffer buffer = ByteBuffer.allocate(1024);
 28         
 29         //4.发送数据到服务端
 30         Scanner scanner  = new Scanner(System.in);
 31         while(scanner.hasNext()) {
 32             String str = scanner.next();
 33             buffer.put((new Date().toString() + "\n" + str).getBytes());
 34             buffer.flip();
 35             socketChannel.write(buffer);
 36             buffer.clear();
 37         }
 38         
 39         /*buffer.put(new Date().toString().getBytes());
 40         buffer.flip();
 41         socketChannel.write(buffer);
 42         buffer.clear();*/
 43         
 44         //5.关闭通道
 45         socketChannel.close();
 46     }
 47     
 48     //服务端
 49     @Test
 50     public void server() throws IOException {
 51         //1.获取通道
 52         ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
 53         
 54         //2.切换到非阻塞模式
 55         serverSocketChannel.configureBlocking(false);
 56         
 57         //3.绑定连接端口号
 58         serverSocketChannel.bind(new InetSocketAddress(8888));
 59         
 60         //4.获取选择器
 61         Selector selector = Selector.open();
 62         
 63         //5.将通道注册选择器
 64         //通过SelectionKey 指定 这个 选择器 对 通道的监听事件 (这里是 accept)( SelectionKey.OP_ACCEPT)
 65         //通过选择器监听的方式,只有等 客户端 连接 准备 就绪了,才会 accept 这个连接
 66         serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
 67         
 68         //6.轮询式的 获取选择器上已经 '准备就绪' 的事件 
 69         while(selector.select() > 0) {   //这代表了 当前选择器 有准备就绪的 事件(第一次循环中因为这个选择器只监听了 accept,所以这个准备就绪的事件就是accept )
 70             
 71             //7.获取当前选择器中,所有注册的 ‘选择键(已就绪的监听事件)’
 72             Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
 73             while(iterator.hasNext()) {
 74                 //8.获取准备 “就绪的” 事件
 75                 SelectionKey sk = iterator.next();
 76                 
 77                 //9.判断具体是什么事件准备就绪 (是否是 accept 准备就绪)
 78                 if(sk.isAcceptable()) {
 79                     //10. 如果客户端连接 准备就绪,就使用accept 来接收
 80                     SocketChannel clientChannel = serverSocketChannel.accept();
 81                     
 82                     //11.切换到非阻塞模式
 83                     clientChannel.configureBlocking(false);
 84                     
 85                     //12.将该客户端的通道注册到选择器上(因为要发送数据都服务器端,想要非阻塞式的,就要注册选择器)
 86                     clientChannel.register(selector, SelectionKey.OP_READ);
 87                 } else if(sk.isReadable()) {   //第一次循环SelectionKey 中,是没有 read 的,SelectionKey还没有更新,//再一次  轮询式的 获取选择器上已经 '准备就绪' 的事件 后,
 88                     //13.获取当前 选择器 上  “读就绪” 状态的通道                                                      //就可以 获取当前 选择器 上  “读就绪” 状态的通道
 89                     SocketChannel socketChannel = (SocketChannel) sk.channel();                           
 90                                               
 91                     //14.读取客户端发来的数据
 92                     ByteBuffer buffer = ByteBuffer.allocate(1024);
 93                     
 94                     //注:这里不能写 -1,只能写 > 0,
 95                     //可能因为会客户端一直会从控制台读取数据,然后发送给服务端,所以将通道中的数据读到缓冲区中时,因为可能一直有数据进来,所以不会返回 -1,
 96                     //如果写 != -1,会一直陷在循环中 ,必须写 > 0,确定是有真实的数据过来的
 97             
 98                     while(socketChannel.read(buffer) > 0) {
 99                         buffer.flip();
100                         System.out.println(new String(buffer.array()));
101                         buffer.clear();
102                     }
103                 }
104                 
105                 //15.取消选择键 SelectionKey,不取消,他就一直有效,SelectionKey 就无法更新
106                 iterator.remove();
107             }
108         }
109     }
110 }

 

 

转载于:https://www.cnblogs.com/xuzekun/p/7435681.html

相关文章:

  • 远程通信的几种选择(RPC,Webservice,RMI,JMS的区别)
  • 从Code Review 谈如何做技术(zz)酷 壳
  • 大白话讲Zookeeper能做什么?(一):命名服务与配置管理
  • 大数据拼精准 可否触动电商个性营销神经
  • 二进制安装MySQL5.5.57
  • Ubuntu 安装和使用 Supervisor(进程管理)
  • 本地虚拟化开发环境 vagrant+virtualbox
  • centos下为php添加gd/curl/zip扩展
  • Simple2D-20(重构)
  • 中国好同事!帮程序猿跟姑娘表白,他们组了一支乐队
  • 洛谷—— P2812 校园网络
  • 客观看待社保系统管理漏洞
  • ssh 免密码登录
  • 积极推动中国金融资产市场化流动
  • 互联网时代 数据中心如何满足未来需求
  • 收藏网友的 源程序下载网
  • [译] 理解数组在 PHP 内部的实现(给PHP开发者的PHP源码-第四部分)
  • 【React系列】如何构建React应用程序
  • 【剑指offer】让抽象问题具体化
  • android 一些 utils
  • Angular Elements 及其运作原理
  • CSS魔法堂:Absolute Positioning就这个样
  • git 常用命令
  • JavaScript设计模式与开发实践系列之策略模式
  • Laravel 中的一个后期静态绑定
  • Mybatis初体验
  • Spring声明式事务管理之一:五大属性分析
  • vue从入门到进阶:计算属性computed与侦听器watch(三)
  • 官方新出的 Kotlin 扩展库 KTX,到底帮你干了什么?
  • 缓存与缓冲
  • 前端
  • 让你成为前端,后端或全栈开发程序员的进阶指南,一门学到老的技术
  • 让你的分享飞起来——极光推出社会化分享组件
  • 什么软件可以剪辑音乐?
  • 我建了一个叫Hello World的项目
  • 一加3T解锁OEM、刷入TWRP、第三方ROM以及ROOT
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • ​configparser --- 配置文件解析器​
  • ​直流电和交流电有什么区别为什么这个时候又要变成直流电呢?交流转换到直流(整流器)直流变交流(逆变器)​
  • #pragma data_seg 共享数据区(转)
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (env: Windows,mp,1.06.2308310; lib: 3.2.4) uniapp微信小程序
  • (poj1.3.2)1791(构造法模拟)
  • (Redis使用系列) SpringBoot中Redis的RedisConfig 二
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (动手学习深度学习)第13章 计算机视觉---图像增广与微调
  • (附程序)AD采集中的10种经典软件滤波程序优缺点分析
  • (离散数学)逻辑连接词
  • (十八)SpringBoot之发送QQ邮件
  • (一)Neo4j下载安装以及初次使用
  • (转)mysql使用Navicat 导出和导入数据库
  • **python多态
  • .NET 指南:抽象化实现的基类
  • .NET单元测试
  • .NET关于 跳过SSL中遇到的问题