关于阻塞与非阻塞:https://www.cnblogs.com/jhxxb/p/11272727.html

一、传统的 IO 流都是阻塞式的

当一个线程调用 read() 或 write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。

因此,在网络通信进行 IO 操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量客户端时,性能急剧下降。

package nio;

import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Scanner; public class TestBlockingNIO { // 客户端
@Test
public void client() throws IOException {
// 获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898)); // 分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024); // 发送到服务端
Scanner scan = new Scanner(System.in);
while (scan.hasNext()) {
String str = scan.next();
buf.put(str.getBytes());
buf.flip();
sChannel.write(buf);
buf.clear(); if ("exit".equals(str)) {
break;
}
} // 接收服务端的反馈
sChannel.shutdownOutput();
int len = 0;
while ((len = sChannel.read(buf)) != -1) {
buf.flip();
System.out.println(new String(buf.array(), 0, len));
buf.clear();
} // 关闭通道
sChannel.close();
} // 服务端
@Test
public void server() throws IOException {
// 获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open(); // 绑定连接
ssChannel.bind(new InetSocketAddress(9898)); retry:
while (true) {
// 获取客户端连接的通道
SocketChannel sChannel = ssChannel.accept(); // 分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024); // 接收客户端的数据
while (sChannel.read(buf) != -1) {
String str = new String(buf.array()).trim();
if ("exit".equals(str)) {
break retry;
}
buf.flip();
System.out.println(str);
buf.clear();
} // 发送反馈给客户端
buf.put("服务端接收数据成功!".getBytes());
buf.flip();
sChannel.write(buf); // 关闭通道
sChannel.close();
} // 关闭通道
ssChannel.close();
}
}

二、Java NIO 是非阻塞式的

当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。

线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。

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

注:NIO 的 IO 行为还是同步的。

/*
* 使用 NIO 完成网络通信的三个核心:
*
* 1. 通道(Channel):负责连接
* java.nio.channels.Channel 接口:
* |--SelectableChannel
* |--SocketChannel
* |--ServerSocketChannel
* |--DatagramChannel
*
* |--Pipe.SinkChannel
* |--Pipe.SourceChannel
*
* 2. 缓冲区(Buffer):负责数据的存取
*
* 3. 选择器(Selector):是 SelectableChannel 的多路复用器。用于监控 SelectableChannel 的 IO 状况
* 可以监听的事件类型(可使用 SelectionKey 的四个常量表示)
* 读: SelectionKey.OP_READ (1)
* 写: SelectionKey.OP_WRITE (4)
* 连接: SelectionKey.OP_CONNECT(8)
* 接收: SelectionKey.OP_ACCEPT (16)
*
* Selector 常用方法
* Set<SelectionKey> keys():所有的 SelectionKey 集合。代表注册在该 Selector上的 Channel
* selectedKeys():被选择的 SelectionKey 集合。返回此Selector的已选择键集
* intselect():监控所有注册的 Channel,当它们中间有需要处理的 IO 操作时,该方法返回,并将对应得的 SelectionKey 加入被选择的 SelectionKey 集合中,该方法返回这些 Channel 的数量。
* int select(long timeout):可以设置超时时长的 select() 操作
* int selectNow():执行一个立即返回的 select() 操作,该方法不会阻塞线程
* Selector wakeup():使一个还未返回的 select() 方法立即返回
* void close():关闭该选择器
*/

1.TCP-SocketChannel

package nio;

import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner; public class TestNonBlockingNIO { //客户端
@Test
public void client() throws IOException {
// 获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898)); // 切换非阻塞模式
sChannel.configureBlocking(false); // 分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024); // 发送数据给服务端
Scanner scan = new Scanner(System.in); while (scan.hasNext()) {
String str = scan.next();
buf.put((new Date().toString() + "\n" + str).getBytes());
buf.flip();
sChannel.write(buf);
buf.clear();
} // 关闭通道
sChannel.close();
} //服务端
@Test
public void server() throws IOException {
// 获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open(); // 切换非阻塞模式
ssChannel.configureBlocking(false); // 绑定连接
ssChannel.bind(new InetSocketAddress(9898)); // 获取选择器
Selector selector = Selector.open(); // 将通道注册到选择器上, 并且指定“监听接收事件”
ssChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ); // 轮询式的获取选择器上已经“准备就绪”的事件
while (selector.select() > 0) { // 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while (it.hasNext()) {
// 获取准备“就绪”的是事件
SelectionKey sk = it.next(); // 判断具体是什么事件准备就绪
if (sk.isAcceptable()) {
// 若“接收就绪”,获取客户端连接
SocketChannel sChannel = ssChannel.accept(); // 切换非阻塞模式
sChannel.configureBlocking(false); // 将该通道注册到选择器上
sChannel.register(selector, SelectionKey.OP_READ);
} else if (sk.isReadable()) {
// 获取当前选择器上“读就绪”状态的通道
SocketChannel sChannel = (SocketChannel) sk.channel(); // 读取数据
ByteBuffer buf = ByteBuffer.allocate(1024); int len = 0;
while ((len = sChannel.read(buf)) > 0) {
buf.flip();
System.out.println(new String(buf.array(), 0, len));
buf.clear();
}
} // 移除当前 SelectionKey
it.remove();
}
}
}
}

2.UDP-DatagramChannel

package nio;

import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner; public class TestNonBlockingNIO2 { @Test
public void send() throws IOException{
DatagramChannel dc = DatagramChannel.open(); dc.configureBlocking(false); ByteBuffer buf = ByteBuffer.allocate(1024); Scanner scan = new Scanner(System.in); while(scan.hasNext()){
String str = scan.next();
buf.put((new Date().toString() + ":\n" + str).getBytes());
buf.flip();
dc.send(buf, new InetSocketAddress("127.0.0.1", 9898));
buf.clear();
} dc.close();
} @Test
public void receive() throws IOException{
DatagramChannel dc = DatagramChannel.open(); dc.configureBlocking(false); dc.bind(new InetSocketAddress(9898)); Selector selector = Selector.open(); dc.register(selector, SelectionKey.OP_READ); while(selector.select() > 0){
Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while(it.hasNext()){
SelectionKey sk = it.next(); if(sk.isReadable()){
ByteBuffer buf = ByteBuffer.allocate(1024); dc.receive(buf);
buf.flip();
System.out.println(new String(buf.array(), 0, buf.limit()));
buf.clear();
}
} it.remove();
}
}
}

三、Pipe(管道)

Java NIO 管道是 2 个线程之间的单向数据连接。Pipe 有一个 source 通道和一个 sink 通道。数据会被写到 sink 通道,从 source 通道读取。

package nio;

import org.junit.Test;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe; public class TestPipe { @Test
public void test() throws IOException {
// 获取管道
Pipe pipe = Pipe.open(); ByteBuffer buf = ByteBuffer.allocate(1024);
buf.put("通过单向管道发送数据".getBytes());
buf.flip(); // 将缓冲区中的数据写入管道
Pipe.SinkChannel sinkChannel = pipe.sink();
sinkChannel.write(buf); ByteBuffer buf2 = ByteBuffer.allocate(1024);
// 读取缓冲区中的数据
Pipe.SourceChannel sourceChannel = pipe.source();
int len = sourceChannel.read(buf2);
System.out.println(new String(buf2.array(), 0, len)); sourceChannel.close();
sinkChannel.close();
}
}


https://mp.weixin.qq.com/s?__biz=Mzg3MjA4MTExMw==&mid=2247484746&idx=1&sn=c0a7f9129d780786cabfcac0a8aa6bb7

Java-NIO 之 Selector 与 Pipe的更多相关文章

  1. Java NIO类库Selector机制解析(下)

    五.  迷惑不解 : 为什么要自己消耗资源? 令人不解的是为什么我们的Java的New I/O要设计成这个样子?如果说老的I/O不能多路复用,如下图所示,要开N多的线程去挨个侦听每一个Channel ...

  2. Java NIO类库Selector机制解析(上)

    一.  前言 自从J2SE 1.4版本以来,JDK发布了全新的I/O类库,简称NIO,其不但引入了全新的高效的I/O机制,同时,也引入了多路复用的异步模式.NIO的包中主要包含了这样几种抽象数据类型: ...

  3. Java NIO类库Selector机制解析--转

    一.  前言 自从J2SE 1.4版本以来,JDK发布了全新的I/O类库,简称NIO,其不但引入了全新的高效的I/O机制,同时,也引入了多路复用的异步模式.NIO的包中主要包含了这样几种抽象数据类型: ...

  4. Java NIO 选择器(Selector)的内部实现(poll epoll)

    http://blog.csdn.net/hsuxu/article/details/9876983 之前强调这么多关于linux内核的poll及epoll,无非是想让大家先有个认识: Java NI ...

  5. Java NIO之Selector(选择器)

    历史回顾: Java NIO 概览 Java NIO 之 Buffer(缓冲区) Java NIO 之 Channel(通道) 其他高赞文章: 面试中关于Redis的问题看这篇就够了 一文轻松搞懂re ...

  6. Java NIO 选择器(Selector)的内部实现(poll epoll)(转)

    转自:http://blog.csdn.net/hsuxu/article/details/9876983 之前强调这么多关于linux内核的poll及epoll,无非是想让大家先有个认识: Java ...

  7. 转:Java NIO系列教程(九) Pipe

    Java NIO 管道是2个线程之间的单向数据连接.Pipe有一个source通道和一个sink通道.数据会被写到sink通道,从source通道读取. 这里是Pipe原理的图示: 创建管道 通过Pi ...

  8. Java NIO之Selector

    选择器是JavaNIO重磅推出的一个概念:在旧有的系统中为了跟踪多端口消息,需要为每一个端口配备一个线程做监听:但是有了selector就不需要了,一个Selector可以管理一众渠道(channel ...

  9. Java NIO学习笔记八 Pipe

    Java NIO Pipe Java NIO管道是两个线程之间的单向数据连接.Pipe 具有源信道和接受通道.您将数据写入sink通道.然后可以从源通道读取该数据. 这是一个原理的Pipe流程图: J ...

  10. Netty快速入门(05)Java NIO 介绍-Selector

    Java NIO Selector Selector是Java NIO中的一个组件,用于检查一个或多个NIO Channel的状态是否处于可读.可写.如此可以实现单线程管理多个channels,也就是 ...

随机推荐

  1. QT版本下载链接

    http://download.qt.io/archive/qt/

  2. ORACLE_笔记_练习题目

    一.plsql用法网址及时复习 extract()函数----用于截取年.月.日.时.分.秒 https://www.cnblogs.com/xqzt/p/4477239.html case when ...

  3. 【python+beautifulsoup4】Beautifulsoup4

    Beautiful soup将复杂HTML文档转换成一个复杂的属性结构,每个节点都是python对象,所有对象可归纳为4种Tag,NavigableString,BeautifulSoup,Comme ...

  4. Vmvare 虚拟机固定IP

    首先我们打开虚拟机的虚拟网络编辑器,打开vmvare菜单栏的编辑,选择虚拟网络编辑器.   在打开的网络虚拟器中,会看到相关信息,虚拟机网络类型采用的NAT模式,子网地址是192.168.89.0,虚 ...

  5. LNMP安装与配置之Nginx

    Nginx简介 Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务.Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Ramb ...

  6. 异步函数Demo

    private static async Task<String> IssueClientRequestAsync(string serverName, string message) { ...

  7. redis四种部署方式

    1.单点 2.主从 3.哨兵 4.集群

  8. C#新增按钮

    代码亲测可用,似乎不需要“ADD”,如下:form_load段:for (int i = 0; i < 10; i++){btn = new Button();btn.Parent = this ...

  9. BZOJ 3289: Mato的文件管理 (区间查询逆序对)

    这道题就是不要求强制在线的 BZOJ 3744 Gty的妹子序列 所以说离线做法有莫队,在线做法见上面连接. 这里贴出常数巨大O(nnlogn)O(n\sqrt nlogn)O(nn​logn)分块+ ...

  10. mysql批量新增和批量删除

    首先推荐使用PreparedStatement的批量处理操作. Connection conn = null; PreparedStatement stmt = null; try{ Class.fo ...