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 }
时间: 2024-08-04 20:34:16

4.NIO的非阻塞式网络通信的相关文章

java nio学习三:NIO 的非阻塞式网络通信

一.阻塞和非阻塞 传统的 IO 流都是阻塞式的.也就是说,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务.因此,在完成网络通信进行 IO 操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量客户端时,性能急剧下降.Java NIO 是非阻塞模式的.当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务.线程通常将非阻塞 IO 的空闲时间用于在其他通道上

Java NIO实现非阻塞式socket通信

博主知识水平有限,只能提供一个个人的狭隘的理解,如果有新人读到这儿,建议看一下其他教程或者API,如果不明白,再来看一下:如果有dalao读到这儿,希望能指出理解中的问题~谢谢 Java提供了用于网络通信的socket和serversocket包,然而实现方式是阻塞式的,同一时间点上只能进行一个连接,这会带来不好的体验.当然了,我们也可以通过不断创建线程的方式管理连接,但线程多了的话反而会降低效率.于是Java推出了非阻塞式IO--channel.并且channel提供关于网络通信的相关chan

JAVA NIO学习记录2-非阻塞式网络通信

一.阻塞与非阻塞 传统的IO 流都是阻塞式的.也就是说,当一个线程调用read() 或write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务.因此,在完成网络通信进行IO 操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量客户端时,性能急剧下降. Java NIO 是非阻塞模式的.当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务.线程通常将非阻塞IO 的空闲时间用于在其他通道上执行I

Java基础:非阻塞式IO

转载请注明出处:jiq?钦's technical Blog 引言 JDK1.4中引入了NIO,即New IO,目的在于提高IO速度.特别注意JavaNIO不完全是非阻塞式IO(No-Blocking IO),因为其中部分通道(如FileChannel)只能运行在阻塞模式下,而其他的通道可以在阻塞式和非阻塞式之间进行选择. 尽管这样,我们还是习惯将Java NIO看作是非阻塞式IO,而前面介绍的面向流(字节/字符)的IO类库则是非阻塞的,详细来看,两者区别如下: IO NIO 面向流(Strea

JAVA基础知识之网络编程——-基于NIO的非阻塞Socket通信

阻塞IO与非阻塞IO 通常情况下的Socket都是阻塞式的, 程序的输入输出都会让当前线程进入阻塞状态, 因此服务器需要为每一个客户端都创建一个线程. 从JAVA1.4开始引入了NIO API, NIO可以实现非阻塞IO, 这样就可以使用一个线程处理所有的客户请求. 基于NIO的非阻塞Socket通信 服务器将用来监听客户端请求的channel注册到selector上,启动一个线程,使用selector的select()获取求情的客户端的channel数量, 当监听到有客户端请求时,就通过Sel

前段时间用java做了个非阻塞式仿飞秋聊天程序

采用Swing 布局 NIO非阻塞式仿飞秋聊天程序, 切换皮肤颜色什么的小功能以后慢慢做 启动主程序. 当用户打开主程序后自动获取局域网段IP可以在 设置 --> IP网段过滤, 拥有 JMF 视频聊天功能(取得视频流读取到ByteBuffer然后写入DatagramChannel), 其实什么功能都是可以加的后期, 简单介绍下 双击用户进行聊天 (第一版是基于channel通道的操作, 非阻塞式, 这一版本为阻塞式) 当有消息发来时自动开启一个线程处理客户端请求并接受数据,并在标题上提示收到新

Java基础知识强化之多线程笔记07:同步、异步、阻塞式、非阻塞式 的联系与区别

1. 同步: 所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回.但是一旦调用返回,就必须先得到返回值了. 换句话话说,调用者主动等待这个"调用"的结果. 对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已. 2. 异步: 所谓异步,"调用"在发出之后,这个调用就直接返回了,所以没有返回结果. 换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果.而是在"调用"发出后,"被调用者&q

非阻塞式JavaScript脚本介绍

JavaScript 倾向于阻塞浏览器某些处理过程,如HTTP 请求和界面刷新,这是开发者面临的最显著的性能问题.保持JavaScript文件短小,并限制HTTP请求的数量,只是创建反应迅速的网页应用的第一步.一个应用程序所包含的功能越多,所需要的JavaScript 代码就越大,保持源码短小并不总是一种选择.尽管下载一个大JavaScript 文件只产生一次HTTP 请求,却会锁定浏览器一大段时间.为避开这种情况,你需要向页面中逐步添加JavaScript,某种程度上说不会阻塞浏览器.非阻塞脚

C语言非阻塞式键盘监听

监听键盘可以使用C语言的字符输入函数,例如 getchar.getch.getche 等,我们会在<结合缓冲区谈谈C语言getchar().getche().getch()的区别>一节中重点讲解它们的区别. 使用getche函数监听键盘的例子: #include <stdio.h> #include <conio.h> int main(){ char ch; int i = 0; //循环监听,直到按Esc键退出 while(ch = getch()){ if(ch