java nio 源码分析 -windows (select模型)

本文详细分析了Java NIO中的Select模型,包括阻塞与非阻塞IO、Selector的open()、select()方法的工作原理,以及ServerSocketChannel的配置和注册过程。通过对源码的解读,阐述了select()方法的核心逻辑,以及如何处理就绪事件。文章还探讨了Windows环境下NIO的性能局限,并提及了Linux下的epoll作为更高效的替代方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java socket

1.什么是nio

阻塞/非阻塞与同步/异步

1.1 阻塞io

阻塞 I/O 发起的 read 请求,线程会被挂起,一直等到内核数据准备好,并把数据从内核区域拷贝到应用程序的缓冲区中,当拷贝过程完成,read 请求调用才返回。接下来,应用程序就可以对缓冲区的数据进行数据解析。
在这里插入图片描述

1.2 非阻塞

非阻塞的 read 请求在数据未准备好的情况下立即返回,应用程序可以不断轮询内核,直到数据准备好,内核将数据拷贝到应用程序缓冲,并完成这次 read 调用。注意,这里最后一次 read 调用,获取数据的过程,是一个同步的过程。这里的同步指的是内核区域的数据拷贝到缓存区这个过程。
在这里插入图片描述

那么这时候我们需要不停的去轮询查询内核io是否已经准备好了
于是有了我们的select、poll的多路复用技术出现(NIO)
在这里插入图片描述

1.3 同步?异步?

其实上述的多路复用都是同步调用技术。为什么这么说呢?因为同步调用、异步调用的说法,是对于获取数据的过程而言的,前面几种最后获取数据的 read 操作调用,都是同步的,在 read 调用时,内核将数据从内核空间拷贝到应用程序空间,这个过程是在 read 函数中同步进行的,如果内核实现的拷贝效率很差,read 调用就会在这个同步过程中消耗比较长的时间。

而真正的异步是当我们发起 aio_read 之后 ,内核自动将数据从内核空间拷贝到应用程序空间,这个拷贝过程是异步的,内核自动完成的,和前面的同步操作不一样,应用程序并不需要主动发起拷贝动作。
在这里插入图片描述

这也就是我们所说的AIO了

2.Selector

2.1 Selector.open()

我们来看如下代码

Selector.open();
public static Selector open() throws IOException {
       
    return SelectorProvider.provider().openSelector();
}

这里会创建一个provider

public static SelectorProvider provider() {
   
        synchronized (lock) {
   
            if (provider != null)
                return provider;
            return AccessController.doPrivileged(
                new PrivilegedAction<>() {
   
                    public SelectorProvider run() {
   
               if(loadProviderFromProperty())
                                return provider;
                        if(loadProviderAsService())
                                return provider;
							provider=sun.nio.ch.DefaultSelectorProvider.create();
                            return provider;
                        }
                    });
        }
    }

这里调用了DefaultSelectorProvider创建SelectorProvider

在windows环境中这里创建的是WindowsSelectorProvider

    public static SelectorProvider create() {
   
        return new sun.nio.ch.WindowsSelectorProvider();
    }

随后调用WindowsSelectorProvider.openSelector

    public AbstractSelector openSelector() throws IOException {
   
        return new WindowsSelectorImpl(this);
    }

那么我们可以来看这个构造器做了什么事

    WindowsSelectorImpl(SelectorProvider sp) throws IOException {
   
        super(sp);
        pollWrapper = new PollArrayWrapper(INIT_CAP);
        wakeupPipe = Pipe.open();
        wakeupSourceFd = ((SelChImpl)wakeupPipe.source()).getFDVal();

        // Disable the Nagle algorithm so that the wakeup is more immediate
        SinkChannelImpl sink = (SinkChannelImpl)wakeupPipe.sink();
        (sink.sc).socket().setTcpNoDelay(true);
        wakeupSinkFd = ((SelChImpl)sink).getFDVal();

        pollWrapper.addWakeupSocket(wakeupSourceFd, 0);
    }

这里调用了super(sp)

        keys = ConcurrentHashMap.newKeySet();
        selectedKeys = new HashSet<>();
        publicKeys = Collections.unmodifiableSet(keys);
        publicSelectedKeys = Util.ungrowableSet(selectedKeys);

初始化了key并把SelectorProvider放入Selector对象中

接下来我们继续看创建了一个PollArrayWrapper对象,该对象相当重要java是通过该数组,把事件传输给c中去调用select函数,select参数的读写事件均来自于该数组

这个对象是个包装对象注释如下

Manipulates a native array of structs corresponding to (fd, events) pairs.
typedef struct pollfd {
SOCKET fd; // 4 bytes
short events; // 2 bytes
} pollfd_t;

构造器如下

    private AllocatedNativeObject pollArray; // The fd array

    long pollArrayAddress; // pollArrayAddress

    @Native private static final short FD_OFFSET     = 0; // fd offset in pollfd
    @Native private static final short EVENT_OFFSET  = 4; // events offset in pollfd

    static short SIZE_POLLFD = 8; // sizeof pollfd struct

    private int size; // Size of the pollArray
	PollArrayWrapper(int newSize) {
   
        int allocationSize = newSize * SIZE_POLLFD;
        pollArray = new AllocatedNativeObject(allocationSize, true);
        pollArrayAddress = pollArray.address();
        this.size = newSize;
    }

在C中其实际struct如下

typedef struct {
   
    jint fd;
    jshort events;
} pollfd;

这个之后会用到,那么接下来我们看wakeupPipe = Pipe.open();,什么是Pipe呢,我们先来看一下java中的注释

/**
 * A pair of channels that implements a unidirectional pipe.
 *  一对实现单向管道的通道。
 * <p> A pipe consists of a pair of channels: A writable {@link
 * Pipe.SinkChannel sink} channel and a readable {@link Pipe.SourceChannel source}
 * channel.  Once some bytes are written to the sink channel they can be read
 * from the source channel in exactly the order in which they were written.
 *  管道由一对通道组成,一个可写的SinkChannel和一个可读的SourceChannel
 * <p> Whether or not a thread writing bytes to a pipe will block until another
 * thread reads those bytes, or some previously-written bytes, from the pipe is
 * system-dependent and therefore unspecified.  Many pipe implementations will
 * buffer up to a certain number of bytes between the sink and source channels,
 * but such buffering should not be assumed.  </p>
 *
 *
 * @author Mark Reinhold
 * @author JSR-51 Expert Group
 * @since 1.4
 */

可以看到Pipe这里有2个Channel那么我们继续

    public static Pipe open() throws IOException {
   
        return SelectorProvider.provider().openPipe();
    }
    public Pipe openPipe() throws IOException {
   
        return new PipeImpl(this);
    }
    PipeImpl(final SelectorProvider sp) throws IOException {
   
        try {
   
            AccessController.doPrivileged(new Initializer(sp));
        } catch (PrivilegedActionException x) {
   
            throw (IOException)x.getCause();
        }
    }

这里AccessController.doPrivileged(new Initializer(sp));这里使用了特权调用

为了方便查看这里贴出PipeImpl

class PipeImpl
    extends Pipe
{
   
    // Number of bytes in the secret handshake.
    private static final int NUM_SECRET_BYTES = 16;

    // Random object for handshake values
    private static final Random RANDOM_NUMBER_GENERATOR = new SecureRandom();

    // Source and sink channels
    private SourceChannel source;
    private SinkChannel sink;

    private class Initializer
        implements PrivilegedExceptionAction<Void>
    {
   

        private final SelectorProvider sp;

        private IOException ioe = null;

        private Initializer(SelectorProvider sp) {
   
            this.sp = sp;
        }

        @Override
        public Void run() throws IOException {
   
            LoopbackConnector connector = new LoopbackConnector();
            connector.run();
            if (ioe instanceof ClosedByInterruptException) {
   
                ioe = null;
                Thread connThread = new Thread(connector) {
   
                    @Override
                    public void interrupt() {
   }
                };
                connThread.start();
                for (;;) {
   
                    try {
   
                        connThread.join();
                        break;
                    } catch (InterruptedException ex) {
   }
                }
                Thread.currentThread().interrupt();
            }

            if (ioe != null)
                throw new IOException("Unable to establish loopback connection", ioe);

            return null;
        }

        private class LoopbackConnector implements Runnable {
   

            @Override
            public void run() {
   
                ServerSocketChannel ssc = null;
                SocketChannel sc1 = null;
                SocketChannel sc2 = null;

                try {
   
                    // Create secret with a backing array.
                    ByteBuffer secret = ByteBuffer.allocate(NUM_SECRET_BYTES);
                    ByteBuffer bb = ByteBuffer.allocate(NUM_SECRET_BYTES);

                    // Loopback address
                    InetAddress lb = InetAddress.getLoopbackAddress();
                    assert(lb.isLoopbackAddress());
                    InetSocketAddress sa = null;
                    for(;;) {
   
                        // Bind ServerSocketChannel to a port on the loopback
                        // address
                        if (ssc == null || !ssc.isOpen()) {
   
                            ssc = ServerSocketChannel.open();
                            ssc.socket().bind(new InetSocketAddress(lb, 0));
                            sa = new InetSocketAddress(lb, ssc.socket().getLocalPort());
                        }

                        // Establish connection (assume connections are eagerly
                        // accepted)
                        sc1 = SocketChannel.open(sa);
                        RANDOM_NUMBER_GENERATOR.nextBytes(secret.array());
                        do {
   
                            sc1.write(secret);
                        } while (secret.hasRemaining());
                        secret.rewind();

                        // Get a connection and verify it is legitimate
                        sc2 = ssc.accept();
                        do {
   
                            sc2.read(bb);
                        } while (bb.hasRemaining());
                        bb.rewind();

                        if (bb.equals(secret))
                            break;

                        sc2.close();
                        sc1.close();
                    }

                    // Create source and sink channels
                    source = new SourceChannelImpl(sp, sc1);
                    sink = new SinkChannelImpl(sp, sc2);
                } catch (IOException e) {
   
                    try {
   
                        if (sc1 != null)
                            sc1.close();
                        if (sc2 != null)
                            sc2.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值