java nio 在windows的实现

0. 结论

先说结论吧。

  • jdk8和以前,java nio的windows实现,在底层是基于winsock2的select
  • 但是winsock2的select是否是基于轮询的,是不是我们常说的select/poll/epoll中的select,我无法查证,毕竟windows不是开源的。如果是轮询,那效率是相当低的。所以说windows就这点不好>_<。
  • 一次select可返回的最大数量是1024。

1. 在windows上的实现

参考java nio 在windows上的实现 很多人说是IOCP,其实是select。 首先我们一步步查看调用链: Selector.select->...->WindowsSelectorImpl.doSelect->WindowsSelectorImpl.SubSelector.poll->WindowsSelectorImpl.SubSelector.poll0 我们看下WindowsSelectorImpl.SubSelector.poll:

private int poll() throws IOException {
  return this.poll0(WindowsSelectorImpl.this.pollWrapper.pollArrayAddress, Math.min(WindowsSelectorImpl.this.totalChannels, 1024), this.readFds, this.writeFds, this.exceptFds, WindowsSelectorImpl.this.timeout);
}

然后再看最后调用的WindowsSelectorImpl.SubSelector.poll0:

private native int poll0(long var1, int var3, int[] var4, int[] var5, int[] var6, long var7);

该函数是一个native方法,其源码需要到jdk源码中查看。但jdk不是开源的,所以native方法实现只能在openJdk中找。

参考OpenJDK与JDK的区别分析,openJdk只是在jdk上加了少许不痛不痒的功能而已,关于WindowsSelectorImpl的实现应该是一样的。

接下来我们找到openJdk的源码,查看winsock2$poll0

2. WindowsSelectorImpl

我们找到openJdk8的源码jdk8 WindowsSelectorImpl.c.shtml

2.1 调用winsock2$select

参考:

在WindowsSelectorImpl$poll0函数中我们看到:

if ((result = select(0 , &readfds, &writefds, &exceptfds, tv))
                                                             == SOCKET_ERROR)

所以调用的是winsock2的select方法,阻塞监听触发read/write事件的fd(这里的fd就是指socket)。激活读写事件的socket会保存在readfds和writefds中。 所以:

  • jdk8和以前,java nio的windows实现,在底层是基于winsock2的select
  • 但是winsock2的select是否是基于轮询的,是不是我们常说的select/poll/epoll中的select,我无法查证,毕竟windows不是开源的。如果是轮询,那效率是相当低的。所以说windows就这点不好>_<。

2.2 设置上层调用的返回值

之后有如下代码:

resultbuf[0] = readfds.fd_count;
for (i = 0; i < (int)readfds.fd_count; i++) {
   resultbuf[i + 1] = (int)readfds.fd_array[i];
}
(*env)->SetIntArrayRegion(env, returnReadFds, 0, readfds.fd_count + 1, resultbuf);

该段代码的作用,是把激活读事件的socket拷贝至returnReadFds中,作为上层调用的返回值。 附近格式类似的代码功能也一样,都是把激活read/write事件的socket拷贝到returnReadFds/returnWriteFds中,作为上层调用的返回值。如果有异常发生,则returnExceptFds也会被正确地被赋值。

2.3 selector的最大监听数

根据上一节,我们发现在poll0调用中,会传入参数this.readFds, this.writeFds, this.exceptFds,以便poll0设置返回值:

private int poll() throws IOException {
  return this.poll0(WindowsSelectorImpl.this.pollWrapper.pollArrayAddress, Math.min(WindowsSelectorImpl.this.totalChannels, 1024), this.readFds, this.writeFds, this.exceptFds, WindowsSelectorImpl.this.timeout);
}

其中this.readFds是成员函数,我们看到定义:

 private final class SubSelector {
        private final int pollArrayIndex;
        private final int[] readFds;
        private final int[] writeFds;
        private final int[] exceptFds;

以及其构造函数:

可以发现,readFds、writeFds、exceptFds都被赋予了固定长度的数组, 而且这个变量并没有再变换过。 readFds、writeFds、exceptFds的作用是接收触发相应事件的socket。 所以我认为,在windows中,selector一次监听返回的最大激活数目不能超过1024。 不过在winsock2.h中,我看到:

typedef struct fd_set {
        u_int fd_count;               /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;

其中fd_set是returnReadFds、returnWriteFds的类型,也就是说fd_set是返回值参数的类型。 然后FD_SETSIZE的长度:

 #ifndef FD_SETSIZE
 #define  FD_SETSIZE      64 
 #endif  /* FD_SETSIZE */

意思非常明显,也就是说winsock默认最多处理64个sock。也可以通过#define修改FD_SETSIZE为想要的值。 我们回看jdk8的WindowsSelectorImpl.c,在开头就有一句:

#define FD_SETSIZE 1024

说明openJdk将这个限制改成了1024,与上文的数值一致。最后,我们再一次确认了这个限制是1024。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券