1. Blocking and non blocking
- Traditional IO flows are blocking. That is, when a thread calls read() or write(), the thread is blocked until some data is read or written, and the thread cannot perform other tasks during this period. Therefore, when the network communication is completed for IO operation, the threads will block, so the server must provide each client with an independent thread for processing. When the server needs to process a large number of clients, the performance drops sharply.
- Java NIO is non blocking. When a thread reads and writes data from a channel, if no data is available, the thread can perform other tasks. Threads usually use the free time of non blocking IO to perform IO operations on other channels, so a single thread can manage multiple input and output channels. Therefore, NIO allows the server side to use one or a limited number of threads to handle all clients connected to the server side at the same time.
2. Selector
2.1 basic concepts
The selector is a multiplexer of the SelectableChannel object. The selector can monitor the IO status of multiple selectablechannels at the same time. That is to say, a single thread can manage multiple channels by using the selector. Selector is the core of non blocking io.
SelectableChannel is an abstract class, which can be reused by Selector. The implementation class is shown in the figure below
To be used with a selector, an instance of this class must first be registered via the register method. This method returns a new SelectionKey object representing the registration of the channel and selector.
Once the selector is registered, the channel remains registered until it is unregistered. This includes any resources that are unassigned to the channel through the selector.
2.2 application of selector
- Create Selector: create a Selector by calling the Selector.open() method.
- Register channel with selector: SelectableChannel.register(Selector sel, int ops)
When calling register(Selector sel, int ops) to register a selector for a channel, the listener event of the selector for the channel needs to be specified by the second parameter ops.
Event types that can be listened to (can be represented by four constants of SelectionKey):
- Read: selectionkey.op'read (1)
- Write: selectionkey.op'write (4)
- Connection: selectionkey.op'connect (8)
- Receive: selectionkey.op'accept (16)
If you listen to more than one event during registration, you can use the bitwise OR operator to connect
Common methods of Selector
Method | Description |
---|---|
Set keys() | All SelectionKey collections. Represents the Channel registered on the Selector |
selectedKeys() | The SelectionKey collection selected. Returns the selected key set for this Selector |
int select() | Monitor all registered channels. When there are IO operations to be processed among them, the method returns and adds the deserved SelectionKey to the selected SelectionKey collection. The method returns the number of these channels. |
int select(long timeout) | select() operation with timeout time can be set |
int selectNow() | Execute an immediate return select() operation, which does not block the thread |
Selector wakeup() | Make an unreturned select() method return immediately |
void close() | Close the selector |
2.3 SelectionKey
SelectionKey: indicates the registration relationship between SelectableChannel and Selector. Each time a channel is registered with a Selector, an event (selection key) is selected. The selection key contains two sets of operations represented as integer values. Each bit of the operation set represents a type of optional operation supported by the channel of the key
Do.
Method | Description |
---|---|
int interestOps() | Get collection of events of interest |
int readyOps() | Gets the collection of operations that the channel is ready for |
SelectableChannel channel() | Get registration channel |
Selector selector() | Return selector |
boolean isReadable() | Check whether the read event in Channal is ready |
boolean isWritable() | Check whether write events in Channal are ready |
boolean isConnectable() | Check whether the connection is ready in the Channel |
boolean isAcceptable() | Check whether reception is ready in Channel |
3 SocketChannel and ServerSocketChannel
SocketChannel in Java NIO is a channel that connects to a TCP network socket.
Operation steps:
- Open SocketChannel
- Read and write data
- Close SocketChannel
The ServerSocketChannel in Java NIO is a channel that can listen for new incoming TCP connections, just like the ServerSocket in standard IO.
3.1 example of implementation file passing from client to server (blocking)
public class TestBlockingNIO { public static void main(String[] args) throws Exception{ client(); } public static void client() throws Exception{ System.out.println("client start"); //1. Access SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9898)); FileChannel inChannel = FileChannel.open(Paths.get("e:\\demo\\study.txt"), StandardOpenOption.READ); //2. Allocate buffer of specified size ByteBuffer buf = ByteBuffer.allocate(1024); //3. Read the local file and send it to the server while (inChannel.read(buf) != -1){ buf.flip(); socketChannel.write(buf); buf.clear(); } //Receive the feedback from the server (it is unnecessary to use this code, which is mainly for testing blocking) //Block socketChannel.shutdownOutput(); int len = 0; while ((len = socketChannel.read(buffer)) != -1) { buffer.flip(); System.out.println(new String(buffer.array(), 0, len)); buffer.clear(); } //4. Close the channel inChannel.close(); socketChannel.close(); System.out.println("client stop"); } } class Server{ public static void main(String[] args) throws Exception{ server(); } public static void server() throws Exception{ System.out.println("server start"); //1. Access ServerSocketChannel serverSocket = ServerSocketChannel.open(); FileChannel outChannel = FileChannel.open(Paths.get("e:\\demo\\copy\\2.txt") ,StandardOpenOption.WRITE,StandardOpenOption.CREATE); //2. Binding connection serverSocket.bind(new InetSocketAddress(9898)); //3. Get the channel of the client SocketChannel socketChannel = serverSocket.accept(); //4. Allocate buffer of specified size ByteBuffer buffer = ByteBuffer.allocate(1024); //5. Receive the client's data and save it locally while(socketChannel.read(buffer) != -1){ buffer.flip(); outChannel.write(buffer); buffer.clear(); } //Send feedback to the client (you may not need this code, which is mainly for testing blocking) buffer.put("Server receives client data successfully".getBytes()); buffer.flip(); socketChannel.write(buffer); //6. Close the channel socketChannel.close(); outChannel.close(); serverSocket.close(); System.out.println("server stop"); } }
Start the server first and then the client
3.2 using NIO to realize non blocking server and client
- Client
public class TestNonBlockingNIO { public static void main(String[] args) throws Exception{ client(); } /** * Client * @throws Exception */ public static void client() throws Exception{ // 1. Access SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9898)); //2. Switch non blocking mode socketChannel.configureBlocking(false); //3. Allocate buffer of specified size ByteBuffer buffer = ByteBuffer.allocate(1024); //4. Send data to the server Scanner scan = new Scanner(System.in); while(scan.hasNext()){ String str = scan.next(); buffer.put((new Date().toString() + "\n" + str).getBytes()); buffer.flip(); socketChannel.write(buffer); buffer.clear(); } //5. Close the channel socketChannel.close(); } }
- Server side
public class NonBlockingNIOServer{ public static void main(String[] args) throws Exception{ server(); } /** * Server side * @throws Exception */ public static void server() throws Exception{ //1. Access ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //2. Switch non blocking mode serverSocketChannel.configureBlocking(false); //3. Binding connection serverSocketChannel.bind(new InetSocketAddress(9898)); //4. Get selector Selector selector = Selector.open(); //5. Register the channel to the selector and specify the listening event serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //6. Polling for events ready on the selector while(selector.select() > 0){ //7. Get all registered selection keys in the current selector (ready listening events) Set<SelectionKey> selectionKeys = selector.selectedKeys(); //8. Iterative acquisition Iterator<SelectionKey> it = selectionKeys.iterator(); while (it.hasNext()){ SelectionKey selectionKey = it.next(); //9. Judge what event is ready if(selectionKey.isAcceptable()){ //10. If "ready to receive", get client connection SocketChannel socketChannel = serverSocketChannel.accept(); //11. Switch non blocking mode socketChannel.configureBlocking(false); //12. Register channel to selector socketChannel.register(selector,SelectionKey.OP_READ); }else if(selectionKey.isReadable()){ // 13. Get the channel of "read ready" status on the current selector SocketChannel socketChannel = (SocketChannel)selectionKey.channel(); //14. Read data ByteBuffer buffer = ByteBuffer.allocate(1024); int len = 0; while((len = socketChannel.read(buffer)) > 0){ buffer.flip(); System.out.println(new String(buffer.array(),0,len)); buffer.clear(); } } } //15. Deselect key it.remove(); } } }
Start the server first, then the client. It can start multiple clients at the same time, which is actually a simple chat room
4 DatagramChannel
Datagram channel in Java NIO is a channel that can send and receive UDP packets.
Operation steps:
- Open datagram channel
- Receive / send data
Sample code
package com.zjx.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.util.Iterator; import java.util.Scanner; /** * @author zhaojiaxing * @date 2020-02-04 22:15 */ public class TestNonBlockingNIO2 { public static void main(String[] args) throws Exception{ send(); } public static void send() throws IOException{ DatagramChannel dc = DatagramChannel.open(); dc.configureBlocking(false); ByteBuffer buffer = ByteBuffer.allocate(1024); Scanner scanner = new Scanner(System.in); while (scanner.hasNext()){ String str = scanner.next(); buffer.put(str.getBytes()); buffer.flip(); dc.send(buffer,new InetSocketAddress("127.0.0.1",9898)); buffer.clear(); } dc.close(); } } class TestServer{ public static void main(String[] args) throws Exception{ receive(); } public static void receive() throws IOException{ DatagramChannel dc = DatagramChannel.open(); dc.configureBlocking(false); dc.bind(new InetSocketAddress(9898)); Selector selector = Selector.open(); dc.register(selector, SelectionKey.OP_READ); while(selector.select() > 0){ Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while (it.hasNext()){ SelectionKey selectionKey = it.next(); if(selectionKey.isReadable()){ ByteBuffer buffer = ByteBuffer.allocate(1024); dc.receive(buffer); buffer.flip(); System.out.println(new String(buffer.array(),0,buffer.limit())); buffer.clear(); } } it.remove(); } } }
5. Pipe
The Java NIO pipeline is a one-way data connection between two threads. Pipe has a source channel and a sink channel. The data is written to the sink channel and read from the source channel.
Use example
public static void main(String[] args) throws Exception{ //1. Access pipeline Pipe pipe = Pipe.open(); //2. Write data in buffer to pipeline ByteBuffer buf = ByteBuffer.allocate(1024); Pipe.SinkChannel sinkChannel = pipe.sink(); buf.put("Send data through a one-way pipe".getBytes()); buf.flip(); sinkChannel.write(buf); //3. Read data in buffer Pipe.SourceChannel sourceChannel = pipe.source(); buf.flip(); int len = sourceChannel.read(buf); System.out.println(new String(buf.array(), 0, len)); sourceChannel.close(); sinkChannel.close(); }