深入理解NIO - Selector、ServerSocketChannel、SocketChannel底层原理

IO多路复用模型

IO多路复用需要OS的支持,IO多路复用模型中,引入了一种新的系统调用,查询IO的就绪状态。

在Linux系统中,JavaNIO的Selector#select() 方法对应的系统调用为select/epoll系统调用。

深入理解NIO - Selector、ServerSocketChannel、SocketChannel底层原理_第1张图片

通过该系统调用,一个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是内核缓冲区可读/可写),内核能够将就绪的状态返回给应用程序。随后,应用程序根据就绪的状态,进行相应的IO系统调用。

深入理解NIO - Selector、ServerSocketChannel、SocketChannel底层原理_第2张图片

目前支持IO多路复用的系统调用,有select、epoll等等。select系统调用,几乎在所有的操作系统上都有支持,具有良好的跨平台特性。epoll是在Linux 2.6内核中提出的,是select系统调用的Linux增强版本。

在IO多路复用模型中通过select/epoll系统调用单个应用程序的线程,可以不断地轮询成百上千的socket连接,当某个或者某些socket网络连接有IO就绪的状态,就返回对应的可以执行的读写操作。

深入理解NIO - Selector、ServerSocketChannel、SocketChannel底层原理_第3张图片

IO多路复用模型的特点:

IO多路复用模型的IO涉及两种系统调用(System Call),一种是IO操作,另一种是select/epoll(就绪查询)。

IO多路复用模型建立在操作系统的基础设施之上,即操作系统的内核必须能够提供多路分离的系统调用select/epoll。

多路复用IO也需要轮询。负责select/epoll状态查询调用的线程,需要不断地进行select/epoll轮询,查找出达到IO操作就绪的socket连接。IO多路复用模型与同步非阻塞IO模型是有密切关系的。

对于注册在选择器上的每一个可以查询的socket连接,一般都设置成为同步非阻塞模型。仅是这一点,对于用户程序而言是无感知的。

IO多路复用模型的优点:

与一个线程维护一个连接的阻塞IO模式相比,使用select/epoll的最大优势在于,一个选择器查询线程可以同时处理成千上万个连接(Connection)。系统不必创建大量的线程,也不必维护这些线程,从而大大减小了系统的开销。Java语言的NIO(New IO)技术,使用的就是IO多路复用模型。在Linux系统上,使用的是epoll系统调用。

IO多路复用模型的缺点:

本质上,select/epoll系统调用是阻塞式的,属于同步IO。都需要在读写事件就绪后,由系统调用本身负责进行读写,也就是说这个读写过程是阻塞的。

⭐️注意:轮询的效率比较低,在硬件和操作系统层有个概念叫做“中断”, 这种机制可以在设备准备好的情况下,主动通知等待的程序。

Selector


Selector selector = Selector.open();

调用栈如下,在我的笔记本上open() 方法就是返回一个WindowsSelectorImpl 实例,在其构造方法中调用了一些native 方法。

<init>:126, WindowsSelectorImpl (sun.nio.ch)
openSelector:44, WindowsSelectorProvider (sun.nio.ch)
open:227, Selector (java.nio.channels)
startServer:63, NioReceiveServer (com.yh.stu.nio.socket)
main:177, NioReceiveServer (com.yh.stu.nio.socket)
深入理解NIO - Selector、ServerSocketChannel、SocketChannel底层原理_第4张图片

ServerSocketChannel

 // 2、获取通道
 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
 // 3.设置为非阻塞
 serverSocketChannel.configureBlocking(false);
 // 4、绑定连接
 ServerSocket serverSocket = serverSocketChannel.socket();
 InetSocketAddress address
         = new InetSocketAddress(NioDemoConfig.SOCKET_SERVER_PORT);
 serverSocket.bind(address);
 // 5、将通道注册到选择器上,并注册的IO事件为:“接收新连接”
 Print.tcfo("serverSocketChannel is linstening...");
 serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);

ServerSocketChannel.open() 创建一个ServerSocketChannel 对象(实际类型是 ServerSocketChannelImpl)对象,如下图

# new ServerSocketChannelImpl(SelectorProviderImpl)
openServerSocketChannel:56, SelectorProviderImpl (sun.nio.ch)
open:108, ServerSocketChannel (java.nio.channels)
run:120, PipeImpl$Initializer$LoopbackConnector (sun.nio.ch)
run:76, PipeImpl$Initializer (sun.nio.ch)
run:61, PipeImpl$Initializer (sun.nio.ch)
doPrivileged:-1, AccessController (java.security)
<init>:171, PipeImpl (sun.nio.ch)
openPipe:50, SelectorProviderImpl (sun.nio.ch)
open:155, Pipe (java.nio.channels)
<init>:127, WindowsSelectorImpl (sun.nio.ch)
openSelector:44, WindowsSelectorProvider (sun.nio.ch)
open:227, Selector (java.nio.channels)
startServer:63, NioReceiveServer (com.yh.stu.nio.socket)
main:177, NioReceiveServer (com.yh.stu.nio.socket)
深入理解NIO - Selector、ServerSocketChannel、SocketChannel底层原理_第5张图片

serverSocketChannel.socket() 方法,在ServerSocketChannel 中创建一个SocketChannel 对象 (实际类型是ServerSocketAdaptor)

socket:110, ServerSocketChannelImpl (sun.nio.ch)
createServerSocketChannel:110, NioReceiveServer (com.yh.stu.nio.socket)
startServer:65, NioReceiveServer (com.yh.stu.nio.socket)
main:177, NioReceiveServer (com.yh.stu.nio.socket)
深入理解NIO - Selector、ServerSocketChannel、SocketChannel底层原理_第6张图片

serverSocket.bind(address) 方法最终调用的是 ServerSocketChannelbind(..) 方法,调用栈和示例图如下:

bind:220, ServerSocketChannelImpl (sun.nio.ch)
bind:74, ServerSocketAdaptor (sun.nio.ch)
bind:67, ServerSocketAdaptor (sun.nio.ch)
createServerSocketChannel:113, NioReceiveServer (com.yh.stu.nio.socket)
startServer:65, NioReceiveServer (com.yh.stu.nio.socket)
main:177, NioReceiveServer (com.yh.stu.nio.socket)

| 深入理解NIO - Selector、ServerSocketChannel、SocketChannel底层原理_第7张图片 ||
|–|

调用 WindowsSelectorImpl 父类 SelectorImplregister 方法将进行注册,源码如下:

	//sun.nio.ch.SelectorImpl#register
    protected final SelectionKey register(AbstractSelectableChannel ch,
                                          int ops,
                                          Object attachment)
    {
        if (!(ch instanceof SelChImpl))
            throw new IllegalSelectorException();
        //将`Selector` 和`ServerSocketChannel` 包装成了 `SelectionKeyImpl `对象
        SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);
        k.attach(attachment);
        synchronized (publicKeys) {
            implRegister(k);
        }
        k.interestOps(ops);
        return k;
    }
    
	// sun.nio.ch.WindowsSelectorImpl#implRegister
    protected void implRegister(SelectionKeyImpl ski) {
        synchronized (closeLock) {
            if (pollWrapper == null)
                throw new ClosedSelectorException();
            growIfNeeded();
            channelArray[totalChannels] = ski;
            ski.setIndex(totalChannels);
            fdMap.put(ski);
            keys.add(ski);// 将 `SelectionKeyImpl ski` 放入`HashSet` 中
            pollWrapper.addEntry(totalChannels, ski);
            totalChannels++;
        }
    }
深入理解NIO - Selector、ServerSocketChannel、SocketChannel底层原理_第8张图片

从源码中不难看出,将SelectorServerSocketChannel 包装成了 SelectionKeyImpl对象,并将 SelectionKeyImpl 放入Set 中,同时SelectionKey.OP_ACCEPT

深入理解NIO - Selector、ServerSocketChannel、SocketChannel底层原理_第9张图片

其实在ServerSocketChannel 中也有一个数组,用来放 SelectionKey 的引用,方法调用栈和图示如下

addKey:115, AbstractSelectableChannel (java.nio.channels.spi)
register:213, AbstractSelectableChannel (java.nio.channels.spi)
register:280, SelectableChannel (java.nio.channels)
startServer:66, NioReceiveServer (com.yh.stu.nio.socket)
main:177, NioReceiveServer (com.yh.stu.nio.socket)

···

深入理解NIO - Selector、ServerSocketChannel、SocketChannel底层原理_第10张图片

到这里一切准备就绪了,等待客户端来连接

创建一个连接

调用栈如下

<init>:129, SocketChannelImpl (sun.nio.ch)
accept:266, ServerSocketChannelImpl (sun.nio.ch)
createSocketChannel:97, NioReceiveServer (com.yh.stu.nio.socket)
startServer:78, NioReceiveServer (com.yh.stu.nio.socket)
main:177, NioReceiveServer (com.yh.stu.nio.socket)
SocketChannelImpl(SelectorProvider sp,
                      FileDescriptor fd, InetSocketAddress remote)
        throws IOException
    {
        super(sp);
        this.fd = fd;
        this.fdVal = IOUtil.fdVal(fd);
        this.state = ST_CONNECTED;
        this.localAddress = Net.localAddress(fd);
        this.remoteAddress = remote;
    }

你可能感兴趣的:(NIO)