Common I/O models in Linux

1.5 I/O models

UNIX provides five I/O models based on the classification of I/O models by UNIX network programming

(1) Block THE I/O model

If not, wait until the datagram is ready and then copy the datagram.

(2) Non-blocking I/O model

Polling checks if there is a data ready state, and if the data is ready, proceed to the next operation

(3) I/O multiplexing model

Linux provides select/poll, a process that passes one or more FD’s to a SELECT or poll system call, so that select/poll can help us detect whether multiple FD’s are in a ready state. The select/poll order scans fd are ready, but select supports a limited number of FDS (1024*8). Linux provides ePoll system calls, which use an event-based approach instead of sequential scanning for higher performance. Java NIO Selector based on epoll multiplexing technology implementation.

(4) Signal driven I/O model

The socket signal-driven I/O function is first enabled and a signal-handling function is executed through the system call SIGAction. When the data is ready, a SIGIO signal is generated for the process and the signal callback notifies the application call to read the data.

(5) Asynchronous I/O

The main difference between this model and the signal-driven model is that the kernel tells us when we can start an I/O operation. The asynchronous I/O model lets the kernel tell us when an I/O operation has completed.

2.I/O multiplexing technology

(1) APPLICATION scenarios of I/O multiplexing technology

There are two ways to handle multiple client connection I/O requests: One is the traditional multithreading, the other one is the I/O multiplexing technology for processing, but compared to the traditional multi-threading, I/O multiplexing is the biggest advantage of system overhead is small, do not need to create additional threads or processes processing client connection, saves system resources, the I/O multiplexing main application scenario is as follows:

  • The server needs to process multiple listening or connected sockets simultaneously
  • The server needs to handle sockets for multiple network protocols simultaneously, such as UDP and TCP

(2) Advantages of epoll

In Linux system, I/O multiplexing technology is used to call select, poll, epoll, in the process of Linux network programming. Select poll epoll epoll epoll epoll epoll epoll epoll Epoll improves on SELECT with the following improvements:

1) Support a process open socket descriptor (FD) is not limited

The biggest drawback of SELECT is that there is a limit to how many FDS can be opened by a single process, whereas epoll does not, and poll is no different from Select except that it has no limit (theoretically, based on linked list storage)

2) I/O efficiency does not decrease linearly as the number of FD’s increases

The traditional select/poll method has a fatal disadvantage: when there is a large set of sockets, only a small number of sockets are “active” at any one time due to network latency or idle links, but select/poll still scans the entire set linearly, resulting in a decrease in efficiency. Epoll only operates on active sockets. This is because epoll relies on the callback function on each FD. Only active sockets actively call this function, and no one else does

3) Use Mmap to speed up kernel-user space messaging

Whether it is SELECT, poll or epoll, the kernel needs to notify the user space of FD messages. How to avoid unnecessary memory replication is very important. Epoll uses the kernel to implement the user space Mmap in the same memory.

**4) Epoll uses a simpler API **

Traditional BIO programming

In the development based on the traditional synchronous blocking model, ServerSocket is responsible for binding IP address and starting the listening port. The Socket initiates the connection. After the connection is successful, the two sides communicate synchronously blocking through input and output streams.

Obviously, this model is not flexible enough to scale. As the number of concurrent clients increases, the number of threads also increases, which may cause problems such as thread stack overflow and failure to create new threads.

1. Communication model

BIO communication model of the server, usually by an independent Acceptor thread is responsible for monitoring the customer client connection, after receiving client connection requests for each client to create a new thread to link process, after processing by return to response to the client output stream, destroyed the last thread, this is typical of a request a response communication model.

2. Code practices

(1) Server code (the code has been uploaded to Github)

/** * BIO: A separate Acceptor thread is responsible for listening for client connections. A new thread is created for each client for link processing after receiving a connection request. A response is sent back to the client via an output stream, and the thread is destroyed
public class BioServer {

    public static void main(String[] args) {
        bioServer(8082);
    }

    /** * @param port */
    public static void bioServer(int port) {
        ServerSocket server = null;
        try {
            // ServerSocket Binds IP addresses and starts listening ports
            server = new ServerSocket(port);
            System.out.println("The bio server is start in port : " + port);
            // The Socket initiates the connection
            Socket socket = null;
            // An infinite loop listens for the client connection, if not, the main thread blocks on the ServerSocket accept operation
            while (true) {
                socket = server.accept();
                new Thread(newBioServerHandler(socket)).start(); }}catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(server ! = null) { System.out.println("The bio server close");
            }
            try {
                server.close();
            } catch(IOException e) { e.printStackTrace(); } server = null; }}}Copy the code

(2) Client code (the code has been uploaded to Github)

/** * BIO communication client: A separate Acceptor thread is responsible for listening for client connections. A new thread is created for each client for link processing after receiving a connection request. A response is sent back to the client via an output stream, and the thread is destroyed
public class BioClient {

    public static void main(String[] args) throws InterruptedException {
        String host = "127.0.0.1";
        int port = 8082;
        bioClient(host, port);
    }

    public static void bioClient(String host, int port) {
        Socket socket = null;
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            socket = new Socket(host, port);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(), true);
            // Send the command to query current time
            out.println("QUERY CURRENT TIME ORDER");
            System.out.println("Client send order to server succeed.");
            // Return the reply
            String resp = in.readLine();
            System.out.println("Now is : " + resp);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(in ! = null) {try {
                    in.close();
                } catch(IOException e) { e.printStackTrace(); }}if(out ! = null) { out.close(); out = null; }// Release socket handle resources
            if(socket ! = null) {try {
                    socket.close();
                } catch(IOException e) { e.printStackTrace(); } socket = null; }}}}Copy the code

(3) Processor (code has been uploaded to Github)

public class BioServerHandler implements Runnable {

    private Socket socket;

    public BioServerHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run(a) {
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            out = new PrintWriter(this.socket.getOutputStream(), true);
            String currentTime = null;
            String body = null;
            while (true) {
                body = in.readLine();
                if (body == null) {
                    break;
                }
                System.out.println("The server receive order: " + body);
                currentTime = "QUERY CURRENT TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() :
                        "BAD ORDER"; out.println(currentTime); }}catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(in ! = null) {try {
                    in.close();
                } catch(IOException e) { e.printStackTrace(); }}if(out ! = null) { out.close(); out = null; }// Release socket handle resources
            if (this.socket ! = null) {try {
                    this.socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                this.socket = null; }}}}Copy the code

(4) Test results

Run the server, then run the client:

Server Console:

Client Console:

At the same time, run the netstat command to check TCP listening port 8082

 

In addition, the dump thread is blocked on the Accept method and is in the RUNNABLE state

To solve the problem of synchronous blocking **** /O (processing links: functional threads =1:1), the backend processes multiple client access requests through a thread pool, forming a ratio of clients M: the maximum number of threads in the pool N, where M can be much larger than N. This is to process tasks through a Java Thread pool, rather than generating threads one at a time. This is called “pseudo asynchronous I/O” in the Netty Definitive Guide.