preface
This article focuses on the IO mechanism in Java and NIO for handling high concurrency in network communications
The second part explains how to optimize the WASTE of CPU resources under the IO mechanism (New IO).
The Echo server
I don’t need to introduce the socket mechanism under single thread, if you don’t know, you can look up the information. So what about using sockets under multiple threads? We use the simplest echo server to help you understand
First, take a look at the flow chart of server and client in multi-threading:
As you can see, multiple clients are sending requests to the server at the same time. What the server does is start multiple threads to match the corresponding client and each thread completes its own client request
Here I’ve written a simple server that uses thread pooling to create threads (I’ve annotated the code) :
public class MyServer { private static ExecutorService executorService = Executors.newCachedThreadPool(); Private static class HandleMsg implements Runnable{// Once a new client requests it, the thread is created to process the Socket client; Public HandleMsg(Socket Client){this.client = client; } @Override public void run() { BufferedReader bufferedReader = null; // create character cache input stream PrintWriter PrintWriter = null; BufferedReader = new bufferedReader (new InputStreamReader(client.getinputStream ()))); PrintWriter = new printWriter (client.getOutputStream(),true); String inputLine = null; long a = System.currentTimeMillis(); while ((inputLine = bufferedReader.readLine())! =null){ printWriter.println(inputLine); } long b = System.currentTimeMillis(); System.out.println(" This thread takes: "+(b-a)+" seconds! ); } catch (IOException e) { e.printStackTrace(); }finally { try { bufferedReader.close(); printWriter.close(); client.close(); } catch (IOException e) { e.printStackTrace(); }}}} public static void main(String[] args) throws IOException {// The main thread on the server is used to loop on client requests. ServerSocket Server = new ServerSocket(8686); // Create a server with port 8686 Socket client = null; While (true){// loop on client = server.accept(); / / the server listens to a client request System. Out.println (client) getRemoteSocketAddress () + "address client connection success!" ); executorService.submit(new HandleMsg(client)); // Put the client request into the HandlMsg thread through the thread pool}}}Copy the code
In the code above we used a class to write a simple Echo server that uses an infinite loop on the main thread to enable port listening
Simple client
With a server, we can access it and send some string data and the function of the server is to return those strings and print out how long the thread took
Let’s write a simple client to respond to the server:
public class MyClient { public static void main(String[] args) throws IOException { Socket client = null; PrintWriter printWriter = null; BufferedReader bufferedReader = null; try { client = new Socket(); client.connect(new InetSocketAddress("localhost",8686)); printWriter = new PrintWriter(client.getOutputStream(),true); printWriter.println("hello"); printWriter.flush(); bufferedReader = new BufferedReader(new InputStreamReader(client.getInputStream())); System.out.println(" The message from the server is: "+ bufferedreader.readline ()); } catch (IOException e) { e.printStackTrace(); }finally { printWriter.close(); bufferedReader.close(); client.close(); }}}Copy the code
In this code, we send a Hello string in a character stream, and if the code is okay the server will return a Hello and print out the log message that we set up
Echo server results are displayed
1. Open the server to enable loop listening:
2. Open a client:
You can see that the client prints out the return result
3. View server logs.
Good, a simple multithreaded socket programming
But consider this: If you have a client request, and you put Sleep in between the I/O writing to the server, so that each request takes 10 seconds on the server thread and then you have a lot of client requests, and each request takes that long then the capability of the server is going to go down a lot and it’s not because the server has a lot of work to do, Just because the server thread is waiting for IO (because Accept, read, and write are blocking), it is uneconomical to have a fast RUNNING CPU waiting for extremely inefficient network IO
What should I do?
NIO
New IO successfully solves these problems. How does it solve them? The smallest unit for IO to process a client request is a thread, while NIO uses a unit even smaller than that: channels. NIO requires only one thread to complete all the receiving, reading, and writing operations
To learn NIO, you need to understand its three core selectors, Buffer, Buffer Channel, and Channel
The blogger took it upon himself to draw an ugly picture to impress everyone
Here is a NIO workflow flow chart of TCP.
So you get the general idea, but let’s do it step by step
Buffer
The first thing you need to know is what a Buffer is and in NIO data interaction is no longer a stream like IO but a Buffer
Bloggers think images are the easiest to understand, so…
You can see where buffers are in the workflow
To get real, the code in the diagram above is as follows:
1. Allocate space in bytes to Buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);Copy the code
Create a ByteBuffer object and specify the memory size
2. Write data to Buffer
1). Data from Channel to Buffer: channel.read(byteBuffer); 2). Data from Client to Buffer: bytebuffer.put (...) ;Copy the code
3. Read data from Buffer:
1). Data from Buffer to Channel: channel.write(byteBuffer); 2). Data from Buffer to Server: bytebuffer.get (...) ;Copy the code
Selector
The selector is at the heart of NIO. It is the channel manager that listens to see if any channels are ready by performing the select() blocking method. Once there is data to read, this method returns the number of selectionkeys
So the server usually executes the select() method in an infinite loop until a channl is ready, and then it starts working. Each channel binds an event to the Selector, and then generates an object called SelectionKey. When a channel is bound to a Selector, a channel must be in non-blocking mode and a FileChannel cannot switch to non-blocking mode, because it’s not a socket channel, so a FileChannel cannot bind an event to a Selector
OP_ACCEPT: SelectionKey.OP_ACCEPT: SelectionKey.OP_READ: SelectionKey. Read event 4. selectionkey. OP_WRITE: write event
Channel
There are four channels: FileChannel: applies to IO file streams DatagramChannel: applies to UDP SocketChannel: Applies to TCP ServerSocketChannel: applies to TCP
This article explains NIO through the common TCP protocol
Let’s take ServerSocketChannel as an example:
Open a ServerSocketChannel channel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();Copy the code
Disable the ServerSocketChannel channel:
serverSocketChannel.close();Copy the code
Loop on SocketChannel:
while(true){
SocketChannel socketChannel = serverSocketChannel.accept();
clientChannel.configureBlocking(false);
}Copy the code
clientChannel.configureBlocking(false); The statement sets this channel to non-blocking, that is, asynchronous free control blocking or non-blocking is one of NIO’s features
SelectionKey
SelectionKey is the core component of channel and Selector interaction such as binding a Selector to a SocketChannel and registering it as a connection event:
SocketChannel clientChannel = SocketChannel.open();
clientChannel.configureBlocking(false);
clientChannel.connect(new InetSocketAddress(port));
clientChannel.register(selector, SelectionKey.OP_CONNECT);Copy the code
The core is the register() method, which returns a SelectionKey object to detect which channel events are available using the following methods:
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();Copy the code
These methods are used by the server to perform corresponding operations in polling
And of course the keys that are bound to the Selector via the Channel can also get them in reverse
Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();Copy the code
When registering events on a Channel, we can also optionally bind a Buffer:
clientChannel.register(key.selector(), SelectionKey.OP_READ,ByteBuffer.allocateDirect(1024));Copy the code
Or bind an Object:
selectionKey.attach(Object);
Object anthorObj = selectionKey.attachment();Copy the code
TCP server for NIO
With that said, let’s take a look at the simplest and most core code (unelegant with so many comments, but easy to understand) :
package cn.blog.test.NioTest; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.nio.charset.Charset; import java.util.Iterator; import java.util.Set; public class MyNioServer { private Selector selector; Private final static int port = 8686; private final static int BUF_SIZE = 10240; Private void initServer() throws IOException {// Create a channel manager object selector this.selector= selector. Open (); // Create a channel object channel ServerSocketChannel channel = serverSocketChannel.open (); channel.configureBlocking(false); // Set the channel to non-blocking channel.socket().bind(new InetSocketAddress(port)); // Bind the channel manager to port 8686 and register the OP_ACCEPT event for the channel. When the event arrives, selector. Select () returns (a key), SelectionKey = channel.register(selector, selectionkey.op_accept); While (true){// poll selector. Select (); Set keys = selectedKeys(); Set keys = selectedKeys(); Iterator = keys.iterator(); Iterator = keys.iterator(); While (iterator.hasnext ()){SelectionKey key = (SelectionKey) iterator.next(); Iterator.remove (); iterator.remove(); If (key.isacceptable ()){if (key.isacceptable ()){// verify that the channel represented by the current key isAcceptable, If yes, receive doAccept(key); }else if (key.isReadable()){ doRead(key); }else if (key.isWritable() && key.isValid()){ doWrite(key); }else if (key.isConnectable()){system.out.println (" Connect successfully! ); } } } } public void doAccept(SelectionKey key) throws IOException { ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); System.out.println("ServerSocketChannel is listening in loop "); SocketChannel clientChannel = serverChannel.accept(); clientChannel.configureBlocking(false); clientChannel.register(key.selector(),SelectionKey.OP_READ); } public void doRead(SelectionKey key) throws IOException { SocketChannel clientChannel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE); long bytesRead = clientChannel.read(byteBuffer); while (bytesRead>0){ byteBuffer.flip(); byte[] data = byteBuffer.array(); String info = new String(data).trim(); System.out.println(" The message sent from the client is: "+info); byteBuffer.clear(); bytesRead = clientChannel.read(byteBuffer); } if (bytesRead==-1){ clientChannel.close(); } } public void doWrite(SelectionKey key) throws IOException { ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE); byteBuffer.flip(); SocketChannel clientChannel = (SocketChannel) key.channel(); while (byteBuffer.hasRemaining()){ clientChannel.write(byteBuffer); } byteBuffer.compact(); } public static void main(String[] args) throws IOException { MyNioServer myNioServer = new MyNioServer(); myNioServer.initServer(); }}Copy the code
I printed the monitor channel to tell you when ServerSocketChannel was started. If you work with the DEBUG of the NIO client, you can clearly see that before entering the SELECT () poll, you already have the ACCEPT KEY. By default, select() does not call the ACCEPT SelectionKey until some other event of interest has been captured by Select () and the ServerSocketChannel loop starts listening
So in a Selector, ServerSocketChannel is always running and serverChannel.accept(); Truly asynchronous (channel in initServer method. ConfigureBlocking (false);) If a SocketChannel successfully connects, the SocketChannel registers a write (READ) event and sets it to asynchronous
TCP client for NIO
If there is a server there must be a client and in fact if you understand the server completely the client code is pretty much the same
package cn.blog.test.NioTest; 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 MyNioClient { private Selector selector; Private final static int port = 8686; private final static int BUF_SIZE = 10240; private static ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE); private void initClient() throws IOException { this.selector = Selector.open(); SocketChannel clientChannel = SocketChannel.open(); clientChannel.configureBlocking(false); clientChannel.connect(new InetSocketAddress(port)); clientChannel.register(selector, SelectionKey.OP_CONNECT); while (true){ selector.select(); Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()){ SelectionKey key = iterator.next(); iterator.remove(); if (key.isConnectable()){ doConnect(key); }else if (key.isReadable()){ doRead(key); } } } } public void doConnect(SelectionKey key) throws IOException { SocketChannel clientChannel = (SocketChannel) key.channel(); if (clientChannel.isConnectionPending()){ clientChannel.finishConnect(); } clientChannel.configureBlocking(false); String info = "Hello server!!" ; byteBuffer.clear(); byteBuffer.put(info.getBytes("UTF-8")); byteBuffer.flip(); clientChannel.write(byteBuffer); //clientChannel.register(key.selector(),SelectionKey.OP_READ); clientChannel.close(); } public void doRead(SelectionKey key) throws IOException { SocketChannel clientChannel = (SocketChannel) key.channel(); clientChannel.read(byteBuffer); byte[] data = byteBuffer.array(); String msg = new String(data).trim(); System.out.println(" server send message: "+ MSG); clientChannel.close(); key.selector().close(); } public static void main(String[] args) throws IOException { MyNioClient myNioClient = new MyNioClient(); myNioClient.initClient(); }}Copy the code
The output
Here I open one server and two clients:
Next, you can try opening a thousand clients at the same time, as long as your CPU is strong enough, the server will not be blocked to degrade performance
That’s the basics of Java NIO