windows下的非阻塞设置
在NIO里面,我们设置一般都会用非阻塞,也就是这样设置erverSocketChannel.configureBlocking(false);
.,具体他做了什么呢,我们一起来看看,最终可以跟到一个JNI
的本地方法:
public static native void configureBlocking(FileDescriptor fd,
boolean blocking)
throws IOException;
我们可以在JDK的源码里找到IOUtil.c
,里面的方法就根据传入的参数blocking
设置了相应的值:
主要是这个函数ioctlsocket
,具体可以看这篇文章。
windows下的select
最后跟进去是也是个JNI
的本地方法,还不是真正的windows
下的select
:
private native int poll0(long pollAddress, int numfds,
int[] readFds, int[] writeFds, int[] exceptFds, long timeout);
pollAddress表示文件描述符数组的首地址
numfds表示有多少个文件描述符要监听
readFds表示监听返回的读事件的文件描述符数组
writeFds表示监听返回的写事件的文件描述符数组
exceptFds表示监听返回的异常事件的文件描述符数组
timeout表示超时时间
这个方法跟我上一篇讲Linux
的select
差不多,也是把要监听的读写异常文件描述符数组传给操作系统调用,然后操作系统再去进行设置,最后返回有事件的个数,然后在从这些数组里面轮询判断到底是哪个文件描述符的哪个事件。底层调用的是WindowsSelectorImpl.c
的Java_sun_nio_ch_WindowsSelectorImpl_00024SubSelector_poll0
方法:
#define FD_SETSIZE 1024
typedef struct {
jint fd;//文件描述符
jshort events;//监听事件
} pollfd;
typedef struct {
u_int fd_count; // 多少个文件描述符
SOCKET fd_array[FD_SETSIZE]; // 文件描述符数组
} FD_SET;
JNIEXPORT jint JNICALL
Java_sun_nio_ch_WindowsSelectorImpl_00024SubSelector_poll0(JNIEnv *env, jobject this,
jlong pollAddress, jint numfds,
jintArray returnReadFds, jintArray returnWriteFds,
jintArray returnExceptFds, jlong timeout)
{
DWORD result = 0;
pollfd *fds = (pollfd *) pollAddress;//文件描述符数组的的首地址
int i;
FD_SET readfds, writefds, exceptfds;//三种事件的集合
struct timeval timevalue, *tv;
static struct timeval zerotime = {0, 0};
int read_count = 0, write_count = 0, except_count = 0;
#ifdef _WIN64
int resultbuf[FD_SETSIZE + 1];//结果数组1025个,第一个是数量,作为后面数据的缓存
#endif
//超时处理
if (timeout == 0) {
tv = &zerotime;
} else if (timeout < 0) {
tv = NULL;
} else {
jlong sec = timeout / 1000;
tv = &timevalue;
//
// struct timeval members are signed 32-bit integers so the
// signed 64-bit jlong needs to be clamped
//
if (sec > INT_MAX) {
tv->tv_sec = INT_MAX;
tv->tv_usec = 0;
} else {
tv->tv_sec = (long)sec;
tv->tv_usec = (long)((timeout % 1000) * 1000);
}
}
/* Set FD_SET structures required for select */
for (i = 0; i < numfds; i++) {
if (fds[i].events & POLLIN) {//设置读事件
readfds.fd_array[read_count] = fds[i].fd;
read_count++;
}
if (fds[i].events & (POLLOUT | POLLCONN))//设置写事件
{
writefds.fd_array[write_count] = fds[i].fd;
write_count++;
}
exceptfds.fd_array[except_count] = fds[i].fd;//剩下的设置异常事件
except_count++;
}
readfds.fd_count = read_count;
writefds.fd_count = write_count;
exceptfds.fd_count = except_count;
/* Call select */ //如果出错的话
if ((result = select(0 , &readfds, &writefds, &exceptfds, tv))
== SOCKET_ERROR) {
/* Bad error - this should not happen frequently */
/* Iterate over sockets and call select() on each separately */
FD_SET errreadfds, errwritefds, errexceptfds;
readfds.fd_count = 0;
writefds.fd_count = 0;
exceptfds.fd_count = 0;
for (i = 0; i < numfds; i++) {
/* prepare select structures for the i-th socket */
errreadfds.fd_count = 0;
errwritefds.fd_count = 0;
if (fds[i].events & POLLIN) {
errreadfds.fd_array[0] = fds[i].fd;
errreadfds.fd_count = 1;
}
if (fds[i].events & (POLLOUT | POLLCONN))
{
errwritefds.fd_array[0] = fds[i].fd;
errwritefds.fd_count = 1;
}
errexceptfds.fd_array[0] = fds[i].fd;
errexceptfds.fd_count = 1;
/* call select on the i-th socket 每一个都会去执行select*/
if (select(0, &errreadfds, &errwritefds, &errexceptfds, &zerotime)
== SOCKET_ERROR) {
/* This socket causes an error. Add it to exceptfds set */
exceptfds.fd_array[exceptfds.fd_count] = fds[i].fd;
exceptfds.fd_count++;
} else {//处理没有报错
/* This socket does not cause an error. Process result */
if (errreadfds.fd_count == 1) {
readfds.fd_array[readfds.fd_count] = fds[i].fd;
readfds.fd_count++;
}
if (errwritefds.fd_count == 1) {
writefds.fd_array[writefds.fd_count] = fds[i].fd;
writefds.fd_count++;
}
if (errexceptfds.fd_count == 1) {
exceptfds.fd_array[exceptfds.fd_count] = fds[i].fd;
exceptfds.fd_count++;
}
}
}
}
/* Return selected sockets. */
/* Each Java array consists of sockets count followed by sockets list */
//设置到返回的数组中
#ifdef _WIN64
resultbuf[0] = readfds.fd_count;//64位的第一个是放数量
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);
resultbuf[0] = writefds.fd_count;
for (i = 0; i < (int)writefds.fd_count; i++) {
resultbuf[i + 1] = (int)writefds.fd_array[i];
}
(*env)->SetIntArrayRegion(env, returnWriteFds, 0,
writefds.fd_count + 1, resultbuf);
resultbuf[0] = exceptfds.fd_count;
for (i = 0; i < (int)exceptfds.fd_count; i++) {
resultbuf[i + 1] = (int)exceptfds.fd_array[i];
}
(*env)->SetIntArrayRegion(env, returnExceptFds, 0,
exceptfds.fd_count + 1, resultbuf);
#else
(*env)->SetIntArrayRegion(env, returnReadFds, 0,
readfds.fd_count + 1, (jint *)&readfds);
(*env)->SetIntArrayRegion(env, returnWriteFds, 0,
writefds.fd_count + 1, (jint *)&writefds);
(*env)->SetIntArrayRegion(env, returnExceptFds, 0,
exceptfds.fd_count + 1, (jint *)&exceptfds);
#endif
return 0;
}
真正的windows
下的select
就是select(0 , &readfds, &writefds, &exceptfds, tv)
,跟Linux
的很像吧。具体可以看看这篇文章。
上面JNI
的看起来好像一大堆,其实他做的事情跟我们前面讲的Linux
类似,只是加了JNI
的操作。首先我们在Java
中调用了poll0
,就是最重的JNI
方法:
传入的就是文件描述符的首地址,然后是数量,要返回的三个集合,超时时间
,具体的Java
代码我后面会分析,我先贴出来一部分的。
pollArrayAddress
可以理解为本地地址:
三个集合:
对应的传入本地的参数就是红框中的:
然后转换成数组首地址,声明集合:
进行集合的初始化:
后面如果有报错的话会针对每一个文件描述符去进行监听,然后设置相应的集合:
否则就直接设置返回的事件到对应的数组给Java
层,如果有宏定义_WIN64
的则第一个是总数量,后面才是文件描述符,否则即直接把集合转换成jint
类型,其实第一个也是数量,因为前面集合FD_SET
的结构体定义里有fd_count
属性:
pollAddress
上面有讲过这个,但是其实不太好理解,这个东西到底是什么,我们来看看源码,开始是在创建选择器实现类的时候会创建PollArrayWrapper
:
然后创建本地对象AllocatedNativeObject
,也就是内存是本地的,不是Java
堆的,Java
只拿到本地返回的内存地址:
最后是调用这个,会进行内存页对齐,总是页大小4K
的倍数,反正你知道最终可以操作本地内存就行:
然后向底层申请内存:
最后JNI
方法:
其实本地是调用了unsafe.cpp
的方法:
其实就是分配内存空间,然后返回内存地址。
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。
Liuux下的Epoll
如果是Linux的JDK,我们看看到EPoll.java
中的一些常量和三个核心方法:
/**
* typedef union epoll_data {
* void *ptr;
* int fd;
* __uint32_t u32;
* __uint64_t u64;
* } epoll_data_t;
*
* struct epoll_event {
* __uint32_t events;
* epoll_data_t data;
* }
*/
private static final int SIZEOF_EPOLLEVENT = eventSize();
private static final int OFFSETOF_EVENTS = eventsOffset();
private static final int OFFSETOF_FD = dataOffset();
// opcodes
static final int EPOLL_CTL_ADD = 1;
static final int EPOLL_CTL_DEL = 2;
static final int EPOLL_CTL_MOD = 3;
// events
static final int EPOLLIN = 0x1;
static final int EPOLLOUT = 0x4;
// flags
static final int EPOLLONESHOT = (1 << 30);
static native int create() throws IOException;
static native int ctl(int epfd, int opcode, int fd, int events);
static native int wait(int epfd, long pollAddress, int numfds, int timeout)
这个跟我前面一篇的讲的是对应的,所以就不多说了,我们看看JNI
里怎么实现的:
这个就更加好理解了,也不多说了。
总结
这里只是介绍了一些跟前面一篇相关的东西,了解下JNI底层跟操作系统如何打交道,具体的所有操作都是由系统调用来实现的,Java只是做了个封装,通过这两篇的讲解,能知道多路复用到底是怎么回事,事件又是怎么传到Java层的,为什么大多情况下select
没有epoll
性能好,当然其实讲的也比较浅,真的要弄明白底层,可能要深入Linux
内核源码啦,有兴趣的可以研究下。
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。