经过前边几节的介绍,我相信大家都掌握了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();}}
}

这个例子的关键点:

  1. 创建一个ServerSocketChannel,和一个Selector,并且把这个server channel 注册到 selector上,注册的时间指定,这个channel 所感觉兴趣的事件是 SelectionKey.OP_ACCEPT,这个事件代表的是有客户端发起TCP连接请求。
  2. 使用 select 方法阻塞住线程,当select 返回的时候,线程被唤醒。再通过selectedKeys方法得到所有可用channel的集合。
  3. 遍历这个集合,如果其中channel 上有连接到达,就接受新的连接,然后把这个新的连接也注册到selector中去。
  4. 如果有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相关推荐

  1. 【NIO】Selector(选择器)

    前言 Github:GitHub - yihonglei/jdk-source-code-reading: JDK source code reading(java-nio) 一 Selector ( ...

  2. 【NIO】异步模型之Callback -- 封装NIO

    在[NIO]IO模型,这节课中,我们提到了5种IO模型.第四种,SIGIO一般都是在进程间使用信号通讯的时候的手段,在Java中不是很适用,我就不深入去讲了.第五种,linux 服务器上的典型代表是 ...

  3. 【NIO】通道Channel

    通道式(Channel)是java.nio的第二个主要创新.通道既不是一个扩展也不是一项增强,而是全新的.极好的Java I/O示例,提供与I/O服务的直接连接.Channel用于在字节缓冲区和位于通 ...

  4. 【NIO】IO多路复用

    上节,我们讲解了阻塞和非阻塞,我们今天讲第三种IO模型,这就是IO多路复用. 引入多路复用的原因 实际上,在服务端与客户端一对一通信的时候,同步阻塞式地读写并不会有太大的问题,最典型的就是两个对等机器 ...

  5. 【NIO】Socket 编程:基于NIO的Server、Client 示例

    1.Server import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; ...

  6. 【java nio】Selector 原理解析

    这篇文章主要介绍NIO三大组件之一selector,看看selector是如何管理多个 channel,获取这些 channel 上发生的事件的 在使用nio编程时通常使用如下几句代码让selecto ...

  7. 【NIO】解读 java.nio.channels.Selector

    目录 Part 1. What's Selector? Part 2. Why Selector? Part 3. How to use Selector? 1) Create Selector 2) ...

  8. 【Java.NIO】Selector,及SelectionKey

    https://blog.csdn.net/robinjwong/article/details/41792623 java.nio.channels public abstract class Se ...

  9. 【NIO】缓存区buffer

    io中包含了很多东西,我个人认为最核心的是selector,那里我会使用大量的篇幅去介绍.但在那之前,我们还是从最简单的东西入手.今天只讲一下buffer. 在没有使用nio之前,我们只能自己维护一个 ...

最新文章

  1. 互联网协议 — 使用 Wireshark 调试 HTTPS 及 HTTP/2 流量
  2. mysql的聚合查询_MySql聚合查询
  3. deltasql 1.6.0 发布,数据库模型版本控制
  4. 2019年寒假作业1编辑总结
  5. java实现js取反_特定位取反(js实现)
  6. CSS3选择器:nth-child和:nth-of-type之间的差异
  7. 基于JAVA+Servlet+JSP+MYSQL的网上书城
  8. ActiveMQ 使用文档
  9. python可以给你干什么-你最想用python做什么?python到底能用来做什么?
  10. JavaScript 的 async/await 理解(4)
  11. 网络流24题-骑士共存问题
  12. Weka中数据挖掘与机器学习系列之Weka系统安装(四)
  13. eclipse 主题设置
  14. 联想软件商店安装教程
  15. html验证码 按住向右滑动,js实现滑动滑块验证登录
  16. 攒机笔记二十二:台式组装机(2022.9)
  17. AutoCAD 2021 安装 详细教程
  18. 管理经济学 知识点总结(一)
  19. mysql创建表里主码和外码_外码必须是另一个关系的主码吗?主键主码 外键外码是同一个东西吗?...
  20. Google Map中的瓦片

热门文章

  1. linux进程和程序的却别,操作系统:进程的概念和与程序的区别
  2. linux看测试环境版本,最新版 EOS 在 Ubuntu 18.04.1 LTS 环境上的安装测试教程
  3. java message bus_【Microsoft Azure学习之旅】消息服务Service Bus的学习笔记及Demo示例...
  4. 揭晓你所不了解的第三代测序技术
  5. A networkIntegration Approach for Drug-Target interaction Prediction and ComputationalDrug Repositio
  6. Badread: simulation of error-prone long reads
  7. Bi-level error correction for PacBio long reads
  8. 基于社交媒体的政治情感分析的相关论文
  9. TensorFlow基础12-(keras.Sequential模型以及使用Sequential模型 实现手写数字识别)
  10. c语言easy,C语言easy….doc