一.堵塞式与非堵塞式

在传统IO中,将数据由当前线程从客户端传入服务端,由服务端的内核进行判断传过来的数据是否合法,内核中是否存在数据。

如果不存在数据 ,并且数据并不合法,当前线程将会堵塞等待。当前线程将无法进行下一步传输,进行排队现象。降低系统性能。

为了解决这一步问题,调用资源开辟多个线程传输。

虽然线程的开辟解决了部分堵塞排队的问题,但由于并没有治理根本堵塞的原因,线程数量也是有限的。总会有堵塞的线程 ,形成排队现象。

为了根本解决堵塞的问题。NIO的非堵塞式成为了主要的传输方式。

在客户端和服务端之间将通道注册到selector选择器,由选择器进行监听channel是否进行什么操作(read()or write())。

当数据就绪或者准备完成时,由selector进行分配到服务端的一个(或多个)线程上进行相关运行操作。

在IO的堵塞后无脑调用线程下。NIO是在准备完成时,才被selector选择分配到一个或者多个线程上传输并被复制到内核地址空间中,由于数据已准备完成或者已就绪,内核就无须被堵塞。

二.Selector(选择器)

也称多路复用器,多条channel复用selector。channe通过注册到selector ,使selector对channel进行监听,

  实现尽可能少的线程管理多个连接。减少了 线程的使用,降低了因为线程的切换引起的不必要额资源浪费和多余的开销。

  也是网络传输非堵塞的核心组件。

三.Selector的使用

分为客户端和服务端两部分:

先实现客户端吧:

  流程: 获取通道绑定主机端口 --> 切换非堵塞状态  --> 开辟buffer容量  -->  将当前时间作为数据写入buffer待传  --> 切换读写方式flip()  --> 写入通道 -->清空并关闭

  /*
* 客户端发送数据 通过channel通道
* */
@Test
public void Client() throws IOException { //获取channel通道 并设置主机号和端口号
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8080)); //因为使用非阻塞NIO 所以必须切换为非阻塞
socketChannel.configureBlocking(false); //默认为true 需要改为非堵塞的 //开辟缓冲区进行存储数据
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //准备工作就绪后,准备发送数据给服务端
//打印当前日期转为Byte数据传出
byteBuffer.put(new Date().toString().getBytes());
//切换读写模式
byteBuffer.flip();
//写入通道
socketChannel.write(byteBuffer);
//完毕时,清除缓冲区内容
byteBuffer.clear(); //====================
//关闭相关流
socketChannel.close(); }

在获取当前时间是用的new Date();还可以使用java8的获取时间的方法。

LocalDateTime.now().toString().getBytes()  //转为Byte字节

因为是网络传输的心形式,所以在获取channel时,使用SocketChannel.open方法。实现方法:

   public static SocketChannel open(SocketAddress remote)
throws IOException
{
SocketChannel sc = open();
try {
sc.connect(remote); //打开一个新的channel时,绑定连接到主机和端口上
} catch (Throwable x) {
try {
sc.close(); //异常时关闭连接
} catch (Throwable suppressed) {
x.addSuppressed(suppressed);
}
throw x;
}
assert sc.isConnected();
return sc;
}

new InetSocketAddress实例创建主机和端口。

   */
public InetSocketAddress(String hostname, int port) {
checkHost(hostname); //检查主机号是否为空 为空返回异常。
InetAddress addr = null;
String host = null;
try {
addr = InetAddress.getByName(hostname);
} catch(UnknownHostException e) {
host = hostname;
}
holder = new InetSocketAddressHolder(host, addr, checkPort(port)); //检查端口。
} //检查端口方法  
private static int checkPort(int port) {
if (port < 0 || port > 0xFFFF)
throw new IllegalArgumentException("port out of range:" + port);
return port;
} //检查主机号方法
private static String checkHost(String hostname) {
if (hostname == null)
throw new IllegalArgumentException("hostname can't be null");
return hostname;
}
 

服务端:

  流程:使用ServerSocketChannel 的方法获取服务端额channel  --> 切换为堵塞状态 --> 为buffer分配容量 --> 绑定端口号 --> 获取selector选择器 --> channel注册进选择器中,并进行监听 -->  选择器进行轮询,进行下一步读写操作。

  /*
* 服务端接收客户端传来的数据
* */
@Test
public void server() throws IOException { //获取channel通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//切换为非堵塞状态
serverSocketChannel.configureBlocking(false);
//分配服务端的缓冲区
ByteBuffer serverByteBuffer = ByteBuffer.allocate(1024);
//将客户端的InetSocketAddress绑定到通道,不绑定 不统一将获取不到数据
serverSocketChannel.bind(new InetSocketAddress(8080));
//获取选择器
Selector selector = Selector.open();
//将通道注册到选择器中,并且制定监听方式
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//进行轮询选择器上就绪成功的事件 当存在就绪成功的及进行下一步
while (selector.select() > 0){
//对已存在的就绪事件进行迭代
Iterator<SelectionKey> selectionKeyIterator = selector.selectedKeys().iterator(); //有元素就进行下一步
while (selectionKeyIterator.hasNext()){
//获取到就绪事件
SelectionKey next = selectionKeyIterator.next(); //对获取到的就绪事件判断是何种类型
if (next.isAcceptable()){ //获取连接
SocketChannel accept = serverSocketChannel.accept(); //将获取到的连接切换为非堵塞模式
accept.configureBlocking(false); //将获取到的链接 注册金selector
accept.register(selector,SelectionKey.OP_READ); //判断是否准备好读
}else if (next.isReadable()){ //获取已就绪的通道
SocketChannel channel = (SocketChannel) next.channel(); //分配缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //读取数据
int length = 0 ;
while ((length = channel.read(byteBuffer)) > 0){
byteBuffer.flip();
System.out.println(new String(byteBuffer.array(),0,length));
byteBuffer.clear();
} } //完成传输需要取消选择键,防止下次出问题
selectionKeyIterator.remove(); }
} }

如何获取选择器?

Selector selector = Selector.open();

实现过程:

 public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
} //首先进入此方法判断是否存在选择器
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null) //第一次为false
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
} //false时 跳入如下方法。
public static ServerSocketChannel open() throws IOException {
return SelectorProvider.provider().openServerSocketChannel();
}
 

随后将获取到的通道注册到获取到的选择器中,在注册时给定监听方式:

 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);  //可多选监听操作项

selectionKey中定义了四个可操作项:

  • OP_READ  可读就绪

  • OP_WRITE  可写就绪

  • OP_CONNECT  连接就绪

  • OP_ACCEPT  接收就绪

迭代key中已就绪的元素。

Iterator<SelectionKey> selectionKeyIterator = selector.selectedKeys().iterator();

获取到当前就绪事件丛迭代器中获取。

selectionKeyIterator.next()

selectionKey包含四个方法:

  • isReadable():测试此选择键是否可读

  • isWritable():测试此选择键是否可写

  • isConnectable():测试此选择键是否完成

  • isAcceptable():测试此选择键是否可以接受一个新的连接

通过这些相应的方法,单独判断是否可以读写,和进行操作。

最后取消选择键,防止下次获取出现异常情况。(第一次判断可能会为true)

selectionKeyIterator.remove();

四.附加

在上面的例子中,把客户端的代码进行稍微改写一下,使之能够无限输入,并通过传输打印在服务端中。

public static void main(String[] args) throws IOException {
//获取channel通道 并设置主机号和端口号
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8080)); //因为使用非阻塞NIO 所以必须切换为非阻塞
socketChannel.configureBlocking(false); //开辟缓冲区进行存储数据
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //附加输入:
Scanner scanner = new Scanner(System.in);
//通过控制台键入数据
while (scanner.hasNext()){
String str = scanner.next();
//准备工作就绪后,准备发送数据给服务端
//打印当前日期转为Byte数据传出
byteBuffer.put((new Date().toString()+":--->"+str).getBytes());
//切换读写模式
byteBuffer.flip();
//写入通道
socketChannel.write(byteBuffer);
//完毕时,清除缓冲区内容
byteBuffer.clear();
}
}

由于扫描流(scanner)不能用于测试类,所以在main方法下进行测试:

每次输入的内容都会被转为Byte字节进行传输。

客户端输入结果:

服务端输出结果:

每输入一次便传输一次。

//完成传输需要取消选择键,防止下次出问题
selectionKeyIterator.remove();

NIO(三):Selector选择器的更多相关文章

  1. NIO组件 Selector(选择器)

    简介 使用Selector(选择器), 可以使用一个线程处理多个客户端连接. Selector 能够检测多个注册的通道上是否有事件发生(多个Channel以事件的方式可以注册到同一个Selector) ...

  2. Java NIO之Selector(选择器)

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

  3. 小白学 Python 爬虫(35):爬虫框架 Scrapy 入门基础(三) Selector 选择器

    人生苦短,我用 Python 前文传送门: 小白学 Python 爬虫(1):开篇 小白学 Python 爬虫(2):前置准备(一)基本类库的安装 小白学 Python 爬虫(3):前置准备(二)Li ...

  4. NIO三大组件之Selector选择器

    什么是选择器 选择器的作用是完成IO的多路复用.一个通道代表一条连接通路,通过选择器可以同时监控多个通道的IO(输入输出)状况.选择器和通道的关系,是监控和被监控的关系. 使用 重要的成员 Selec ...

  5. NIO的Selector

    参考自 Java NIO系列教程(六) Selector Java-NIO-Selector java.nio.channels.Selector NIO新功能Top 10(下) 出发点: 如何管理多 ...

  6. Nio使用Selector客户端与服务器的通信

    使用NIO的一个最大优势就是客户端于服务器自己的不再是阻塞式的,也就意味着服务器无需通过为每个客户端的链接而开启一个线程.而是通过一个叫Selector的轮循器来不断的检测那个Channel有消息处理 ...

  7. 5.NIO_ Selector选择器

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

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

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

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

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

随机推荐

  1. 使用eval将字符串转化成字典时报name 'null' is not defined错误解决办法

    在接口测试过程中,为了取值将形如字典形式的字符串使用eval()方法转化成字典方便取值 str={"code":100,"num":1,"data&q ...

  2. python 并发专题(九):基础部分补充(一)进程

    概念 串行:所有的任务一个一个的完成. 并发:一个cpu完成多个任务.看起来像是同时完成. 并行:多个cpu执行多个任务,真正的同时完成. 阻塞:cpu遇到IO就是阻塞. 非阻塞:没有IO,就叫非阻塞 ...

  3. hihoCoder 1114 小Hi小Ho的惊天大作战:扫雷·一 最详细的解题报告

    题目来源:小Hi小Ho的惊天大作战:扫雷·一 解题思路:因为只要确定了第一个是否有地雷就可以推算出后面是否有地雷(要么为0,要么为1,如果不是这两个值就说明这个方案行不通),如果两种可能中有一种成功, ...

  4. Angular 懒加载找不到模块问题解决方法

    问题: 懒加载无法找到模块 解决办法: 在app-routing.module.ts中引入该模块

  5. 基于python的自动化测试框架搭建

    滴~ 今日打卡!   好多天没来打卡了.博主最近一直在把碎片化知识转化为知识体系的过程中挣扎.Python语言.selenium.unittest框架.HTMLTestRunner框架都有所了解,也写 ...

  6. Cyber Security - Palo Alto Firewall Objects Addresses, Services, and Groups(3)

    LDAP Authentication and Remote Users and Groups Create Remote User Objects and LDAP Integration: sam ...

  7. QQ音乐Android客户端Web页面通用性能优化实践

    QQ音乐 Android 客户端的 Web 页面日均 PV 达到千万量级,然而页面的打开耗时与 Native 页面相距甚远,需要系统性优化.本文将介绍 QQ 音乐 Android 客户端在进行 Web ...

  8. 【软件安装】CentOS7安装MariaDb(mysql_替代品安装)

    1.背景 Maria Db是流行的跨平台MySQL数据库管理系统的分支,被认为是MySQL 的完全替代品.Maria Db是由Sun在Sun Micro systems合并期间被Oracle收购后,于 ...

  9. 关于ES6的let和const

    变量 var存在的问题 可以重复声明 无法限制修改 没有块级作用域 (在全局范围内有效) 存在变量提升 const/let 不可以重复声明 let a = 1; let a = 2; var b = ...

  10. npm\cnpm\yarn\tyarn 关于源和代理的问题

    npm 是一个包管理器.Node.js 自带. cnpm 是 npm 的阿里版,用的阿里源. yarn 是另一个包管理器,不自带,需要另外装.可以单独装,也可以用 npm 装. tyarn 是 yar ...