preface

  • Before introducing NIO, we must take a look at the IO part of Java.
  • BIO (Blocking IO)
  • Blocking IO is implemented primarily in Java with serversocket.accept ().
  • NIO (Non-blocking IO)
  • Non-blocking IO is implemented in Java mainly through NIOSocketChannel + Seletor.
  • AIO (Asyc IO)
  • Asynchronous IO, currently do not do learning.

BIO

Simple implementation of the server and client

package net.io; import net.ByteUtil; import java.io.*; import java.net.ServerSocket; import java.net.Socket; //NIO (NonBlocking IO) // Use an event listener to store the connection to the client. Public class Server {public Server(int port) {try {// create Server, Port ServerSocket ServerSocket = new ServerSocket(port); ----- block method while (true) {// Listen, block method socket socket = serversocket.accept (); System.out.println(" client "+ socket.getinetAddress ())); InputStream inputStream = socket.getInputStream(); ObjectInputStream ois = new ObjectInputStream(inputStream); Get = ois.readObject(); System.out.println(" received message: "+ get); OutputStream = socket.getOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(outputStream); String message = "Hello client, I am server "; // The knowledge is written to the output stream buffer oos. WriteObject (message); // Send and flush oos. Flush (); } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { new Server(7000); }}Copy the code
package net.io; import net.ByteUtil; import java.io.*; import java.net.Socket; public class Client { public Client(int port){ try { Socket socket = new Socket("localhost",port); OutputStream = socket.getoutputstream (); //inputStream is an inputStream that receives information from the outside. ObjectOutputStream oos = new ObjectOutputStream(outputStream); // String message = "Hello server, I am a client "; // The knowledge is written to the output stream buffer oos. WriteObject (message); // Send and flush oos. Flush (); InputStream InputStream = socket.getinputStream (); ObjectInputStream ois = new ObjectInputStream(inputStream); Object get = ois.readObject(); System.out.println(" receive message: "+ get); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { new Client(7000); }}Copy the code

The Server calls accept() to listen for the Socket connection, so it blocks accept(). This means that the server can handle only one socket at a time. Therefore, we can increase the number of connections handled by the server by creating a thread for socket connection execution. However, the new problem also comes out, if the client connection is too many, then it will cause the server to create a lot of threads to handle the socket, if the server continues to open the connection, then the corresponding threads will not be destroyed, such a large number of threads maintenance is very resource consumption. NIO for Java was designed to address this situation.

NIO

First we need to introduce NIO. While BIO reads and writes to sockets, NIO reads and writes to channels (or buffers). Here we need to talk about the buffer before we talk about NIO. We know that this is a buffer, and we know that sockets have inputStream and outputStream, but our channel has both read and write methods. Since both methods operate on buffers (a channel is only better than a normal input-output stream, i.e. a channel is a two-way stream and input-output streams are two one-way streams), we can see the importance of buffers.

Buffer

Abstract classes such as ByteBuffer and IntBuffer are derived abstract classes of Buffer. Static allocate(X capacity) method is invoked to initialize the size of Buffer, or wrap(X X) method is used to initialize the size of Buffer. This method is equivalent to storing information directly into a buffer. As for the put() method for storing buffer and the get() method for retrieving cache, I will describe it in detail in the following code. The most important method is the flip() method, which acts as a read/write switch. It makes the cache read/write. It also isolates read and write from each other (it is important to note that the buffer should be written in sequence and read again, and then reset by calling clear() at last, otherwise the buffer capacity will become smaller and smaller, as explained in the code below).

package net.nio.buffer; import java.nio.IntBuffer; Public class TestBuffer {public static void main(String[] args) {/* *IntBuffer has four important arguments * 1.mark * 2.position Equivalent to the current subscript, index * 3. Limit represents the end of the buffer and cannot be read beyond this subscript and of course cannot exceed the maximum capacity. * 4.Capacity Specifies the maximum Capacity of the IntBuffer object, as defined when the IntBuffer object is initialized. Cannot change (IntBuffer. Allocate (int capacity)) * * CTRL + H can view subclasses of this class */ / IntBuffer IntBuffer = IntBuffer.allocate(5); Intbuffer. put(10); intBuffer.put(11); intBuffer.put(12); // intBuffer.put(13); // intBuffer.put(14); /* * Here is the implementation mechanism of read/write inversion: * For example, if we have a buffer size of 5, we call put() to write data to the buffer. Public Buffer flip() {* limit = capacity * Public Buffer flip() {* limit = capacity * Public Buffer flip() {* limit = position; * position = 0; * mark = -1; * return this; *} * * When we call get(), we get position from 0. Stop until position = limit = 3 is read (excluding 3) * 1. If we put() again to the buffer without calling flip() at this point (that is, without switching from the read state to the write state), we would report an error over overflow * 2. If we call flip() once and write one data, then position = 0 and limit = 3, then we can store at most three data, and if we don't call flip() again, we'll get the wrong data. * * If the size of the buffer gets smaller and smaller, the buffer gets smaller and smaller, so that it can't reach its maximum capacity, which can be a waste of memory. * The flip() method, however, sets the limit = position (assignment operation), so if there is less and less data, * will result in a smaller and smaller portion of the buffer. The size of the buffer should be set according to actual usage (and clear() should be called promptly), otherwise it may result in wasted memory for the buffer. */ intBuffer.flip(); While (intbuffer.hasRemaining ()) {system.out.println (intbuffer.get ()); }}}Copy the code

Channel

Channels are the foundation of NIO implementations, and for NIO, channels are the equivalent of sockets in the BIO. A Channel has many methods. The two most commonly used methods are write(ByteBuffer buf) and read(ByteBuffer buf). (Note that the read() method is written by a channel to a buffer and the write() method is written by a buffer to a channel.)

package net.nio.channel; import java.io.FileOutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; Public class TestChannel {public static void main(String[] args) throws Exception{String ABC = "I am writing to the file "; FileOutputStream FileOutputStream = new FileOutputStream("C:\\ XXX \ XXX \ XXX \test.txt"); / / to get from the output stream channel instance FileChannel channel = fileOutputStream. GetChannel (); ByteBuffer = ByteBuffer. Allocate (1024); Bytebuffer.put (abc.getBytes()); // Convert a string to a byte array and put it into a buffer. byteBuffer.flip(); // Write buffer data to channel (write from buffer, read from channel) channel.write(byteBuffer); // Close the channel and output stream channel.close(); fileOutputStream.close(); }}Copy the code

Simple NIO implementation

As I mentioned earlier in NIO, NIO requires a Selector thread to listen for any client implementations that occur to process, rather than a thread in BIO that maintains a socket.

Now for NIO we’re not going to introduce Selector, but we’re going to implement a client and a server in BIO. (as a trainee)

Server

package net.nio.socket; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Arrays; Public class Server {public static void main(String[] args) throws Exception {// Start niO Server, ServerSocketChannel ServerSocketChannel = ServerSocketChannel.open(); InetSocketAddress inetSocketAddress = new InetSocketAddress(8000); serverSocketChannel.bind(inetSocketAddress); ByteBuffer = ByteBuffer. Allocate (40); / / the server receives the request from the client to create instances of client socket SocketChannel SocketChannel = serverSocketChannel. The accept (); Socketchannel.read (byteBuffer); socketChannel.read(byteBuffer); byte[] array = byteBuffer.array(); String msg = new String(array); System.out.println(" server received message: "+ MSG); Bytebuffer.flip (); // Reverse the read state to the write state of the buffer array. Bytebuffer.put ("ok".getBytes()); // Echo the data to the client. Clear bytebuffer.clear (); clear bytebuffer.clear (); }}Copy the code

Client

package net.nio.serverclient; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public class NIOClient { public static void main(String[] args) throws Exception { SocketChannel socketChannel = SocketChannel.open(); InetSocketAddress inetSocketAddress = new InetSocketAddress("localhost",8000); socketChannel.configureBlocking(false); // If (! Socketchannel.connect (inetSocketAddress)) {system.out.println (" Client cannot connect to server... ); // If the client does not complete the connection while (! SocketChannel. FinishConnect ()) {System. Out. Println (" connection..." ); String message = "Hello, Server!" ; ByteBuffer byteBuffer = ByteBuffer.wrap(message.getBytes()); Socketchannel.write (byteBuffer); System.out.println(" send finished "); }}Copy the code

NIO implementation (based on Selector)

So the first thing we need to know is what is Selector? A Selector is a Selector, and since it’s a Selector, there must be an option before there is a selection, and if you understand this, you know that a channel must have the rigister() method (because you need to register yourself in a Selector).

Now that the options are there, how do you choose them? Selector deals with registered channels that have events (such as server: receive, read/write, client: read/write, the server registers itself at the start of the connection, and the client registers it at the end of the connection).

Instead of registering a simple channel, a Selector encapsulates the channel and its listener events into a SelectionKey stored in the Selector’s underlying Set.

The keys() method returns all selectionkeys that have been registered. The selectedKeys() method returns the selectionKey for which an event occurred.

So here’s a simple Selector workflow, and I’m going to attach the code, because it’s annotated in detail, so I’m not going to go into anything more than the important points. Server

package net.nio.serverclient; 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; import java.util.Set; Public class NIOServer {public static void main(String[] args) throws Exception {// Enable ServerSocketChannel listening ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // Bind port 8000 serversocketChannel. bind(new InetSocketAddress(8000)); Selector = Selector. Open (); / / set to monitor for non-blocking serverSocketChannel. ConfigureBlocking (false); / / will ServerSocketChannel registered to the Selector (registered event is ACCEPT) ServerSocketChannel. In the register (the Selector, SelectionKey. OP_ACCEPT); //Selector listens for ACCEPT while (true) {// No event occurred. If (selector. Select (1000) == 0) {system.out.println (" Server waited 1S, no event occurred... ); continue; Set<SelectionKey> selectionKeys = selectedKeys(); selectionKeys = selectionKeys (); Iterator<SelectionKey> iterator = selectionKeys.iterator(); While (iterator.hasnext ()) {SelectionKey = iterator.next(); Iterator.remove (); // Remove the read event iterator.remove(); / / according to the corresponding event corresponding to deal with the if (selectionKey. IsAcceptable ()) {/ / a new client connection server SocketChannel SocketChannel = serverSocketChannel.accept(); / / to the client set non-blocking socketChannel. ConfigureBlocking (false); Register (selector, selectionkey. OP_READ, bytebuffer.allocate (1024)); } if (selectionkey.isreadable ()) {SocketChannel SocketChannel = (SocketChannel) selectionKey.channel(); ByteBuffer = (ByteBuffer) selectionKey.attachment(); socketChannel.read(byteBuffer); // Reset buffer bytebuffer.clear (); String message = new String(byteBuffer.array()); System.out.println(" received message from client: "+ message); } } } } }Copy the code

Client

package net.nio.serverclient; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.Scanner; public class NIOClient { public static void main(String[] args) throws Exception { SocketChannel socketChannel = SocketChannel.open(); InetSocketAddress inetSocketAddress = new InetSocketAddress("localhost",8000); socketChannel.configureBlocking(false); // If (! Socketchannel.connect (inetSocketAddress)) {system.out.println (" Client cannot connect to server... ); // If the client does not complete the connection while (! SocketChannel. FinishConnect ()) {System. Out. Println (" connection..." ); While (true) {Scanner Scanner = new Scanner(system.in); String message = scanner.nextLine(); ByteBuffer byteBuffer = ByteBuffer.wrap(message.getBytes()); Socketchannel.write (byteBuffer); System.out.println(" send finished "); }}}Copy the code

We can see here that we only need an infinite loop in the main function, which listens for the selector registered channel (select() method), and then handles the event according to the channel registered listening event.

The important thing to note here is that the ServerSocketChannel and SocketChannel need to be programmed unblocking (call configureBlocking(false)), otherwise they will not be registered in the Selector.

One other thing to note is that we are iterating through the Set at the time of occurrence each time. To avoid repeating the processing time, we remove() the selctionKey at the time of occurrence after we get it.

The last

Thank you for reading here, after reading what do not understand, you can ask me in the comments section, if you think the article is helpful to you, remember to give me a thumbs up, every day we will share Java related technical articles or industry information, welcome to pay attention to and forward the article!