“This is the 25th day of my participation in the November Gwen Challenge. See details of the event: The Last Gwen Challenge 2021”.

Like again, form a habit 👏👏

IO model

IO model simply means what kind of channel is used to send and receive data. Java supports three NETWORK programming IO modes: BIO, NIO and AIO.

BIO (Blocking IO)

BIO is a synchronous blocking model, where each client connection corresponds to one processing thread. The typical model is as follows:

BIO server example code

public class SocketServer {

    static ExecutorService threadPool = 
        Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8888);
        while(true){
            System.out.println("Waiting for the client to connect...");
            // Block method, if there is no client connection will always block here
            Socket clientSocket = serverSocket.accept();
            System.out.println("A client is connected...");
            // Single thread mode
            handler(clientSocket);

            // Multithreaded
            // threadPool.execute(() -> {
            // try {
            // handler(clientSocket);
            // } catch (IOException e) {
            // e.printStackTrace();
            / /}
            / /});}}private static void handler(Socket clientSocket) throws IOException {
        byte[] bytes = new byte[12];
        System.out.println("Ready to read...");
        // Receive data from client, block method, block when there is no data to read
        int read = clientSocket.getInputStream().read(bytes);
        System.out.println("Read complete...");
        if(read ! = -1){
            System.out.println("Received data from client:" + new String(bytes,0,read));
        }
        clientSocket.getOutputStream().write("HelloClient".getBytes()); clientSocket.getOutputStream().flush(); }}Copy the code

BIO client sample code

public class SocketClient {

    public static void main(String[] args) throws IOException {
        // Address and port to connect to the server
        Socket socket = new Socket("localhost".8888);
        // Send data to the server
        socket.getOutputStream().write("Bio Client Tests".getBytes());
        socket.getOutputStream().flush();
        System.out.println("Sending data to the server end!!!!!");
        byte[] bytes = new byte[1024];
        // Receives data from the server
        socket.getInputStream().read(bytes);
        System.out.println("Data received from server:" + newString(bytes)); socket.close(); }}Copy the code

disadvantages

Due to the accept () method is a blocking, if there is no client connection, will have been blocked, and in the case of a single thread due to read is a blocking operation, if the connection is not do data read and write operations will lead to thread blocking, waste of resources, but also in other client don’t come, multi-threaded can solve this problem, However, many clients will waste a lot of resources if they are just connected without reading or writing. If there are too many threads, the server will have too many threads and the pressure will be too great, such as THE C10K problem.

Application scenarios

The BIO mode applies to scenarios where the number of connections is small and a large amount of data is sent at a time. This mode has high requirements on server resources and the concurrency is limited to applications. But the program is simple and easy to understand.

NIO (Non Blocking IO)

Synchronous non-blocking, the server implementation mode is that a thread can process multiple requests (connections), the connection request sent by the client will be registered with the multiplexer selector, multiplexer polling to the connection has IO requests to process, JDK1.4 began to introduce.

Application Scenarios:

NIO is suitable for architectures with large number of connections and relatively short connections (light operation), such as chat server, bullet screen system, communication between servers, and programming is complicated.

NIO server code example

public class NioServer {
    // Save the client connection
    static List<SocketChannel> channelList = new ArrayList<>();
    
    public static void main(String[] args) throws IOException {
        // Create a NIO ServerSocketChannel, similar to the BIO serverSocket
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(8888));
        // Set ServerSocketChannel to non-blocking
        serverSocket.configureBlocking(false);
        System.out.println("Service started successfully");
        while(true) {// The accept method in non-blocking mode does not block, otherwise it would
            //NIO's non-blocking is implemented internally by the Linux kernel's accept function
            SocketChannel socketChannel = serverSocket.accept();
            if(socketChannel ! =null){
                System.out.println("Connection successful");
                // Set SocketChannel to non-blocking
                socketChannel.configureBlocking(false);
                // Save client connections in List
                channelList.add(socketChannel);
            }
            // Traverses the connection for data reading
            Iterator<SocketChannel> iterator = channelList.iterator();
            while(iterator.hasNext()){
                SocketChannel sc = iterator.next();
                ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                // Non-blocking mode The read method does not block, otherwise it would
                int len = sc.read(byteBuffer);
                // If there is data, print it out
                if (len > 0){
                    System.out.println("Received message:" + new String(byteBuffer.array()));
                }else if (len == -1) {// If the client is disconnected, remove the socket from the set
                    iterator.remove();
                    System.out.println("Client disconnects");
                }
            }
        }
    }
}
Copy the code

Conclusion: if the number of connections too much, there will be a lot of invalid traversal, if there are 10000 links, of which only 1000 connections have write data, but because of the other 9000 connection didn’t disconnect, every time we’re going to the polling traversal ten thousand times, with nine over ten of the traversal is invalid, this is obviously not a very satisfactory condition.

NIO introduces examples of multiplexer code

public class NioSelectorServer {

    public static void main(String[] args) throws IOException {
        // Create NIO ServerSocketChannel
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(9000));
        // Set ServerSocketChannel to non-blocking
        serverSocket.configureBlocking(false);
        // Turn on Selector to handle channels, which creates epoll
        Selector selector = Selector.open();
        // Register a ServerSocketChannel with a Selector that is interested in the client accept connection
        serverSocket.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("Service started successfully");
        while(true) {// Block to wait for an event to occur
            selector.select();
            // Get SelectionKey instances of all events registered in selector
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            // Process the event by iterating through SelectionKey
            while(iterator.hasNext()){
                SelectionKey key = iterator.next();
                // If the event is OP_ACCEPT, the connection is made to obtain the congratulations event registration
                if (key.isAcceptable()){
                    ServerSocketChannel server = (ServerSocketChannel)key.channel();
                    SocketChannel socketChannel = server.accept();
                    socketChannel.configureBlocking(false);
                    // Only read events are registered. If you need to send data to the client, you can register write events
                    socketChannel.register(selector,SelectionKey.OP_READ);
                    System.out.println("Client connected successfully");
                }else if (key.isReadable()){        // If the event is OP_READ, the read is printed
                    SocketChannel socketChannel = (SocketChannel)key.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                    int len = socketChannel.read(byteBuffer);
                    // If there is data, print it out
                    if (len > 0){
                        System.out.println("Received message:" + new String(byteBuffer.array()));
                    }else if(len == -1) {// If the client disconnects, close the Socket
                        System.out.println("Client disconnects"); socketChannel.close(); }}// Delete the key from the event set to prevent repeat processing of the next selectiterator.remove(); }}}}Copy the code

NIO has three core components: channels, buffers, and selectors.

1) A channel is similar to a stream. Each channel corresponds to a buffer buffer, and the underlying buffer is an array.

2) A channel is registered with a selector, which sends it to an idle thread for processing according to the occurrence of a channel read or write event.

NIO Buffer and channel are both readable and writable.

NIO in JDK1.4 is implemented using the Linux kernel function select() or poll(). Similar to NioServer code, selector polls all sockchannels each time to see which channel has read or write events. Epoll was introduced in JDK1.5 to optimize NIO based on event response.

The underlying implementation

The following methods are important in the NioSelectorServer code and are understood at the level of Hotspot and Linux kernel functions

Selector.open() // Create a multiplexer

socketChannel.register(selector, SelectionKey.OP_READ) // Register a channel with the multiplexer

selector.select() // Block to wait for an event to occur
Copy the code

Conclusion: The whole call process of NIO is that Java calls the kernel function of the operating system to create the Socket, obtains the file descriptor of the Socket, and then creates a Selector object corresponding to the Epoll descriptor of the operating system. Takes on the Socket connection file descriptor event is bound to the Selector corresponding Epoll file descriptor, for asynchronous notifications of events, this is achieved using a thread, and do not need too many invalid traversal, event handling to the operating system kernel interruption program implementation (operating system), greatly improving the efficiency.

Epoll function details

(1) int epoll_create(int size);

Creates an epoll instance and returns a non-negative number as a file descriptor for all subsequent calls to the epoll interface.

The size parameter may hold size descriptors, but size is not a maximum, just an indication to the operating system of its order of magnitude, and is now largely deprecated.

So I call Selector Selector = Selector. Open (); Executing this statement essentially creates an epoll object, created by calling the operating system function epoll_create.

(2) int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

The op operation is performed on the target file descriptor fd using the epoll instance referenced by the file descriptor EPfd.

The epfd parameter indicates the file descriptor corresponding to epoll, and the fd parameter indicates the file descriptor corresponding to socket.

The op argument has the following values:

  • EPOLL_CTL_ADD: Registers a new FD to an EPFD and associates the event with it.
  • EPOLL_CTL_MOD: Modifies the listening event of a registered FD.
  • EPOLL_CTL_DEL: Removes the fd from the EPFD and ignores the bound event, which can be null.

The event parameter is a structure

struct epoll_event {
    __uint32_t   events;      /* Epoll events */
    epoll_data_t data;        /* User data variable */
    };

    typedef union epoll_data {
    void        *ptr;
    int          fd;
    __uint32_t   u32;
    __uint64_t   u64;
} epoll_data_t;
Copy the code

There are many possible values for events, but here are just a few of the most common:

  • EPOLLIN: indicates that the corresponding file descriptor is readable.
  • EPOLLOUT: indicates that the corresponding file descriptor is writable.
  • EPOLLERR: Indicates that an error occurs in the file descriptor.

Returns 0 on success and -1 on failure

(3) int epoll_wait(int epfd, struct epoll_event *events, int maxEvents, int timeout);

Wait for events on the file descriptor EPfd.

Epfd is the file descriptor corresponding to Epoll, events is the set of all available events for the caller, maxEvents is the maximum number of events to wait before returning, and timeout is the timeout time.

Linux kernel functions (SELECT, poll, epoll) are used to implement THE I/O multiplexing layer. Windows does not support epoll implementation. Windows layer is based on winsock2 select function implementation (not open source).

select poll Epoll (JDK 1.5 and above)
Mode of operation traverse traverse The callback
The underlying implementation An array of The list Hash table
IO efficiency Each call is linear traversal, order n time. Each call is linear traversal, order n time. Whenever an IO event is ready, the system registered callback function will be called, time complexity O(1)
Maximum connections Have a cap There is no upper limit There is no upper limit

AIO (NIO 2.0)

Asynchronous non-blocking, the operating system after the completion of the callback notified the server program to start the thread to process, generally applicable to a large number of connections and a long connection time applications.

Application scenarios

AIO mode applies to architectures with a large number of connections and long connections (heavy operations), and is supported in JDK7.

AIO server code example

public class AIOServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8888));
        serverChannel.accept(null.new CompletionHandler<AsynchronousSocketChannel, Object>() {

            @Override
            public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
                try{
                    System.out.println("2 --"+Thread.currentThread().getName());
                    // The client cannot connect to the server if you do not write this line of code
                    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 attachment) {
                            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 attachment) { 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); }}Copy the code

AIO client code example

public class AIOClient {
    public static void main(String... args) throws Exception {
        AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1".9000)).get();
        socketChannel.write(ByteBuffer.wrap("HelloServer".getBytes()));
        ByteBuffer buffer = ByteBuffer.allocate(512);
        Integer len = socketChannel.read(buffer).get();
        if(len ! = -1) {
            System.out.println("Client receives message:" + new String(buffer.array(), 0, len)); }}}Copy the code

BIO, NIO, AIO