IO model-see through NIO and EPOLL

Mainly research NIO and EPOLL to
discuss in-depth Java code -> HotSpot source code -> linux function

一. BIO

1 public static void main(String[] args) throws IOException {
    
    
2	ServerSocket serverSocket = new ServerSocket(9000);
3	
4	while (true) {
    
    
5	    Socket socket = serverSocket.accept();	
6	    byte[] buffer = new byte[1024];
7	    int read = socket.getInputStream().read(buffer);
8	    if (read != 0) {
    
    
9	        String data = new String(buffer, 0, read);
10	        System.out.println(data);
11	        OutputStream outputStream = socket.getOutputStream();
12	
13	        outputStream.write(data.getBytes());
14	        outputStream.flush();
15	        socket.close();
	    }
	}
}

Terminal command: When
Insert picture description here
debug is started, you will find that the pc pointer will be blocked on line 5. We start the terminal telnet localhost 9000 and the
pointer will continue to execute, and block again on line 7. At this time, we will send bio test in the terminal and the pc will continue to execute
. The terminal will start a console again, go to telnet and find that it is also blocked, and then send it will wait for the first execution to complete before executing the following

The idea console is as follows:
Insert picture description here

2. NIO

  1. No multiplexing, pure non-blocking
public static void main(String[] args) throws IOException {
    
    
	List<SocketChannel> clientSocketList = new ArrayList<>(1024);
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.socket().bind(new InetSocketAddress(9000));
    serverSocketChannel.configureBlocking(false);

    while (true) {
    
    
        SocketChannel clientSocket = serverSocketChannel.accept();
        if (clientSocket != null) {
    
    
            clientSocket.configureBlocking(false);
            clientSocketList.add(clientSocket);
        }

        Iterator<SocketChannel> iterator = clientSocketList.iterator();

        while (iterator.hasNext()) {
    
    
            SocketChannel sc = iterator.next();
            ByteBuffer dst = ByteBuffer.allocate(1024);
            int len = sc.read(dst);

            if (len > 0) {
    
    
                System.out.println("接收到消息: " + new String(dst.array()));
            }
        }
    }
}

From the above code, we find that a very important parameter configureBlocking(false) is set, set to non-blocking, accept and read are not blocked, and the incoming socket is placed in a container, and then loop to facilitate this container to see if there is an inputStream Stream data, output if there is any, continue the next cycle.
Open two terminals telnet localhost 9000 The
first send a and the
second send b

IDEA console is as follows:
Insert picture description here

This code example (can be understood as) is the working principle of Linux's select, and poll is to set the maximum value of the clientSocketList container to Integer.MAX_VALUE.

  1. The key is here!!! The
    key is here!!! The
    key is here!!! The
    key is here!!! The
    key is here!!!
    Using multiplexed Selector
public static void main(String[] args) throws IOException {
    
    
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.socket().bind(new InetSocketAddress(9000));
    serverSocketChannel.configureBlocking(false);
    // linux epoll_create
    Selector selector = Selector.open();
    // linux epoll_ctl
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

    while (true) {
    
    
        // linux epoll_wait rdList epoll的就绪队列有事件 才会继续执行
        selector.select();

        Set<SelectionKey> selectionKeys = selector.selectedKeys();
        Iterator<SelectionKey> iterator = selectionKeys.iterator();


        while (iterator.hasNext()) {
    
    
            SelectionKey sk = iterator.next();

            if (sk.isAcceptable()) {
    
    
                ServerSocketChannel server = (ServerSocketChannel) sk.channel();
                SocketChannel sc = server.accept();
                sc.configureBlocking(false);
                sc.register(selector, SelectionKey.OP_READ);
                System.out.println("客户端连接成功");
            } else if (sk.isReadable()) {
    
    
                SocketChannel sc = (SocketChannel) sk.channel();
                ByteBuffer dst = ByteBuffer.allocate(1024);
                int len = sc.read(dst);

                if (len > 0) {
    
    
                    System.out.println("接收到消息: " + new String(dst.array()));
                }
            }
            iterator.remove();
        }
    }
}

First terminal telnet localhost 9000
second terminal telnet localhost 9000
first terminal send a
second terminal seng b
first terminal send c

IDEA console is as follows:
Insert picture description here

!!! This code mainly introduces a related operation of EPOLL when running jdk in Linux. The server and the connected socket are registered for OPEN and READ events respectively. When there is no event, it will be blocked in the select() method. When there are related events, the following code logic will be executed.
Next, we will focus on the three methods of Selector, open, register, and select, and what exactly is done.

// open方法 jdk源码
public static Selector open() throws IOException {
    
    
    return SelectorProvider.provider().openSelector();
}

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;
                        // 重点create方法 这里直接点进去 会根据下载的jdk pc版本进入不同方法 我们直接去openjdk源码里看Epoll的实现
                        provider = sun.nio.ch.DefaultSelectorProvider.create();
                        return provider;
                    }
                });
    }
}

!!! Source code analysis

// open jdk源码
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();
}

public class EPollSelectorProvider
    extends SelectorProviderImpl
{
    
    
    public AbstractSelector openSelector() throws IOException {
    
    
    	// return了一个实现类
        return new EPollSelectorImpl(this);
    }

    public Channel inheritedChannel() throws IOException {
    
    
        return InheritedChannel.getChannel();
    }
}

EPollSelectorImpl(SelectorProvider sp) throws IOException {
    
    
    super(sp);
    long pipeFds = IOUtil.makePipe(false);
    fd0 = (int) (pipeFds >>> 32);
    fd1 = (int) pipeFds;
    try {
    
    
    	// new了一个EPollArrayWrapper
        pollWrapper = new EPollArrayWrapper();
        pollWrapper.initInterrupt(fd0, fd1);
        fdToKey = new HashMap<>();
    } catch (Throwable t) {
    
    
        try {
    
    
            FileDispatcherImpl.closeIntFD(fd0);
        } catch (IOException ioe0) {
    
    
            t.addSuppressed(ioe0);
        }
        try {
    
    
            FileDispatcherImpl.closeIntFD(fd1);
        } catch (IOException ioe1) {
    
    
            t.addSuppressed(ioe1);
        }
        throw t;
    }
}

// EPollArrayWrapper里
EPollArrayWrapper() throws IOException {
    
    
    // creates the epoll file descriptor
    // 我们都知道linux里 一切皆是文件 所以创建了一个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<>();
}

// 三个核心native方法
private native int epollCreate();
private native void epollCtl(int epfd, int opcode, int fd, int events);
private native int epollWait(long pollAddress, int numfds, long timeout, int epfd) throws IOException;

// 我们先拿epollCreate()举例, 我们要去看c语言如何实现这个方法, 那么拿类名加下划线和方法名, 
// EPollArrayWrapper_epollCreate
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 linux中的方法 C语言可以直接调用它 
     // 在linux里用man命令可以查看相关方法的详细描述
    int epfd = epoll_create(256);
    if (epfd < 0) {
    
    
       JNU_ThrowIOExceptionWithLastError(env, "epoll_create failed");
    }
    return epfd;
}

The above is the open analysis of selector jdk source -> hotspot source -> linux syntax summary
register corresponds to epollCtl, epollCtl is equivalent to registering an event
select corresponds to epollWait, epollWait is equivalent to binding this event and listening

三. AIO

public static void main(String[] args) throws IOException, InterruptedException {
    
    
    AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(9000));

    serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
    
    
        @Override
        public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
    
    
            try {
    
    
                System.out.println("2--"+Thread.currentThread().getName());
                // 再此接收客户端连接,如果不写这行代码后面的客户端连接连不上服务端
                serverChannel.accept(attachment, this);
                System.out.println(socketChannel.getRemoteAddress());
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
    
    
                    @Override
                    public void completed(Integer result, ByteBuffer buffer) {
    
    
                        System.out.println("3--"+Thread.currentThread().getName());
                        buffer.flip();
                        System.out.println(new String(buffer.array(), 0, result));
                        socketChannel.write(ByteBuffer.wrap("HelloClient".getBytes()));
                    }

                    @Override
                    public void failed(Throwable exc, ByteBuffer buffer) {
    
    
                        exc.printStackTrace();
                    }
                });
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }

        @Override
        public void failed(Throwable exc, Object attachment) {
    
    
            exc.printStackTrace();
        }
    });

    System.out.println("1--"+Thread.currentThread().getName());
    Thread.sleep(Integer.MAX_VALUE);
}

Start the first terminal and execute telnet localhost 9000 and send a
Start the second terminal and execute telnet localhost 9000 and send b

IDEA console is as follows:
Insert picture description here

In the code case of BIO and NIO, they are all executed by the main thread, and this AIO has so many threads.

Four. Summary

The main difference between BIO, NIO, and AIO is divided into two blocking and non-blocking, synchronous and non-synchronous.
From the first and second examples, we can understand the difference between blocking and non-blocking.
From the third and fourth For example, we can see the difference between synchronous and asynchronous.

Insert picture description here

NIO appeared after 1.4, first adopted linux select, then poll
1.5 and later adopted epoll's IO multiplexing architecture

Insert picture description here

Guess you like

Origin blog.csdn.net/weixin_45657738/article/details/112722525