接下来看看Selector创建过程,上面调用了EPollSelectorProvider#openSelector来开始初始化selector
public AbstractSelector openSelector() throws IOException { // 直接new 一个EPollSelectorImpl return new EPollSelectorImpl(this); } // 该构造方法只能是包内使用,供provider来调用 EPollSelectorImpl(SelectorProvider sp) throws IOException { // 调用父类SelectorImpl的构造方法初始化selectedKeys、publicKeys、publicSelectedKeys // 上面已经说过了,如果使用Netty的优化,publicKeys、publicSelectedKey会被替换 super(sp); // 调用linux的pipe方法,创建一个管道,配置为非阻塞的 long pipeFds = IOUtil.makePipe(false); // 高32为读文件描述符 fd0 = (int) (pipeFds >>> 32); // 低32位为写文件描述符 fd1 = (int) pipeFds; // EPollArrayWrapper包含一系列native方法来调用EPollArrayWrapper.c本地方法 pollWrapper = new EPollArrayWrapper(); pollWrapper.initInterrupt(fd0, fd1); // fdToKey用来保存文件描述符和SelectionKeyImpl的映射 fdToKey = new HashMap<>(); } EPollArrayWrapper() throws IOException { // creates the epoll file descriptor // 创建epoll的文件描述符 epfd = epollCreate(); // the epoll_event array passed to epoll_wait int allocationSize = NUM_EPOLLEVENTS * SIZE_EPOLLEVENT; pollArray = new AllocatedNativeObject(allocationSize, true); pollArrayAddress = pollArray.address(); // eventHigh needed when using file descriptors > 64k if (OPEN_MAX > MAX_UPDATE_ARRAY_SIZE) eventsHigh = new HashMap<>(); }终于看到创建epoll文件描述符相关代码了,上面这个还是看不到究竟调用了哪些本地方法,我们看看相关的c代码
// jdk/src/solaris/native/sun/nio/ch/IOUtil.c JNIEXPORT jlong JNICALL Java_sun_nio_ch_IOUtil_makePipe(JNIEnv *env, jobject this, jboolean blocking) { int fd[2]; // 打开pipe if (pipe(fd) < 0) { JNU_ThrowIOExceptionWithLastError(env, "Pipe failed"); return 0; } if (blocking == JNI_FALSE) { // 配置管道为非阻塞 if ((configureBlocking(fd[0], JNI_FALSE) < 0) || (configureBlocking(fd[1], JNI_FALSE) < 0)) { JNU_ThrowIOExceptionWithLastError(env, "Configure blocking failed"); close(fd[0]); close(fd[1]); return 0; } } // 将读写文件描述符放入一个long型中返回 return ((jlong) fd[0] << 32) | (jlong) fd[1]; } // jdk/src/solaris/native/sun/nio/ch/EPollArrayWrapper.c JNIEXPORT jint JNICALL Java_sun_nio_ch_EPollArrayWrapper_epollCreate(JNIEnv *env, jobject this) { /* * epoll_create expects a size as a hint to the kernel about how to * dimension internal structures. We can't predict the size in advance. */ // 这里调用linux函数epoll_create创建epoll的文件描述符 int epfd = epoll_create(256); if (epfd < 0) { JNU_ThrowIOExceptionWithLastError(env, "epoll_create failed"); } return epfd; } 总结经过上面说明,现在对于Netty启动过程中线程池的初始化过程和selector初始化过程已经比较清晰了,对于native方法的分析让我们对比linux中epoll编程,对于原理更加清楚。
接下来就是将需要监听的描述符注册到epoll上,对应到Netty就是讲channel注册到selector上,下一篇文章继续写Netty源码—二、server启动(2)
参考
Netty源码分析——Reactor的processSelectedKeys
关于SelectedSelectionKeySet优化的讨论
https://github.com/netty/netty/issues/2363
https://github.com/netty/netty/commit/cd579f75d2b5f236f35bc47f454cc07e50ae8037