Since the beginning of the Internet, BIO and NIO have always been the topic of conversation, both in daily life and in interviews. So what exactly are they? Hopefully you will understand these concepts thoroughly after reading this article, which also uses Java code to implement an EXAMPLE of I/O multiplexing, and finally I/O principles.
What is IO?
First of all, we need to understand what is I/O. A network request and a disk read are BOTH I/O. Therefore, we can generally understand that data is read and written through media.
The following is an I/O diagram of a network read. Data goes from peripherals (network cards) to kernel space, to user space (JVM) and finally to the application. What are blocked and non-blocked?
Let’s use another call link diagram to illustrate where blocking is located. The first diagram explains BIO, or synchronous blocking IO, which was implemented in the earliest versions of Java IO. When the program is called to read IO, synchronization blocks the program until data is written to kernel space from the network card, and then to user space. The program can continue to run.NIO was introduced in Java 1.4, but NIO solved the problem of blocking from the network card to the kernel space. This means that an application sends a request to read the IO, and if the data has not been written from the network card to the kernel space, the return is not ready. This does not require the application to wait for the result to die. After writing to kernel space, the program continues to read data, which blocks the program, as shown below.About *IO if you still do not understand, you can see this article, explain or more vivid.”The first time I heard someone talk about NIO in terms of a relationship between a man and a woman, it was sordid, but easy to understand.”.
IO multiplexing
With basic IO understood, we begin to understand IO multiplexing. Let’s say we want to implement an online chat system, and we have 100 people chatting at the same time, and we don’t care how many paths there are, but we need to support 100 people at the same time, what do we do?
We need to request for each user to create a new thread, to support multiple simultaneous users at the same time, however the goose, as more and more users, you need to create the thread more and more, the frequent context switch and thread creation and destruction, the system performance influence is very big, the specific link can understand by the following figure.
So can we eliminate multithreading and use one thread to handle N IO requests, without context switching and thread creation and destruction, is the program faster? That’s the idea behind IO multiplexing.
First we literally, multiplexing is multiplexing, English translation of “reuse”, in fact, simple to understand is to use a single thread through the records to track (I/O) of each flow state to manage multiple I/O at the same time, in order to reduce the number of threads created and switching cost, and thus enhanced the capacity of the server. So that makes it a little bit easier to understand when you look at the picture.
Since using single thread to solve the problem of multiple I/o switching, must not be blocked, if blocked or need to read serial IO, blocking program, it’s meaningless to switch, so the NIO and fully equal to IO multiplexing, but the necessary condition to solve the multiplex “non-blocking”, this also answered the question of the title.
Own IO multiplexing
The above understanding of what is IO multiplexing themselves can be achieved, the following is I write in Java IO multiplexing code, code is just to express the meaning, there are problems can be discussed with each other.
public void start(a) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(6789));
serverSocketChannel.configureBlocking(false);
while (true) {
SocketChannel channel = serverSocketChannel.accept();
register(channel);
Set<SocketChannel> selectionKeys = selectedKeys();
if(selectionKeys.size() ! =0) {
for(SocketChannel socketChannel : selectionKeys) { handle(socketChannel); }}}}private void register(SocketChannel channel) {
if(channel ! =null) { publicKeys.add(channel); }}private Set<SocketChannel> publicKeys = new HashSet<>();
private Set<SocketChannel> selectedKeys(a) {
Set<SocketChannel> publicSelectedKeys = new HashSet<>();
for (SocketChannel fd : publicKeys) {
if((fd.validOps() & OP_READ) ! =0) { publicSelectedKeys.add(fd); }}return publicSelectedKeys;
}
private void handle(SocketChannel socketChannel) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
StringBuilder sb = new StringBuilder();
while (socketChannel.read(byteBuffer) > 0) {
byteBuffer.flip();
sb.append(StandardCharsets.UTF_8.decode(byteBuffer));
}
if (sb.length() > 0) {
System.out.println("The server receives a message:"+ sb.toString()); }}Copy the code
The logic is relatively simple, and a few key pieces of information are introduced directly.
- define
register
Method that is added directly to when a new connection comes inpublicKeys
Set, so there is a place to store all socket connections. - define
selectedKeys
Method to get allready
State socket, principle is used(fd.validOps() & OP_READ) ! = 0
Go through all of thepublicKeys
The socket inside determines whether it is ready to read, and then adds toselectedKeys
In the result set, becausefd.validOps()
This judgment is non-blocking, so you can continue running the program. - The outer layer
while (true)
Loop to get what can be readsocket
So as to achieve IO multiplexing logic.
Is it logical? Then write a super simple Client to test and verify.
public void start(a) throws IOException {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(6789));
socketChannel.configureBlocking(false);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String request = scanner.nextLine();
if(request ! =null && request.length() > 0) { socketChannel.write(StandardCharsets.UTF_8.encode(request)); }}}Copy the code
System implementation
Ok, so the question comes, their own implementation of IO multiplexing is not ok? Why does the Internet keep boasting about select, poll, epoll? Here we look at the select source you will understand.
int select(
int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);
Copy the code
Let’s go through these parameters very briefly
- NFDS: the maximum file descriptor in the monitored file descriptor set is increased by 1, which is simply understood as the number of files to be iterated over
- Readfds/writefds/exceptfds: read/write/exception events to monitor descriptor set.
- Timeout: indicates the timeout period for blocking
If you want to dig deeper into the source code, you can download it directly from www.kernel.org/ and find the fs/select.c file.
Back to the beginning, we learned from the above parameters that the system level implementation of select is actually a lower level implementation of the loop lookup we just wrote, but directly at the system level performance is higher.
So what is poll, epoll? Poll is also a system call function provided by the operating system. The main difference between poll and SELECT is that it removes the limit that select can listen on only 1024 file descriptors. Epoll is the last upgrade. The program calls epoll without the need for full pass, support change pass, and do not need to loop through, but through asynchronous IO events wake up the program.
So you can see that either the system or you write your own code is solving the problem of single-threaded multiplexing, which is what we call IO multiplexing.