preface

The article will be synchronized to the personal website: xiaoflyfish.cn/

  • Website recently enriched a lot of content, are full of dry goods!

  • Wechat search: month with flying fish, make a friend, into the interview communication group!

  • Public number background reply 666, you can get free electronic books!

Feel good, hope to like, look, forward to support, thank you

background

  • In the age of the Internet, the vast majority of data are acquired through the Internet.

  • In the server-side architecture, the vast majority of data is also interacted over the network.

Moreover, as the development engineer of the server side, they will carry out a series of service design, development and capability opening, and service capability opening also needs to be completed through the network, so they are not too unfamiliar with network programming and network IO model.

Many excellent frameworks (such as Netty, HSF, Dubbo, Thrift, etc.) have encapsulated the underlying network IO, and the desired service capability can be developed by providing API capabilities or configuration. Therefore, most engineers do not have a good understanding of the underlying network IO model.

This article systematically explained Linux kernel IO model, Java network IO model and the relationship between the two!

What is the IO

We all know that in the Linux world, everything is a file.

A file is a binary stream. Whether Socket, FIFO, pipe, or terminal, everything is a stream to us.

  • In the process of information exchange, we send and receive data to these streams, referred to as I/O operations.

  • To Read data into the stream, the system calls Read, Write data, and the system calls Write.

Typically, a complete IO for a user process is divided into two phases:

Disk I/o:

Network IO:

Operating systems and drivers run in kernel space, while applications run in user space, and cannot use Pointers to pass data, because Linux uses virtual memory mechanisms that require system calls to the kernel to complete IO actions.

IO has memory IO, network IO and disk IO three kinds, usually we say IO refers to the latter two!

Why do I need IO models

When using synchronous communication, all operations are executed sequentially in a single thread, which has obvious disadvantages:

  • For synchronous communication operation will block the same thread of any other operation, only after the operation is completed, the subsequent operations can be completed, so the synchronized block + multithreading (each Socket to create a thread), but the number of threads in the system is limited, thread it’s a waste of time, at the same time Suitable for scenarios with few sockets.

The IO model needs to appear.

IO model for Linux

Before describing the Linux IO model, let’s take a look at the process of reading data in Linux:

Take the user requesting the index.html file as an example

The basic concept

User space and kernel space

The core of the operating system is the kernel, which is independent of ordinary applications and has access to the protected memory space as well as all permissions to access the underlying hardware devices.

  • To ensure the security of the kernel, user processes cannot operate the kernel directly. The operating system divides the virtual space into two parts, one is the kernel space and the other is the user space.

Process switching

To control process execution, the kernel must have the ability to suspend a process running on the CPU and resume execution of a previously suspended process.

This behavior is called process switching.

Therefore, it can be said that any process runs under the support of the operating system kernel and is closely related to the kernel.

Process blocking

If some expected events do not occur in the executing process, such as system resource request failure, waiting for the completion of an operation, new data has not arrived, or no new work is done, the system automatically executes Block primitives to change the running state into the blocked state.

It can be seen that the blocking of a process is an active behavior of the process itself, so only the running process (to obtain CPU) can be put into the blocking state.

When a process is blocked, it consumes no CPU resources.

File descriptor

A File Descriptor is a computer science term, an abstract concept used to describe a reference to a File.

Formally a non-negative integer, the file descriptor is, in fact, an index value that points to the record table of open files maintained by the kernel for each process.

  • When a program opens an existing file or creates a new file, the kernel returns a file descriptor to the process.

Cache IO

The default IO operation for most file systems is caching IO.

The read and write process is as follows:

  • Read operation: The operating system checks the kernel buffer for any data it needs, and returns it directly from the cache if it has already been cached. Otherwise, the data is read from disks or network adapters and cached in the operating system cache.

  • Write operation: copies data from user space to the cache in kernel space. It is up to the operating system to decide when to write to disk, network card, etc., unless sync is explicitly called.

Assuming that kernel space caches no needed data, user processes read data from disk or network in two stages:

  • Stage 1: the kernel program reads data from disks, network cards, etc., into the kernel space cache;

  • Phase 2: The user program copies data from the kernel space cache to user space.

Disadvantages of caching IO:

In the process of data transmission, multiple data copy operations need to be performed in the application address space and kernel space. These data copy operations bring high CPU and memory overhead.

A synchronized block

User space application to execute a system call, it would cause the application block, do nothing, until the data is ready, and copies the data from the kernel to the user process, the process to deal with data, waiting for the data to the two stages of the process data, the whole process is blocked, can’t deal with any other network IO.

  • The calling application is in a state where it is no longer consuming CPU but simply waiting for a response, so this is very efficient from a processing perspective.

This is also the simplest IO model, and can be used with fewer FDS and faster readiness.

Synchronous nonblocking

After a non-blocking system call is called, the process is not blocked, and the kernel returns to the process immediately, with an error if the data is not ready.

  • After the process returns, it can do something else before making a system call.

  • Repeat the process above, making system calls over and over again. This process is often called polling.

  • Polling checks the kernel data until it is ready, and then copies the data to the process for data processing.

  • Note that the process is still blocked during the entire process of copying data.

  • In this way, in the program to set the Socket O_NONBLOCK.

IO multiplexing

IO multiplexing, which is the ability for a process to foretell the kernel that one or more IO conditions specified by the process are ready.

Enables a process to wait on a sequence of events.

The I/O multiplexing methods include Select, Poll, and Epoll.

Pseudocode describes IO multiplexing:

While (status == OK) {ready_fd_list = io_wait(fd_list); For (fd in ready_fd_list) {data = read(fd) // Ready data read to user buffer process(data)}}Copy the code

Signal drive

First we allow the Socket to do signal-driven IO and install a signal handler. The process continues to run without blocking.

When the data is ready, the process receives a SIGIO signal and can process the data by calling the I/O operation function in the signal handler function.

The process is as follows:

  • Enable the SOCKET signal driven I/O function

  • The system calls Sigaction to execute the signal handler (non-blocking, return immediately)

  • When the data is ready, a Sigio signal is generated and the application is informed of the data by signal callback

One big problem with this IO approach is that Linux has a limit on the number of signal queues beyond which data cannot be read

Asynchronous nonblocking

The asynchronous I/O process is as follows:

  • When the user thread invokes the aio_read system call, it can immediately start doing something else. The user thread does not block

  • The kernel begins the first stage of IO: preparing data. When the kernel waits until the data is ready, it copies the data from the kernel buffer to the user buffer

  • The kernel sends a signal to the user thread or calls back to the callback interface registered by the user thread to tell the user thread that the Read operation is complete

  • The user thread reads data from the user buffer to complete subsequent service operations

Unlike synchronous I/OS, asynchronous I/OS are not executed sequentially.

After the aiO_read system call is made by the user process, the kernel data is returned directly to the user process, whether it is ready or not, and the user process can do something else.

When the data is ready, the kernel copies the data directly to the process and then sends notifications from the kernel to the process.

The main differences between signal driven I/O and asynchronous I/O are:

  • Signal-driven by the kernel telling us when we can start an IO operation (the data is in the kernel buffer), asynchronous IO by the kernel telling the IO operation when it has completed (the data is already in user space).

Asynchronous IO is also called event-driven IO. In Unix, it defines a set of library functions for accessing files asynchronously and defines a series of interfaces of AIO.

  • useaio_readoraio_writeTo initiate asynchronous I/O operations, run theaio_errorCheck the status of running I/O operations.

At present, the kernel implementation of AIO in Linux is only effective for file IO. If you want to realize the real AIO, you need to implement it by yourself.

There are many open source asynchronous IO libraries, such as Libevent, Libev, and libuv.

Java network IO model

BIO

BIO is a typical network programming model, which is usually the way we implement a server program. It corresponds to the Synchronous blocking IO model of the Linux kernel. The process of sending and receiving data is as follows:

The steps are as follows:

  • The main thread accepts the request

  • When the request arrives, a new thread is created to process the socket and complete the response to the client

  • The main thread continues to accept the next request

The server side handles the pseudo-code as follows:

This is the classic one connection for one thread model. The main reason for using multithreading is that the three main functions of socket.accept(), socket.read() and socket.write() are blocked synchronously.

When a connection is processing I/O, the system blocks, which it would if it were single-threaded, but the CPU is freed up, and by enabling multithreading, the CPU can do more.

This is the essence of all multithreading:

Using multiple cores, you can use multiple threads to use CPU resources when I/O is blocked but the CPU is idle.

When faced with hundreds of thousands or even millions of connections, the traditional BIO model is powerless.

With the rise of mobile applications and the popularity of various online games, millions of long connections are becoming more common. At this time, a more efficient I/O processing model is necessary.

NIO

JDK1.4 began with the introduction of the NIO class library, primarily implemented using the Selector multiplexer.

Selector is implemented by IO multiplexing Epoll on mainstream operating systems such as Linux.

NIO implementation process, similar to Select:

  • Create a ServerSocketChannel listening client connection and bind the listening port, setting it to non-blocking mode

  • Create a Reactor thread, create a Selector, and start the thread

  • Registers the ServerSocketChannel with the Reactor thread’s Selector and listens for Accept events

  • Selector loops wirelessly through the thread’s run method to poll for ready keys

  • Selector listens for new client access, processes new requests, completes TCP three-way handshake, and establishes a physical connection

  • Registers a new client connection with a Selector, listens for reads, and reads network messages sent by the client

  • When the data sent by the client is ready, the client request is read and processed

The simple processing model uses a single-threaded infinite loop to Select ready events that execute system calls (Select and Poll before Linux 2.6, Epoll after 2.6, and IOCP for Windows) and block waiting for new events to arrive.

When a new event arrives, a bit is registered on the Selector to indicate that it is readable, writable, or connected. The pseudocode for the simple handling model looks like this:

NIO has changed from blocking reads and writes (threads tied up) to single-thread polling events that find network descriptors to read and write.

Except that polling for events is blocked (nothing to do must be blocked), the rest of the I/O operations are purely CPU operations and there is no need to enable multithreading.

In addition, due to thread saving, the problem of thread switching when the number of connections is large is also solved, thus making it possible to handle a large number of connections.

AIO

JDK1.7 introduces NIO2.0, which provides an implementation of asynchronous file channels and asynchronous socket channels.

  • The underlying layer is implemented by IOCP on Windows and simulated by IO multiplexing Epoll on Linux.

In the JAVA NIO framework, Selector is responsible for polling IO events in the operating system instead of all registered channels in the application query, managing the set of currently registered channels, and locating the channel where the event occurred.

In the JAVA AIO framework, however, because the application is subscription-notification rather than polling, the Selector is no longer needed, and instead a Channel registers listeners directly to the operating system.

In the JAVA AIO framework, only two network IO channels are implemented:

  • AsynchronousServerSocketChannel (server listening passage)

  • AsynchronousSocketChannel (Socket Socket channel).

The specific process is as follows:

  • Create AsynchronousServerSocketChannel, binding listener port

  • Call AsynchronousServerSocketChannel accpet method, was introduced into its own CompletionHandler, including the previous step, are blocked

  • Incoming connections, callback CompletionHandler completed method, in it, call AsynchronousSocketChannel read method, the incoming CompletionHandler responsible for dealing with data

  • When the data is ready, the Completed method of the CompletionHandler responsible for processing the data is triggered and you proceed to the next step

  • The write operation is similar, passing in the CompletionHandler

The last

Feel there is harvest, hope to help like, forward ha, thank you, thank you

Month with flying fish, make a friend

The public account responds to 666 backstage, you can get free electronic books

This article uses the article synchronization assistant to synchronize