IO model refers to what channels are used to send and receive data during network data transmission. Common ones are BIO, NIO and AIO(NIO2.0). I will introduce these in detail next
What exactly does synchronous/asynchronous/blocking/non-blocking mean?
-
Synchronous/asynchronous is when you call a method, and if the method is synchronous, you wait for it to finish before you can do anything else. If it’s asynchronous, it’ll give you back immediately, but it’s not a real result, it’s a real result that’s being notified by a message mechanism or a callback mechanism.
-
Blocking/non-blocking Blocking is when you call a method to get information about a washing machine. If there is no washing machine, the method will block until it can find information about a washing machine. Non-blocking means that when you call a method to get the washing machine information, if you don’t get the information at the time, you don’t block it all the time, you can do something else, but you check every now and then, and then block it again, but that doesn’t stop you from doing anything else.
BIO(Synchronous blocking)
We use BIO a lot. You probably learned about socket communication when we learned about programming basics javaSE, which uses synchronous blocking. Let’s take a look at the BIO model:
In the BIO model, each connection is processed by one thread. If the server uses a single thread to process, the connection will be blocked.
- Disadvantages:
- The read operation in this code blocks. If the server does not send data after the connection, it will block the current thread, wasting resources.
- If the number of connections is high, it means that more and more threads will be created, which will strain the server and will be optimized for thread pool handling, which will solve the problem.
- Application Scenario BIO applies to the architecture with a small number of fixed connections. This mode has high requirements on server resources but low program complexity.
Code sample
// Client package com.example.netty.bio; import java.io.IOException; import java.net.Socket; public class SocketClient { public static void main(String[] args) throws IOException { Socket socket = new The Socket (" 127.0.0.1 ", 9000); Socket.getoutputstream ().write(" I am the client ".getBytes()); socket.getOutputStream().flush(); System.out.println(" Sending data to server ends "); byte[] bytes = new byte[1024]; try { int read = socket.getInputStream().read(bytes); Println (" bytes,0,read) +new String(bytes,0,read); } catch (IOException e) { e.printStackTrace(); }finally {}}} // server package com.example.netty.bio; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class SocketServer{ public static void main(String[] args) throws IOException { ServerSocket serverSocket=new ServerSocket(9000); While (true){system.out.println (" waiting for connection "); // Block waiting Socket client = serversocket.accept (); System.out.println(" client connected "); handleRead(client); } } /** * * @param client */ private static void handleRead(Socket client) { new Thread(new Runnable() { @Override public void run() { byte[] bytes = new byte[1024]; try { int read = client.getInputStream().read(bytes); Println (" new String(bytes,0,read)); system.out. println(" new String(bytes,0,read)); // Thread.sleep(Integer.MAX_VALUE); Client.getoutputstream ().write(" Hello, I received your data ".getBytes()); client.getOutputStream().flush(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } finally { } } }).start(); }}Copy the code
NIO(Synchronous non-blocking)
NIO is an upgrade from THE BIO to replace blocking with non-blocking, although the mode is changed, but the code is much more complex. In the NIO model, a server can open a thread to handle multiple connections. It is non-blocking, and all data sent by the client is registered with the multiplexer selector. When a selector (whose select method is blocked) is polled for a read, write, or connect request, When there is no data, the business program does not need to block and wait.
NIO has three major components: channels, buffers, and selectors.
- A Channel is similar to a stream, but it is a two-way stream. It is a Channel that connects the server to the client. Both the client and the server can use the Channel to read and write data.
- A Buffer is a Buffer used to store data that is passed between clients and servers through channels.
- A Selector corresponds to one or more threads, and the client connection is registered with the Selector, which then invokes the backend handler
NIO structure model
Where exactly is NIO non-blocking?
As you probably know from the model diagram, all of the client’s connection channels are registered with selector, and the select polls to get the states of those channels, such as ACCPET and READ.
If a connection request status is found in the polling process, it indicates that there is already a client to connect to the server, directly pass this channel to the back-end program to process the connection operation; In the BIO model, it blocks on Accept until a connection request is made to continue with the rest of the code.
If a read request status is found in the polling process, it indicates that a client has sent data to the server, the server can directly give the channel to the back-end program for read operation processing; In the BIO model, read is blocked until a connection request is made to proceed with the subsequent code.
The above can be summarized as: In the NIO model, if the server performs a read operation, it indicates that there is available data to read. If the server performs an ACCPET operation, it indicates that a client must initiate a connection with the server. This operation can be guaranteed only when the selector polls the state of a readable and connectable channel.
How does NIO handle a selector poll that yields read and ACCPET states for multiple channels?
- Single thread
If under the single thread model, the treatment is according to the change resulting from the polling channel order processing, yes, it is synchronous processing, also is that it can finish this channel operation, can continue to process the next channel’s request, in the selector code, is through the traverse all have change channels for processing, And if you look at the code, you’ll see that this one selector for one thread pattern is actually redis’s single-threaded IO model.
- multithreading
If you’re in multithreaded mode, when you’re going through each channel, the selector is going to hand off the operation on the channel to a thread, and it’s going to be thrown to the thread, and the order of execution is going to depend on the CPU scheduling.
Let’s take a look at how NIO works in combination with the code
Code sample
- Server code
package com.example.netty.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; public class NioServer { public static void main(String[] args) throws IOException { ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.socket().bind(new InetSocketAddress(9000)); Selector selector = Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); While (true) {system.out.println (" wait for the event to happen "); Int select = selector. Select (); Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); iterator.remove(); handleChannel(selectionKey); } } } private static void handleChannel(SelectionKey selectionKey) { if (selectionKey.isAcceptable()) { System.out.println(" a client has connected "); ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel(); try { SocketChannel client = channel.accept(); client.configureBlocking(false); Register (selectionkey.selector (), selectionkey.op_read); } catch (IOException e) { e.printStackTrace(); } else if (selectionKey.isreadable ()) {system.out.println (" received the request to send data from the client "); SocketChannel channel = (SocketChannel) selectionKey.channel(); ByteBuffer allocate = ByteBuffer. Allocate (1024); ByteBuffer allocate = ByteBuffer. Allocate (1024); try { int read = channel.read(allocate); if (read ! Println (" allocate. Array (), 0, read) + new String(allocate. Array (), 0, read)); } channel.write(bytebuffer.wrap (" Hello, I am the server ".getBytes())); // If the following line is released, there will always be writable operations, because 99.999% of the time in the network is writable, Don't usually listen / / selectionKey. InterestOps (selectionKey. OP_WRITE | selectionKey. OP_READ); selectionKey.interestOps(SelectionKey.OP_READ); } catch (IOException e) { e.printStackTrace(); } else if (selectionKey.isWritable()) {system.out.println (" write event "); }}}Copy the code
- Server architecture diagram
-
Server side code description
- Create a ServerSocketChannel and Selector, and register the ServerSocketChannel with the Selector
- The Selector listens for a channel event through the Select () method. When the client connects, the Selector listens for the connection event and gets the selectionKey bound to the ServerSocketChannel registration
- SelectionKey gets the bound ServerSocketChannel through the channel() method
- ServerSocketChannel A SocketChannel is obtained by the Accept () method
- Register SocketChannel with Selector, care about read event
- After registration, a SelectionKey is returned, which is associated with the SocketChannel
- The Selector continues to listen for events through the Select () method. When the client sends data to the server, the Selector listens for the read event, retrieving the selectionKey bound to the SocketChannel registry
- SelectionKey gets the bound socketChannel through the channel() method
- Read data from socketChannel
- Write server data back to the client using socketChannel
You may have noticed that I mentioned horizontal triggering in the code comments, but let me explain: horizontal triggering is a pattern in multiplexing, and there is also an edge triggering.
-
Level trigger
If detected data in the channel change triggers after notice, if after receiving the notification, event handlers are not fully read all the data in the buffer or no reading at all, so in a horizontal trigger mode, will trigger this notice, until the contents of the buffer is read out, select and poll in NIO belongs to this pattern
-
Edge trigger
The same is true except that when the system notifies you once, it notifies you again only when the data in the channel changes again. Epoll can be triggered either horizontally or edge.
-
Client code
package com.example.netty.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; public class NioClient { private Selector selector; public static void main(String[] args) throws IOException { NioClient client = new NioClient(); Client. InitClient (" 127.0.0.1 ", 9000); client.connect(); } private void connect() throws IOException {while (true) {// block waiting for selector. Select (); Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); iterator.remove(); handler(selectionKey); }} private void handler(SelectionKey) throws IOException {// If (selectionKey.isreadable ()) { SocketChannel channel = (SocketChannel) selectionKey.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int len = channel.read(buffer); if (len ! Println (" new String(buffer.array(), 0, len)); }} else if the incident / / connection (selectionKey. IsConnectable ()) {SocketChannel channel = (SocketChannel) selectionKey. Channel (); IsConnectionPending is used to determine whether the connection is completed. If the connection is pending, call finishConnect. If you can't connect will throw an exception if (channel. IsConnectionPending ()) {channel. FinishConnect (); } channel.configureBlocking(false); ByteBuffer = bytebuffer.wrap (" Hello, THIS is the client ".getBytes()); channel.write(buffer); selectionKey.interestOps(SelectionKey.OP_READ); } } private void initClient(String s, int i) throws IOException { SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); selector = Selector.open(); socketChannel.connect(new InetSocketAddress(s, i)); socketChannel.register(selector, SelectionKey.OP_CONNECT); }}Copy the code
Common underlying implementation apis for multiplexing
Poll has no maximum connection limitation compared with SelelCT; Compared with the previous two, epoll is a different mechanism. It notifies the caller based on the way of event notification.
AIO(asynchronous non-blocking)
Asynchronous non-blocking: the operating system calls back to notify the server program to start the thread to process the asynchronous non-blocking. This mode applies to applications with a large number of connections and a long connection time. Application scenarios: AIO mode applies to architectures with a large number of connections and a long connection (reoperation), and is supported in JDK7
- AIO code examples:
// Server code package com.example.netty.aio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousServerSocketChannel; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; public class AioServer { public static void main(String[] args) throws IOException, InterruptedException { final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(9000)); serverChannel.accept(null,new CompletionHandler<AsynchronousSocketChannel, Object>() { @Override public void completed(AsynchronousSocketChannel socketChannel, Object attachment) { serverChannel.accept(attachment, this); try { 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) { buffer.flip(); System.out.println(new String(buffer.array(), 0, result)); socketChannel.write(ByteBuffer.wrap("HelloAioClient".getBytes())); } @Override public void failed(Throwable exc, ByteBuffer attachment) { } }); } catch (IOException e) { e.printStackTrace(); } } @Override public void failed(Throwable exc, Object attachment) { } }); Thread.sleep(Integer.MAX_VALUE); }} // Client code package com.example.netty.aio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.util.concurrent.ExecutionException; public class AioClient { public static void main(String[] args) throws ExecutionException, InterruptedException, IOException { AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open(); SocketChannel. Connect (new InetSocketAddress (9000) "127.0.0.1,"). The get (); socketChannel.write(ByteBuffer.wrap("HelloServer".getBytes())); ByteBuffer buffer = ByteBuffer.allocate(512); Integer len = socketChannel.read(buffer).get(); if (len! Println (" new String(buffer.array(), 0, len)); }}}Copy the code
Conclusion: Asynchronous non-blocking code is very simple, all operations are triggered by the callback mechanism, we only need to handle our own logic in the callback method, in fact AIO is based on NIO encapsulation, as will be explained later netTY is based on NIO encapsulation
BIO, NIO, AIO
There will be a large number of interview materials and architect must-see books waiting for you to choose, including Java foundation, Java concurrency, micro services, middleware and more information waiting for you to take oh.