【NIO】Selector
经过前边几节的介绍,我相信大家都掌握了IO多路复用的核心思想了。昨天我给了一个使用C语言进行 linux 编程的IO多路复用的例子。那个例子使用的是 poll 这个系统调用,今天我会给出一个使用Java实现与之完全对等的例子。
Selector
在Java中,Selector这个类是select/epoll/poll的外包类,在不同的平台上,底层的实现可能有所不同,但其基本原理是一样的,其原理图如下所示:
所有的Channel都归Selector管理,这些channel中只要有至少一个有IO动作,就可以通过Selector.select方法检测到,并且使用selectedKeys得到这些有IO的channel,然后对它们调用相应的IO操作。
我这里有一个服务端的例子,这个例子与昨天使用 poll 的 C 语言的例子是完全对应的:
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.Iterator;
import java.util.Set;public class EpollServer {public static void main(String[] args) {try {ServerSocketChannel ssc = ServerSocketChannel.open();ssc.socket().bind(new InetSocketAddress("127.0.0.1", 8000));ssc.configureBlocking(false);Selector selector = Selector.open();// 注册 channel,并且指定感兴趣的事件是 Acceptssc.register(selector, SelectionKey.OP_ACCEPT);ByteBuffer readBuff = ByteBuffer.allocate(1024);ByteBuffer writeBuff = ByteBuffer.allocate(128);writeBuff.put("received".getBytes());writeBuff.flip();while (true) {int nReady = selector.select();Set<SelectionKey> keys = selector.selectedKeys();Iterator<SelectionKey> it = keys.iterator();while (it.hasNext()) {SelectionKey key = it.next();it.remove();if (key.isAcceptable()) {// 创建新的连接,并且把连接注册到selector上,而且,// 声明这个channel只对读操作感兴趣。SocketChannel socketChannel = ssc.accept();socketChannel.configureBlocking(false);socketChannel.register(selector, SelectionKey.OP_READ);}else if (key.isReadable()) {SocketChannel socketChannel = (SocketChannel) key.channel();readBuff.clear();socketChannel.read(readBuff);readBuff.flip();System.out.println("received : " + new String(readBuff.array()));key.interestOps(SelectionKey.OP_WRITE);}else if (key.isWritable()) {writeBuff.rewind();SocketChannel socketChannel = (SocketChannel) key.channel();socketChannel.write(writeBuff);key.interestOps(SelectionKey.OP_READ);}}}} catch (IOException e) {e.printStackTrace();}}
}
这个例子的关键点:
- 创建一个ServerSocketChannel,和一个Selector,并且把这个server channel 注册到 selector上,注册的时间指定,这个channel 所感觉兴趣的事件是 SelectionKey.OP_ACCEPT,这个事件代表的是有客户端发起TCP连接请求。
- 使用 select 方法阻塞住线程,当select 返回的时候,线程被唤醒。再通过selectedKeys方法得到所有可用channel的集合。
- 遍历这个集合,如果其中channel 上有连接到达,就接受新的连接,然后把这个新的连接也注册到selector中去。
- 如果有channel是读,那就把数据读出来,并且把它感兴趣的事件改成写。如果是写,就把数据写出去,并且把感兴趣的事件改成读。
这个程序的核心骨架就这么点,很简单。
为了测试这个服务端,我提供了一个客户端的小例子:
public class EpollClient {public static void main(String[] args) {try {SocketChannel socketChannel = SocketChannel.open();socketChannel.connect(new InetSocketAddress("127.0.0.1", 8000));ByteBuffer writeBuffer = ByteBuffer.allocate(32);ByteBuffer readBuffer = ByteBuffer.allocate(32);writeBuffer.put("hello".getBytes());writeBuffer.flip();while (true) {writeBuffer.rewind();socketChannel.write(writeBuffer);readBuffer.clear();socketChannel.read(readBuffer);}} catch (IOException e) {}}
}
这个客户端不断地向服务端发送"hello",并且从服务端接收"received"。
大家多启几个客户端程序,去连接同一个服务端,观察一下现象,动手改一下客户端代码,让它发送的更丰富一点,不要让每一个客户端发的都一样。
原理和实现
我们来分析一下,具体的实现,先从创建入手,看一下Selector.open的实现
public static Selector open() throws IOException {return SelectorProvider.provider().openSelector();}
这个Provider在不同的机器上会有不同的默认实现,例如在linux平台上,就是这样的:
代码位于jdk/src/solaris/classes/sun/nio/ch/DefaultSelectorProvider.java
public static SelectorProvider create() {String osname = AccessController.doPrivileged(new GetPropertyAction("os.name"));if (osname.equals("SunOS"))return createProvider("sun.nio.ch.DevPollSelectorProvider");if (osname.equals("Linux"))return createProvider("sun.nio.ch.EPollSelectorProvider");return new sun.nio.ch.PollSelectorProvider();}
可以看到,如果是高版本的Linux,就会使用EPollSelectorProvider,而默认则使用PollSelectorProvider, epoll相关的内容,我们下节课介绍,今天先不管它,继续看poll的实现。
public AbstractSelector openSelector() throws IOException {return new PollSelectorImpl(this);}
OK,找到这个地方了。至于register的过程,我就不分析了。我今天只分析一下select 的过程。通过查看select的实现,我们最终可以找到这个位置:
solaris/classes/sun/nio/ch/PollSelectorImpl.java中的doSelect
protected int doSelect(long timeout)throws IOException{ if (channelArray == null)throw new ClosedSelectorException();processDeregisterQueue();try {begin();pollWrapper.poll(totalChannels, 0, timeout);} finally {end();} processDeregisterQueue();int numKeysUpdated = updateSelectedKeys();if (pollWrapper.getReventOps(0) != 0) {// Clear the wakeup pipepollWrapper.putReventOps(0, 0); synchronized (interruptLock) {IOUtil.drain(fd0);interruptTriggered = false;} } return numKeysUpdated;}
这里最重要的是pollWrapper.poll,OK,我们去看这个定义:
int poll(int numfds, int offset, long timeout) {return poll0(pollArrayAddress + (offset * SIZE_POLLFD),numfds, timeout);} private native int poll0(long pollAddress, int numfds, long timeout);
又到了native方法了。大家可以猜一下这个poll0是怎么实现的,然后我们来看答案:
JNIEXPORT jint JNICALL
Java_sun_nio_ch_PollArrayWrapper_poll0(JNIEnv *env, jobject this,jlong address, jint numfds,jlong timeout)
{struct pollfd *a;int err = 0;a = (struct pollfd *) jlong_to_ptr(address);if (timeout <= 0) { /* Indefinite or no wait */RESTARTABLE (poll(a, numfds, timeout), err);} else { /* Bounded wait; bounded restarts */err = ipoll(a, numfds, timeout);}if (err < 0) {JNU_ThrowIOExceptionWithLastError(env, "Poll failed");}return (jint)err;
}
如何,你是不是已经猜到了?绕了这么久,到最后,原来是我们的大熟人 poll 啊。到此为止,我们终于把selector前前后后的事情都搞明白了。
一个同学问我,NIO里引入channel 的是干嘛的?好像它只是对socket的一层封装,看不出来什么意义嘛。到这里,终于可以完整地回答这位同学了,channel 是与 selector 适配用的。selector 直接管理 Java Socket 很难实现,所以使用channel做一次封装与之适配。这一切都是为了 selector 而存在的。selector 是Java NIO的基石,是根本之所在。
OK,终于把NIO的核心知识讲完了,长舒一口气。
【NIO】Selector相关推荐
- 【NIO】Selector(选择器)
前言 Github:GitHub - yihonglei/jdk-source-code-reading: JDK source code reading(java-nio) 一 Selector ( ...
- 【NIO】异步模型之Callback -- 封装NIO
在[NIO]IO模型,这节课中,我们提到了5种IO模型.第四种,SIGIO一般都是在进程间使用信号通讯的时候的手段,在Java中不是很适用,我就不深入去讲了.第五种,linux 服务器上的典型代表是 ...
- 【NIO】通道Channel
通道式(Channel)是java.nio的第二个主要创新.通道既不是一个扩展也不是一项增强,而是全新的.极好的Java I/O示例,提供与I/O服务的直接连接.Channel用于在字节缓冲区和位于通 ...
- 【NIO】IO多路复用
上节,我们讲解了阻塞和非阻塞,我们今天讲第三种IO模型,这就是IO多路复用. 引入多路复用的原因 实际上,在服务端与客户端一对一通信的时候,同步阻塞式地读写并不会有太大的问题,最典型的就是两个对等机器 ...
- 【NIO】Socket 编程:基于NIO的Server、Client 示例
1.Server import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; ...
- 【java nio】Selector 原理解析
这篇文章主要介绍NIO三大组件之一selector,看看selector是如何管理多个 channel,获取这些 channel 上发生的事件的 在使用nio编程时通常使用如下几句代码让selecto ...
- 【NIO】解读 java.nio.channels.Selector
目录 Part 1. What's Selector? Part 2. Why Selector? Part 3. How to use Selector? 1) Create Selector 2) ...
- 【Java.NIO】Selector,及SelectionKey
https://blog.csdn.net/robinjwong/article/details/41792623 java.nio.channels public abstract class Se ...
- 【NIO】缓存区buffer
io中包含了很多东西,我个人认为最核心的是selector,那里我会使用大量的篇幅去介绍.但在那之前,我们还是从最简单的东西入手.今天只讲一下buffer. 在没有使用nio之前,我们只能自己维护一个 ...
最新文章
- 互联网协议 — 使用 Wireshark 调试 HTTPS 及 HTTP/2 流量
- mysql的聚合查询_MySql聚合查询
- deltasql 1.6.0 发布,数据库模型版本控制
- 2019年寒假作业1编辑总结
- java实现js取反_特定位取反(js实现)
- CSS3选择器:nth-child和:nth-of-type之间的差异
- 基于JAVA+Servlet+JSP+MYSQL的网上书城
- ActiveMQ 使用文档
- python可以给你干什么-你最想用python做什么?python到底能用来做什么?
- JavaScript 的 async/await 理解(4)
- 网络流24题-骑士共存问题
- Weka中数据挖掘与机器学习系列之Weka系统安装(四)
- eclipse 主题设置
- 联想软件商店安装教程
- html验证码 按住向右滑动,js实现滑动滑块验证登录
- 攒机笔记二十二:台式组装机(2022.9)
- AutoCAD 2021 安装 详细教程
- 管理经济学 知识点总结(一)
- mysql创建表里主码和外码_外码必须是另一个关系的主码吗?主键主码 外键外码是同一个东西吗?...
- Google Map中的瓦片
热门文章
- linux进程和程序的却别,操作系统:进程的概念和与程序的区别
- linux看测试环境版本,最新版 EOS 在 Ubuntu 18.04.1 LTS 环境上的安装测试教程
- java message bus_【Microsoft Azure学习之旅】消息服务Service Bus的学习笔记及Demo示例...
- 揭晓你所不了解的第三代测序技术
- A networkIntegration Approach for Drug-Target interaction Prediction and ComputationalDrug Repositio
- Badread: simulation of error-prone long reads
- Bi-level error correction for PacBio long reads
- 基于社交媒体的政治情感分析的相关论文
- TensorFlow基础12-(keras.Sequential模型以及使用Sequential模型 实现手写数字识别)
- c语言easy,C语言easy….doc