Java's IO model and the underlying principle of IO multiplexing (select, poll, epoll)

insert image description hereAlthough there are many io models, there are three for java, namely BIO, NIO, and AIO. It is the encapsulation of various IO models of the operating system by the java language. First understand what is synchronous and asynchronous, blocking and non-blocking.

Synchronization: When the caller calls the callee, the callee does not return any results and no feedback until the callee finishes processing the call.
Asynchronous: When the caller calls the callee, the callee will give the caller a feedback immediately, indicating that the request has been received, but the result will not be returned. At this time, the caller can do other things. After the callee finishes processing the request, the result will be returned to the caller through an event or callback mechanism.

The biggest difference between the two is that when asynchronous: the caller does not need to obtain the result before performing other operations, and the callee will return the result to the caller through the callback mechanism.

Blocking: When the caller initiates a request, while the caller is waiting for the callee to return the result, the current thread will be suspended and cannot perform other tasks, that is, it can only continue to execute when the conditions are ready.
Non-blocking: When the caller initiates a request, he does not need to wait for the result to be returned, and can do other things first.

That is to say: synchronous and asynchronous are relative to the callee. After the callee receives the request, according to whether the callee needs the caller to wait when processing the result, if the caller waits for the result of the callee, it is synchronous , if the caller does other things without waiting, it is asynchronous.

Blocking and non-blocking are relative to the caller. If the caller waits for the result to be returned, it is in a blocking state. If the caller does not wait for the result and executes other results after the caller makes a estrus request at this time, then it is non-blocking.
Take boiling water as an example. When using an ordinary kettle to boil water, the person who boils water (the caller) uses the kettle to boil water (the callee). If the person who boils the water waits for the water to boil before watching TV, it is synchronously blocked.

And boil the kettle, watch TV by yourself, and check whether the water is boiled from time to time, it is synchronous and non-blocking (because the callee can do other things at this time, it is non-blocking, and the ordinary kettle There is no reminder function, so it is synchronous).

If you buy a high-end kettle. An alarm will sound when the water boils. If the person who is boiling the water can watch TV directly at this time, and wait for the kettle to ring, he can judge that it is boiled (for the high-end kettle of the callee, when the call request is received, the water will start to boil, and after the water is boiled, it will be boiled. Give the caller a prompt by sound, so it is asynchronous) is asynchronous and non-blocking.

If you use a high-end kettle, the caller waits for the water to boil in front of the kettle, which is asynchronously blocked

1BIO blocking IO

insert image description hereBlocking io is a model of one request and one response. Only when the callee returns the result, the next request is processed. Therefore, when multiple requests are to be resolved, in order to improve efficiency, multiple threads are required to process them. Generally, pooling technology is used to solve BIO, such as FixThreadPool, which has a fixed thread pool size, to ensure limited resource control.

2 NIO synchronous non-blocking IO

Non-blocking means that relative to the caller, there is no need to wait for the result to be returned, and other things can be processed. The implementation mode of the server is that one thread processes multiple requests, and the requests sent by the client are all registered on the multiplexer, and then the multiplexer polls for io requests and processes them. For low-concurrency applications, use synchronous blocking io, use multiple threads to process multiple requests, and for high-load and high-concurrency applications, you should use NIO's non-blocking mode to develop.
There are three core components for NIO, namely channel, buffer and selector.
insert image description hereinsert image description here

Channels are similar to streams. Each channel corresponds to a buffer buffer, and each channel is registered to the selector. The selector determines which request is processed by the thread. Select can correspond to multiple threads or a single thread. NIO buffer and channel Can read and write.

Question: What is the difference between IO and NIO?
1 The io stream is blocking io, and NIO is non-blocking io
. Blocking is relative to the caller. When calling, it cannot do other things, it is blocked. Non-blocking means that after the caller initiates the request, he can make other requests without waiting for the result immediately, which directly improves the efficiency. For non-blocking io, a single thread can do other things while reading data from the channel to the buffer buffer. When the data is completely read into the buffer, the thread continues to process the data. The same is true for non-blocking writes. A thread requests to write some data to a channel, and the step needs to wait for it to be completely written, and the current thread can do other things. And io is blocking. It means that the thread will be blocked when reading and writing. Other things can only be done when it is fully read in.
2 IO is stream-oriented, and NIO is buffer-oriented.
In the stream-oriented io, data is directly written or read into the Stream object and NIO reads it into the Buffer for operation. In the NIO library, all data is processed by the buffer, and then directly read when reading and writing data. Write to the buffer. 3NIO is implemented through the buffer to
read and write through the channel.
Channels are bidirectional and can be read as well as written, while streams are unidirectional. Whether it is reading or writing, the channel interacts with the buffer. Because of the existence of the buffer, the channel can realize asynchronous reading and writing.
4NIO has a selector, which can use a single thread to process multiple channels.
Therefore, NIO is generally fast and avoids the problem of thread switching under multi-threading.
To sum up: NIO is suitable for scenarios with a large number of connections and relatively short connection events, such as chat servers and barrage systems.

3AIO asynchronous non-blocking

Asynchronous means that for the callee, after receiving the call request, it only replies with a call response, and then returns the result to the caller after completing the processing. After receiving the request, the operating system will notify the corresponding thread (caller) to perform subsequent operations. Therefore, it is suitable for scenarios with a long connection time and a large number of connections.

The introduction idea of ​​IO multiplexing

First of all, for the server, what if the client has multiple requests?
Solution 1, the most direct and simplest implementation is to create multiple threads to correspond to multiple requests and improve the processing speed of requests. However, in some specific scenarios, the system overhead will increase due to the context switching between multiple threads. ,long time.
Solution 2, use a single thread. If you use a single thread to process, you can avoid switching back and forth between multiple threads. For example, redis is single-threaded, and the bottom layer is a file event processor. It uses the io multiplexing model. A socket is used to monitor requests. When multiple sockets have requests, it is necessary to determine which sockets have requests, and to process the requests in the current socket one by one for the sockets that have requests, which involves io multi-channel The principle of reuse.

In the linux system, everything is a file, and each request will have a network connection. In the linux system, each network connection is identified by the file descriptor fd . If there are connections A, B, C, D, E, 5 connection sockets for connection requests.
insert image description hereAt this point, you can write simple and crude pseudocode

//1先获取到所有请求文件的文件描述符,记录下有哪些请求描述符
int[] fds=new int[5];
//2对所有的文件请求进行扫描,看看有没有请求
while(true){
    
    
	for(int i=0;i<5;i++){
    
    
		if(fds[i] 有请求数据){
    
    
			1读取到文件描述符,定位到具体的请求
			2处理请求
		}
	}
}

Therefore, based on the above ideas, there are several specific implementations such as select, poll, epoll, etc.

IO multiplexing under select mode

insert image description here
Above the red line is the processing of request descriptors, recording all file descriptors (not in order) into an array, and recording to the largest file descriptor, and then starting to execute the select function.

The last three of the five parameters (the largest file descriptor, the descriptor for reading the file, the descriptor for writing the file, the set of abnormal file descriptors, and the timeout time) generally have default values.

Among them, the read file descriptor set rset is specifically a bitmap bit array, 1024 bits, which records the status of the current bit, and the subscript is 1 for the position status of the file descriptor, indicating that the current file descriptor is sending a request. That is, this rset contains all file information. Details of executing the select function:
insert image description hereWhen executing select, the program will copy rset from the user state to the kernel state, and the kernel state will determine which file descriptors are active. If there are no active ones, they will always be blocked here. When there is an active request, that is, when there is data, the kernel will set the corresponding position in the bitmap of rset to 1. At this time, it will not be blocked and enter the following process of processing the request.
When processing a request, first traverse all sockets and judge the bitmap in the socket. Because the largest file descriptor max has been recorded, it is only necessary to read the array before max in the bitmap and return it to find out which The file descriptor makes a request, and then reads the corresponding data for processing

selectAnalysis of advantages and disadvantages

The advantage is that the rsetcopy is transferred to the kernel state, and the activity of the file descriptor is judged by the kernel state, which is more efficient than directly judging in the user state in the pseudo code.
Disadvantages:
1. The default size of the bitmap in rset is 1024, which is limited in size.
2 After one traversal, there may be other file descriptors to be modified next time, so in the next traversal, rest needs to be reset, all reset to 0. That is, rset cannot be reused. 3 User state rsetcopy to user
state There is also an overhead
4 even if the select returns, that is, the bit corresponding to the rest is set (it can be understood as a new request, at this time I just know that there is a request), but I don’t know which position or which positions are carried out Modification, so in the next step, it is necessary to traverse again to find the changed position and make the corresponding request.

Poll mode realizes io multiplexing

insert image description hereAt this time, compared with select, bitmap is not used to record the information of each fd, but a structure pollfd is customized. This structure has three attributes. The first fd represents the file descriptor, and events represents the event that is processed first. Whether to read or write, the last parameter revents indicates whether it is currently active. When the poll method is called, it is still a blocking function. If fd has no data, it will block. When there is data, you need to set the revents of pollfd with data in pollfds. Whether it is POLLIN, if it is, it means yes There is data, and then it will be processed below.
1 Compared with the 1024-bit limit of select, there is no limit to using the pollfd structure.
2 Compared with the non-reusable problem of bitmap in fdset when each event is monitored, poll only needs to modify the attributes in the structure.

IO multiplexing in epoll mode

insert image description here
1 First use the epoll_create function to create an epfd parameter for the most important epollwait method parameter below.
The epoll_create function opens up a whiteboard,
insert image description here

2 When the epoll_ctl function is executed, each fd and events will be recorded on the whiteboard, which is similar to the pollfd structure of poll. But do not store revents
insert image description here
3 Execute the epoll_wait function. At this time, the kernel mode and the user mode share epfd, and the kernel mode still judges which fd has an event. When there is no data, it will block. When there is data, the events event will be triggered, that is, event-driven instead of polling , it is necessary to **"set"** the fd with data. At this time, the setting is not a mark like the first two, but the mark is realized by rearranging, and the fd with data is placed in the front position to return. At this time, the returned value is different. The return value is the number m of triggering events. At this time, only the first m numbers in epfd need to be traversed. So the time complexity is 1 at this time.
Advantages: Compared with select, it solves the last two problems of select
1 does not need to copy, 2 the time complexity of traversing again is 1

Summarize

Select mode
fd_set uses an array to implement
1. fd_size has a limit of 1024 bitmap
fd【i】 = accept()
2. fdset cannot be reused, new fd comes in and is recreated
3. User mode and kernel mode copy generate overhead
4. O(n) Time-complexity polling
The result of a successful call is greater than 0, the result of an error is -1, and the result of a timeout is 0
with a timeout

	poll
基于结构体存储fd
struct pollfd{
	int fd;
	short events;
	short revents; //可重用
}
解决了select的1,2两点缺点

epoll solves select 1, 2, 3, 4 without polling, and the time complexity is O(1)
epoll_create creates a whiteboard to store fd_events and epoll_ctl
to register new descriptors with the kernel or change a certain file descriptor state. The registered descriptors will be maintained in a red-black tree in the kernel. epoll_wait
will add the I/O ready descriptors to a linked list for management through the callback function. The process can call epoll_wait() to get the event completion the descriptor

两种触发模式:
	LT:水平触发
		当 epoll_wait() 检测到描述符事件到达时,将此事件通知进程,进程可以不立即处理该事件,下次调用 epoll_wait() 会再次通知进程。是默认的一种模式,并且同时支持 Blocking 和 No-Blocking。
	ET:边缘触发
		和 LT 模式不同的是,通知之后进程必须立即处理事件。
		下次再调用 epoll_wait() 时不会再得到事件到达的通知。很大程度上减少了 epoll 事件被重复触发的次数,
		因此效率要比 LT 模式高。只支持 No-Blocking,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

Guess you like

Origin blog.csdn.net/m0_56184347/article/details/124361021