从JVM的角度认识Java的NIO
1. 定义
java.nio (NIO stands for non-blocking I/O[1]) is a collection of Java programming language APIs that offer features for intensive I/O operations. It was introduced with the J2SE 1.4 release of Java by Sun Microsystems to complement an existing standard I/O. NIO was developed under the Java Community Process as JSR 51.[2] An extension to NIO that offers a new file system API, called NIO.2, was released with Java SE 7 ("Dolphin").[3]
NIO 有三大核心组件: Channel(通道), Buffer(缓冲区),Selector(多路复用器)
1、channel 类似于流,每个 channel 对应一个 buffer缓冲区,buffer 底层就是个数组
2、channel 会注册到 selector 上,由 selector 根据 channel 读写事件的发生将其交由某个空闲的线程处理
3、NIO 的 Buffer 和 channel 都是既可以读也可以写
2.例子
public class NIOServer {
//public static ExecutorService pool = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws IOException {
// 创建一个在本地端口进行监听的服务Socket通道.并设置为非阻塞方式
ServerSocketChannel ssc = ServerSocketChannel.open();
//必须配置为非阻塞才能往selector上注册,否则会报错,selector模式本身就是非阻塞模式
ssc.configureBlocking(false);
ssc.socket().bind(new InetSocketAddress(9000));
// 创建一个选择器selector
Selector selector = Selector.open();
// 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
System.out.println("等待事件发生。。");
// 轮询监听channel里的key,select是阻塞的,accept()也是阻塞的
int select = selector.select();
System.out.println("有事件发生了。。");
// 有客户端请求,被轮询监听到
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
//删除本次已处理的key,防止下次select重复处理
it.remove();
handle(key);
}
}
}
private static void handle(SelectionKey key) throws IOException {
if (key.isAcceptable()) {
System.out.println("有客户端连接事件发生了。。");
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
//NIO非阻塞体现:此处accept方法是阻塞的,但是这里因为是发生了连接事件,所以这个方法会马上执行完,不会阻塞
//处理完连接请求不会继续等待客户端的数据发送
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
//通过Selector监听Channel时对读事件感兴趣
sc.register(key.selector(), SelectionKey.OP_READ);
} else if (key.isReadable()) {
System.out.println("有客户端数据可读事件发生了。。");
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
//NIO非阻塞体现:首先read方法不会阻塞,其次这种事件响应模型,当调用到read方法时肯定是发生了客户端发送数据的事件
int len = sc.read(buffer);
if (len != -1) {
System.out.println("读取到客户端发送的数据:" + new String(buffer.array(), 0, len));
}
ByteBuffer bufferToWrite = ByteBuffer.wrap("HelloClient".getBytes());
sc.write(bufferToWrite);
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
} else if (key.isWritable()) {
SocketChannel sc = (SocketChannel) key.channel();
System.out.println("write事件");
// NIO事件触发是水平触发
// 使用Java的NIO编程的时候,在没有数据可以往外写的时候要取消写事件,
// 在有数据往外写的时候再注册写事件
key.interestOps(SelectionKey.OP_READ);
//sc.close();
}
}
}
3.源码解析,深入到JVM层面
ServerSocketChannel ssc = ServerSocketChannel.open();
java.nio.channels.ServerSocketChannel#open
public static ServerSocketChannel open() throws IOException {
//看下provider()的方法,最终返回的是EPollSelectorProvider对象.
return SelectorProvider.provider().openServerSocketChannel();
}
java.nio.channels.spi.SelectorProvider#provider
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
//注意看这里,DefaultSelectorProvider
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}
我都知道java语言是跨平台的,所以这个DefaultSelectorProvider中的create()方法,在Windows平台上返回的是sun.nio.ch.WindowsSelectorProvider,在Linux平台上返回的是sun.nio.ch.EPollSelectorProvider,我们主要研究Linux平台的,因为服务一般都是这个平台的。
sun.nio.ch.DefaultSelectorProvider
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();
}
接着我们要看openServerSocketChannel()方法,其实是sun.nio.ch.SelectorProviderImpl,它是sun.nio.ch.EPollSelectorProvider的父类
sun.nio.ch.SelectorProviderImpl#openServerSocketChannel
public ServerSocketChannel openServerSocketChannel() throws IOException {
return new ServerSocketChannelImpl(this);
}
sun.nio.ch.ServerSocketChannelImpl#ServerSocketChannelImpl(java.nio.channels.spi.SelectorProvider)
ServerSocketChannelImpl(SelectorProvider var1) throws IOException {
super(var1);
//注意看这里,会返回一个Socket对象的文件描述符
this.fd = Net.serverSocket(true);
this.fdVal = IOUtil.fdVal(this.fd);
this.state = 0;
}
sun.nio.ch.Net#serverSocket
static FileDescriptor serverSocket(boolean var0) {
//socket0是个本地方法,所以我们看Hotspot的源码
return IOUtil.newFD(socket0(isIPv6Available(), var0, true, fastLoopback));
}
JNIEXPORT int JNICALL
Java_sun_nio_ch_Net_socket0(JNIEnv *env, jclass cl, jboolean preferIPv6,
jboolean stream, jboolean reuse)
{
int fd;
int type = (stream ? SOCK_STREAM : SOCK_DGRAM);
#ifdef AF_INET6
int domain = (ipv6_available() && preferIPv6) ? AF_INET6 : AF_INET;
#else
int domain = AF_INET;
#endif
//注意这行代码,这就调用到了Linux内核的代码了,int socket(int domain, int type ,int protocol)这个函数
//domain参数指定了socket的通信domain。type参数指定了socket类型,这个参数通常在创建流socket时候被指定为SOCK_STREAM
//protocol总是会被指定为0. -----《Linux-UNIX系统编程手册(上、下册)中文版》第56.2章节
fd = socket(domain, type, 0);
if (fd < 0) {
return handleSocketError(env, errno);
}
所以我们知道,Java层面的ServerSocketChannel在操作系统底层,就是个Socket对象的文件描述符
-
Selector selector = Selector.open();这个
selector变量是个EPollSelectorImpl对象,它里面包含了epoll实例,所以selector对象可以理解为操作系统的epoll对象
java.nio.channels.Selector#open
public static Selector open() throws IOException {
//provider()这个方法在上面已经讲述过了,最终返回的是EPollSelectorProvider对象.
return SelectorProvider.provider().openSelector();
}
我们来看看sun.nio.ch.EPollSelectorProvider#openSelector()方法
public AbstractSelector openSelector() throws IOException {
//返回了一个EPollSelectorImpl对象
return new EPollSelectorImpl(this);
}
sun.nio.ch.EPollSelectorImpl(SelectorProvider sp)
EPollSelectorImpl(SelectorProvider sp) throws IOException {
super(sp);
long pipeFds = IOUtil.makePipe(false);
fd0 = (int) (pipeFds >>> 32);
fd1 = (int) pipeFds;
//查看这个方法
pollWrapper = new EPollArrayWrapper();
pollWrapper.initInterrupt(fd0, fd1);
fdToKey = new HashMap<>();
}
sun.nio.ch.EPollArrayWrapper()
EPollArrayWrapper() throws IOException {
// creates the epoll file descriptor
//查看这个方法,这个是个native方法啊,所以看Hotspot的源码Java_sun_nio_ch_EPollArrayWrapper_epollCreate(JNIEnv *env, jobject this)
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<>();
}
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.
*/
//系统调用 epoll_create()创建了一个新的 epoll 实例,参数 size 指定了我们想要通过 epoll 实例来检查的文件描述符个数
//epfd作为函数返回值,epoll_create()返回了代表新创建的 epoll 实例的文件描述符
//-----《Linux-UNIX系统编程手册(上、下册)中文版》第63.4.1章节
int epfd = epoll_create(256);
if (epfd < 0) {
JNU_ThrowIOExceptionWithLastError(env, "epoll_create failed");
}
return epfd;
}
-
ssc.register(selector, SelectionKey.OP_ACCEPT);这个selector对象就是上面的EPollSelectorImpl在解析源码之前,我们来想想,上面两步,操作系统底层的
Socket对象有了,epoll对象也有了,那么是不是要把这个Socket对象绑定到epoll上了呢?
实际调用的是java.nio.channels.SelectableChannel#register(java.nio.channels.Selector, int)
会调用到java.nio.channels.spi.AbstractSelectableChannel#register
public final SelectionKey register(Selector sel, int ops,
Object att)
throws ClosedChannelException
{
synchronized (regLock) {
if (!isOpen())
throw new ClosedChannelException();
if ((ops & ~validOps()) != 0)
throw new IllegalArgumentException();
if (blocking)
throw new IllegalBlockingModeException();
SelectionKey k = findKey(sel);
if (k != null) {
k.interestOps(ops);
k.attach(att);
}
if (k == null) {
// New registration
synchronized (keyLock) {
if (!isOpen())
throw new ClosedChannelException();
//看这个方法,sel对象就是上面的EPollSelectorImpl,this就是ServerSocketChannel对象
k = ((AbstractSelector)sel).register(this, ops, att);
addKey(k);
}
}
return k;
}
}
会调用到sun.nio.ch.SelectorImpl#register(AbstractSelectableChannel var1, int var2, Object var3)方法
protected final SelectionKey register(AbstractSelectableChannel ch, int ops, Object attachment) {
//var1就是ServerSocketChannel对象
if (!(ch instanceof SelChImpl)) {
throw new IllegalSelectorException();
} else {
//这个对象里面方法了,ServerSocketChannel对象和EPollSelectorImpl对象
SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);
k.attach(attachment);
synchronized(this.publicKeys) {
//3.1 调用EPollSelectorImpl对象的implRegister方法,这里只是java层面的注册
this.implRegister(k);
}
//3.2 这里会把Socket对象的文件描述符放到EPollSelectorImpl对象的pollWrapper的updateDescriptors里面
//还会把这个Socket对象感兴趣的事件放到eventsLow这个集合中
k.interestOps(ops);
return k;
}
}
sun.nio.ch.implRegister(SelectionKeyImpl ski)
protected void implRegister(SelectionKeyImpl ski) {
if (closed)
throw new ClosedSelectorException();
//拿到ServerSocketChannel对象
SelChImpl ch = ski.channel;
//拿到ServerSocketChannel对象里面的Socket对象文件描述符
int fd = Integer.valueOf(ch.getFDVal());
//讲Socket文件描述符和这个Socket对应的Channel对象绑定在fdToKey里面
fdToKey.put(fd, ski);
//将Socket对象文件描述符添加的到EPollSelectorImpl对象的集合里面,这是放在java层面的集合里面
pollWrapper.add(fd);
keys.add(ski);
}
sun.nio.ch.SelectionKeyImpl#interestOps(int)
public SelectionKey interestOps(int ops) {
this.ensureValid();
//看这行代码
return this.nioInterestOps(ops);
}
//最后会调用到sun.nio.ch.EPollArrayWrapper#setUpdateEvents
private void setUpdateEvents(int fd, byte events, boolean force) {
if (fd < MAX_UPDATE_ARRAY_SIZE) {
if (this.eventsLow[fd] != -1 || force) {
//把这个Socket对象感兴趣的事件放到eventsLow这个集合中
this.eventsLow[fd] = events;
}
} else {
Integer key = fd;
if (!this.isEventsHighKilled(key) || force) {
this.eventsHigh.put(key, events);
}
}
}
int select = selector.select();这个selector对象就是上面的EPollSelectorImpl
sun.nio.ch.SelectorImpl#select()
public int select() throws IOException {
return this.select(0L);
}
调用到sun.nio.ch.SelectorImpl#lockAndDoSelect
private int lockAndDoSelect(long var1) throws IOException {
synchronized(this) {
if (!this.isOpen()) {
throw new ClosedSelectorException();
} else {
int var10000;
synchronized(this.publicKeys) {
synchronized(this.publicSelectedKeys) {
//看这个方法,实际调用的是sun.nio.ch.EPollSelectorImpl.doSelect(long timeout)
var10000 = this.doSelect(var1);
}
}
return var10000;
}
}
}
sun.nio.ch.EPollSelectorImpl.doSelect(long timeout)
protected int doSelect(long timeout) throws IOException {
if (closed)
throw new ClosedSelectorException();
processDeregisterQueue();
try {
//这个方法会在当前线程上注册一个钩子,保证线程在执行的时候如果被中断则进行唤醒
begin();
//主要看这个方法,里面做了将Socket文件描述符注册到epoll对象上,以及阻塞等待事件的发生
pollWrapper.poll(timeout);
} finally {
//这个方法会删除begin()方法注册的钩子
end();
}
processDeregisterQueue();
//这个方法会将pollArray数组中的已经完成的事件取出,找到对应的Key将其
//更新前面提到的SelectionKey的Set集合,即selector.selectedKeys()方法返回的集合
int numKeysUpdated = updateSelectedKeys();
if (pollWrapper.interrupted()) {
// Clear the wakeup pipe
pollWrapper.putEventOps(pollWrapper.interruptedIndex(), 0);
synchronized (interruptLock) {
pollWrapper.clearInterrupted();
IOUtil.drain(fd0);
interruptTriggered = false;
}
}
return numKeysUpdated;
}
sun.nio.ch.EPollArrayWrapper.poll(long timeout)
int poll(long timeout) throws IOException {
//查看这个方法,在这里把Socket文件描述符注册到epoll对象上
updateRegistrations();
//在这里阻塞,等待注册到epoll对象上的Socket文件描述符上的事件发生,调用的是native方法,查看HotSpot方法Java_sun_nio_ch_EPollArrayWrapper_epollWait
//返回值,updated就是就绪的文件描述符的个数
//native方法epollWait()
//pollArrayAddress是发生事件的数组地址,epoll会把发生的事件复制到数组中
//updated设置为要处理的事件数
updated = epollWait(pollArrayAddress, NUM_EPOLLEVENTS, timeout, epfd);
for (int i=0; i<updated; i++) {
//这里判断是否是中断pipe的fd,如果是则直接break跳出循环
if (getDescriptor(i) == incomingInterruptFD) {
interruptedIndex = i;
interrupted = true;
break;
}
}
//返回就绪的文件描述符的个数
return updated;
}
sun.nio.ch.updateRegistrations()
private void updateRegistrations() {
synchronized (updateLock) {
int j = 0;
while (j < updateCount) {
//看到这个updateDescriptors变量,就是在sun.nio.ch.SelectorImpl#register(AbstractSelectableChannel var1, int var2, Object var3)方法里面设置进去的。
//从更新文件描述符数组中获取文件描述符(刚刚我们已经将fd添加进去)
int fd = updateDescriptors[j];
//看这个代码,获取这个Socket感兴趣的事件,就是在sun.nio.ch.SelectionKeyImpl#interestOps(int)这个代码放入的
short events = getUpdateEvents(fd);
//registered是一个bitMap跟踪是否文件描述符被epoll注册
boolean isRegistered = registered.get(fd);
//下面开始根据,我们在java层面设置的对应Channel其实也就是操作系统底层的Socket对象所感兴趣的事件,来计算出要传给操作系统系统调用的真正的opcode
int opcode = 0;
if (events != KILLED) {
//如果已经注册过且不为0则为修改,如果为0则为删除
//如果是添加,则标记为注册过,因为在linux层面,
//对于epoll_ctl()方法,EPOLL_CTL_ADD参数
//将描述符 fd 添加到 epoll 实例 epfd 中的兴趣列表中去。对于 fd 上我们感兴趣的事件,都
//指定在 ev 所指向的结构体中,下面会详细介绍。如果我们试图向兴趣列表中添加一个已存在
//的文件描述符,epoll_ctl()将出现 EEXIST 错误。-----《Linux-UNIX系统编程手册(上、下册)中文版》第63.4.2章节
if (isRegistered) {
opcode = (events != 0) ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;
} else {
//如果没注册过且不为0则为添加
opcode = (events != 0) ? EPOLL_CTL_ADD : 0;
}
if (opcode != 0) {
//查看这个方法,是个navive方法,所以看HotSpot源码Java_sun_nio_ch_EPollArrayWrapper_epollCtl
epollCtl(epfd, opcode, fd, events);
//更新registered
if (opcode == EPOLL_CTL_ADD) {
//如果是添加,则标记为注册过,因为在linux层面,
//对于epoll_ctl()方法,EPOLL_CTL_ADD参数
//将描述符 fd 添加到 epoll 实例 epfd 中的兴趣列表中去。对于 fd 上我们感兴趣的事件,都
//指定在 ev 所指向的结构体中,下面会详细介绍。如果我们试图向兴趣列表中添加一个已存在
//的文件描述符,epoll_ctl()将出现 EEXIST 错误。-----《Linux-UNIX系统编程手册(上、下册)中文版》第63.4.2章节
registered.set(fd);
} else if (opcode == EPOLL_CTL_DEL) {
registered.clear(fd);
}
}
}
j++;
}
updateCount = 0;
}
}
JNIEXPORT void JNICALL
Java_sun_nio_ch_EPollArrayWrapper_epollCtl(JNIEnv *env, jobject this, jint epfd,
jint opcode, jint fd, jint events)
{
struct epoll_event event;
int res;
event.events = events;
event.data.fd = fd;
//系统调用 epoll_ctl()能够修改由文件描述符 epfd 所代表的 epoll 实例中的兴趣列表。
//参数 fd 指明了要修改兴趣列表中的哪一个文件描述符的设定。该参数可以是代表管道、
//FIFO、套接字、POSIX 消息队列、inotify 实例、终端、设备,甚至是另一个 epoll 实例的文件
//描述符(例如,我们可以为受检查的描述符建立起一种层次关系)。但是,这里 fd 不能作为普
//通文件或目录的文件描述符(会出现 EPERM 错误)。-----《Linux-UNIX系统编程手册(上、下册)中文版》第63.4.2章节
RESTARTABLE(epoll_ctl(epfd, (int)opcode, (int)fd, &event), res);
/*
* A channel may be registered with several Selectors. When each Selector
* is polled a EPOLL_CTL_DEL op will be inserted into its pending update
* list to remove the file descriptor from epoll. The "last" Selector will
* close the file descriptor which automatically unregisters it from each
* epoll descriptor. To avoid costly synchronization between Selectors we
* allow pending updates to be processed, ignoring errors. The errors are
* harmless as the last update for the file descriptor is guaranteed to
* be EPOLL_CTL_DEL.
*/
if (res < 0 && errno != EBADF && errno != ENOENT && errno != EPERM) {
JNU_ThrowIOExceptionWithLastError(env, "epoll_ctl failed");
}
}
事件注册完了后,再回到sun.nio.ch.EPollArrayWrapper.poll(long timeout)方法
接着调用Java_sun_nio_ch_EPollArrayWrapper_epollWait
JNIEXPORT jint JNICALL
Java_sun_nio_ch_EPollArrayWrapper_epollWait(JNIEnv *env, jobject this,
jlong address, jint numfds,
jlong timeout, jint epfd)
{
struct epoll_event *events = jlong_to_ptr(address);
int res;
if (timeout <= 0) { /* Indefinite or no wait */
//系统调用 epoll_wait()返回 epoll 实例中处于就绪态的文件描述符信息。单个 epoll_wait()调
//用能返回多个就绪态文件描述符的信息。-----《Linux-UNIX系统编程手册(上、下册)中文版》第63.4.3章节
RESTARTABLE(epoll_wait(epfd, events, numfds, timeout), res);
} else { /* Bounded wait; bounded restarts */
res = iepoll(epfd, events, numfds, timeout);
}
if (res < 0) {
JNU_ThrowIOExceptionWithLastError(env, "epoll_wait failed");
}
return res;
}
回到sun.nio.ch.EPollSelectorImpl.doSelect(long timeout)方法,接着查看updateSelectedKeys()这一步。
private int updateSelectedKeys() { int entries = this.pollWrapper.updated; int numKeysUpdated = 0;
private int updateSelectedKeys() {
//获取已经就绪的事件数,updated就是在`sun.nio.ch.EPollArrayWrapper.poll(long timeout)`这个方法中设置的。
int entries = this.pollWrapper.updated;
int numKeysUpdated = 0;
for(int i = 0; i < entries; ++i) {
//这个方法是从epollArray中获取就绪的事件关联的fd句柄,就是根据在 epollWait(pollArrayAddress, NUM_EPOLLEVENTS, timeout, epfd)这句代码中最终系统调用epoll_wait(epfd, events, numfds, timeout),在events里设置地址
//然后在这里根据上面设置的这个地址,和偏移量拿到已经就绪的Socket的文件描述符。
//上面是个人猜测,然后找挖坑的张师傅求证,结论正确,具体的以后要研究系统调用的epoll_wait源码。暂时可参考,https://mp.weixin.qq.com/s/OmRdUgO1guMX76EdZn11UQ
int nextFD = this.pollWrapper.getDescriptor(i);
//根据文件描述符,从fdToKey中获取该文件描述符所对应的SelectionKeyImpl对象,是在`sun.nio.ch.implRegister(SelectionKeyImpl ski)`方法里面绑定的
SelectionKeyImpl ski = (SelectionKeyImpl)this.fdToKey.get(nextFD);
if (ski != null) {
//这个方法是从epollArray中获取就绪的事件类型
int rOps = this.pollWrapper.getEventOps(i);
//翻译事件并将其加入selectedkeys集合中
if (this.selectedKeys.contains(ski)) {
if (ski.channel.translateAndSetReadyOps(rOps, ski)) {
++numKeysUpdated;
}
} else {
ski.channel.translateAndSetReadyOps(rOps, ski);
if ((ski.nioReadyOps() & ski.nioInterestOps()) != 0) {
//放入到selectedKeys中,注意看下这个变量
this.selectedKeys.add(ski);
++numKeysUpdated;
}
}
}
}
return numKeysUpdated;
}
sun.nio.ch.SelectorImpl.selectedKeys这个变量
在这个构造方法方法sun.nio.ch.SelectorImpl(SelectorProvider sp)
protected SelectorImpl(SelectorProvider sp) {
super(sp);
if (Util.atBugLevel("1.4")) {
this.publicKeys = this.keys;
this.publicSelectedKeys = this.selectedKeys;
} else {
this.publicKeys = Collections.unmodifiableSet(this.keys);
//会把selectedKeys值作为publicSelectedKeys的成员变量,所以它俩相当于同一个集合
this.publicSelectedKeys = Util.ungrowableSet(this.selectedKeys);
}
}
所以在例子中的Iterator<SelectionKey> it = selector.selectedKeys().iterator();
sun.nio.ch.SelectorImpl.selectedKeys()
public Set<SelectionKey> selectedKeys() {
if (!this.isOpen() && !Util.atBugLevel("1.4")) {
throw new ClosedSelectorException();
} else {
//所以这里返回的publicSelectedKeys实际就是上面的selectedKeys,里面放了就绪的Socket事件的文件描述符。
return this.publicSelectedKeys;
}
}
再来看看例子中SocketChannel sc = ssc.accept();这行代码
它最终会调用到sun.nio.ch.ServerSocketChannelImpl#accept0然后进行Java本法方法的调用,具体会调用到JVM的如下方法:
JNIEXPORT jint JNICALL
Java_sun_nio_ch_ServerSocketChannelImpl_accept0(JNIEnv *env, jobject this,
jobject ssfdo, jobject newfdo,
jobjectArray isaa)
{
jint ssfd = (*env)->GetIntField(env, ssfdo, fd_fdID);
jint newfd;
struct sockaddr *sa;
int alloc_len;
jobject remote_ia = 0;
jobject isa;
jint remote_port;
NET_AllocSockaddr(&sa, &alloc_len);
/*
* accept connection but ignore ECONNABORTED indicating that
* a connection was eagerly accepted but was reset before
* accept() was called.
*/
for (;;) {
socklen_t sa_len = alloc_len;
//这里就是进行了系统的调用,获取一个新的Socket的文件描述符,来和客户端的Socket进行对话
//参考-----《Linux-UNIX系统编程手册(上、下册)中文版》第56.5.2章节和UNIX网络编程卷1:套接字联网API(第3版)第4.5和第4.6以及,https://blog.51cto.com/ticktick/779866。用这个newfd对应的Socket和客户端的Socket进行通信
newfd = accept(ssfd, sa, &sa_len);
if (newfd >= 0) {
break;
}
if (errno != ECONNABORTED) {
break;
}
/* ECONNABORTED => restart accept */
}
if (newfd < 0) {
free((void *)sa);
if (errno == EAGAIN)
return IOS_UNAVAILABLE;
if (errno == EINTR)
return IOS_INTERRUPTED;
JNU_ThrowIOExceptionWithLastError(env, "Accept failed");
return IOS_THROWN;
}
(*env)->SetIntField(env, newfdo, fd_fdID, newfd);
remote_ia = NET_SockaddrToInetAddress(env, sa, (int *)&remote_port);
free((void *)sa);
isa = (*env)->NewObject(env, isa_class, isa_ctorID,
remote_ia, remote_port);
(*env)->SetObjectArrayElement(env, isaa, 0, isa);
return 1;
}