1. Introduction to NIO library
1. Buffer Buffer
A Buffer is an object that contains some data to be written and read out.
In NIO, all data is processed in a buffer. When reading data, it is read directly from the channel to the buffer, and when writing data, it is also written from the buffer to the channel.
A buffer is essentially an array, usually a byte array (ByteBuffer), or other types of arrays. In addition, the buffer also provides information such as structured access to data and maintaining read and write locations.
The inheritance relationship of the Buffer class is shown in the figure below:
2. Channel
Channel is a channel where network data is read and written through Channel. The difference between a channel and a stream is that the channel is bidirectional (the channel can be used to read and write the latter at the same time), and the stream only moves in one direction.
Channels can be roughly divided into two categories: SelectableChannel for network reading and writing (ServerSocketChannel and SocketChannel are their subclasses), and FileChannel for file operations.
The following example shows the file using FileChannel to write data to a file, read data from a file, and copy the file data to another file:
public class NioTest{ public static void main(String[] args) throws IOException { copyFile(); } //Copy the file private static void copyFile() { FileInputStream in=null; FileOutputStream out=null; try { in=new FileInputStream("src/main/java/data/in-data.txt"); out=new FileOutputStream("src/main/java/data/out-data.txt"); FileChannel inChannel=in.getChannel(); FileChannel outChannel=out.getChannel(); ByteBuffer buffer=ByteBuffer.allocate(1024); int bytesRead = inChannel.read(buffer); while (bytesRead!=-1) { buffer.flip(); outChannel.write(buffer); buffer.clear(); bytesRead = inChannel.read(buffer); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //Write file private static void writeFileNio() { try { RandomAccessFile fout = new RandomAccessFile("src/main/java/data/nio-data.txt", "rw"); FileChannel fc=fout.getChannel(); ByteBuffer buffer=ByteBuffer.allocate(1024); buffer.put("hi123".getBytes()); buffer.flip(); try { fc.write(buffer); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //Read file private static void readFileNio() { FileInputStream fileInputStream; try { fileInputStream = new FileInputStream("src/main/java/data/nio-data.txt"); FileChannel fileChannel=fileInputStream.getChannel();//Get channel from FileInputStream ByteBuffer byteBuffer=ByteBuffer.allocate(1024);//Create a buffer int bytesRead=fileChannel.read(byteBuffer);//Read the data to the buffer while(bytesRead!=-1) { /*limit=position * position=0; */ byteBuffer.flip(); //hasRemaining(): Inform whether there is an element between the current position and the limit while (byteBuffer.hasRemaining()) { System.out.print((char) byteBuffer.get()); } /* * Clear the buffer* position=0; * limit=capacity; */ byteBuffer.clear(); bytesRead = fileChannel.read(byteBuffer); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}3. Multiplexer Selector
The multiplexer provides the ability to select tasks that are ready. The Selector will constantly poll the Channel registered on it. If a channel sends a read or write event, the Channel will be in the ready state and will be polled by the Selector. Then, the set of ready channels can be obtained through the SelectionKey to perform subsequent I/O operations.
A multiplexer Selector can poll multiple channels at the same time. Since JDK uses epoll instead of the traditional select implementation, it does not have the limit of maximum connection handle 1024/2048, which means that only one thread is required to be responsible for the poll of the Selector and can access thousands of clients. The model is shown in the figure below:
Process a Selector with a single thread. To use Selector, you have to register the Channel with Selector and then call its select() method. This method will block until a registered channel has event ready. Once this method returns, the thread can process these events, such as new connections, data reception, etc.
Note:
1. What select model?
select is an event triggering mechanism, which triggers processing when the waiting event occurs, and is mostly used for the processing of the client by the Linux implementation of servers.
It can simultaneously detect a set of non-blocking IO devices that support non-blocking, whether there are events (such as readable, writable, high priority error output, etc.) until a device triggers an event or exceeds the specified waiting time. That is, their responsibility is not to do IO, but to help the caller find the currently ready device.
2. What is the epoll model?
The design idea of epoll is to split the single operation of select/poll into 1 epoll_create + multiple epoll_ctrl + one wait. In addition, the kernel has added a file system "eventpollfs" for epoll operations. Each or more file descriptors to be monitored have an inode node of the corresponding eventpollfs file system, and the main information is stored in the eventpoll structure. The important information of the monitored files is stored in the epitem structure. So they are a one-to-many relationship.
2. NIO server-side development
Function description: Turn on the server side and send hello string to each access client.
There are several main steps for using NIO for server-side development:
1. Create ServerSocketChannel and configure it to non-blocking mode
serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false);
2. Bind listening and configure TCP parameters, such as backlog size
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
3. Create an independent I/O thread for polling the multiplexer Selector
4. Create a Selector, register the ServerSocketChannel you created previously to the Selector, and listen for SelectionKey.ACCEPT
selector=Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
5. Start the I/O thread, execute the Selector.select() method in the loop body, and poll the ready channel
while(true) { try { //select() blocks until at least one channel is ready on the event you registered //If there is no channel ready, it will block here //select(long timeout) is the same as select(), except that it will block timeout milliseconds (parameters). selector.select(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); break; } }6. When polling the Channel in the ready state, it needs to be judged. If it is the OP_ACCEPT state, it means it is a new client access. Then call ServerSocketChannel.accept() method to accept the new client.
//Return the ready SelectionKey, and then iterate to execute Set<SelectionKey> readKeys=selector.selectedKeys(); for(Iterator<SelectionKey> it=readKeys.iterator(); it.hasNext();) { SelectionKey key=it.next(); it.remove(); try { if(key.isAcceptable()) { ServerSocketChannel server=(ServerSocketChannel) key.channel(); SocketChannel client=server.accept(); client.configureBlocking(false); client.register(selector,SelectionKey.OP_WRITE); } else if(key.isWritable()) { SocketChannel client=(SocketChannel) key.channel(); ByteBuffer buffer=ByteBuffer.allocate(20); String str="hello"; buffer=ByteBuffer.wrap(str.getBytes()); client.write(buffer); key.cancel(); } }catch(IOException e) { e.printStackTrace(); key.cancel(); try { key.channel().close(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } }7. Set the newly accessed client link SocketChannel to non-blocking mode, and configure some other TCP parameters.
if(key.isAcceptable()) { ServerSocketChannel server=(ServerSocketChannel) key.channel(); SocketChannel client=server.accept(); client.configureBlocking(false); ... }8. Register SocketChannel to Selector and listen to OP_WRITE
client.register(selector,SelectionKey.OP_WRITE);
9. If the polled Channel is OP_WRITE, it means that data is to be written into the SockChannel, then the ByteBuffer object is constructed and the data packet is written.
else if(key.isWritable()) { SocketChannel client=(SocketChannel) key.channel(); ByteBuffer buffer=ByteBuffer.allocate(20); String str="hello"; buffer=ByteBuffer.wrap(str.getBytes()); client.write(buffer); key.cancel(); }The complete code is as follows:
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.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.util.Iterator;import java.util.Set;public class ServerSocketChannelDemo{public static void main(String[] args) {ServerSocketChannel serverSocketChannel;Selector selector=null;try {serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);serverSocketChannel.socket().bind(new InetSocketAddress(8080));selector=Selector.open();serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);}catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}while(true) {try {//select() blocks until at least one channel is ready on the event you registered //If there is no channel ready, it will block here all the time.//select(long timeout) is the same as select(), except that it will block timeout milliseconds (parameters). selector.select();}catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();break;}//Return the ready SelectionKey, and then iterate to execute Set<SelectionKey> readKeys=selector.selectedKeys();for (Iterator<SelectionKey> it=readKeys.iterator();it.hasNext();) {SelectionKey key=it.next();it.remove();try {if(key.isAcceptable()) {ServerSocketChannel server=(ServerSocketChannel) key.channel();SocketChannel client=server.accept();client.configureBlocking(false);client.register(selector,SelectionKey.OP_WRITE);} else if(key.isWritable()) {SocketChannel client=(SocketChannel) key.channel();ByteBuffer buffer=ByteBuffer.allocate(20);String str="hello";buffer=ByteBuffer.wrap(str.getBytes());client.write(buffer);key.cancel();}}catch(IOException e) {e.printStackTrace();key.cancel();try {key.channel().close();}catch (IOException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}}}}}}}}}}}We use telnet localhost 8080 to simulate multiple clients:
The program run results are as follows:
Summarize
The above is all the detailed explanation of Java NIO server-side development in this article, I hope it will be helpful to everyone. Interested friends can continue to refer to other related topics on this site. If there are any shortcomings, please leave a message to point it out. Thank you friends for your support for this site!