Original text: chenmingyu. Top/nio /
Learning Java IO system, the focus is to learn IO model, understand a variety of IO models after you can better understand Java IO
Java IO is a set of Java apis for reading and writing data (input and output). Most programs process some input and produce some output from that input. Java provides the java.io package for this purpose
IO system in Java can be divided into Bio, Nio, Aio three IO models
- About Bio, we need to know what the synchronous blocking IO model is, what objects the Bio operates on: streams, and how to use Bio for network programming, the question of using Bio for network programming
- About Nio, we need to know what is the synchronous non-blocking IO model, what is the multiplexing IO model, the concepts of Buffer,Channel,Selector in Nio, and how to use Nio for network programming
- About Aio, we need to know what the asynchronous non-blocking IO model is, how many ways can Aio implement asynchronous operations, and how can Aio be used for network programming
BIO
If you want to process multiple streams at the same time, you need to use multiple threads
A stream consists of character streams and byte streams. A stream is conceptually a continuous stream of data. When the program needs to read data, it needs to use the input stream to read data, and when it needs to write data out, it needs to use the output stream
Blocking IO model
In Linux, when an application calls recvFROM, the kernel does not return the data immediately if the data is not ready. Instead, the kernel waits for the data to be ready, and the data is copied from the kernel to user space and then returns. During this time, the application process blocks until it returns, which is called the blocking IO model
flow
There are two main types of streams that operate in BIO, byte streams and character streams, both of which can be divided into input streams and output streams based on the direction of the stream
According to type and input and output direction, it can be divided into:
- Input byte stream: InputStream
- Output byte stream: OutputStream
- Input character stream: Reader
- Output character stream: Writer
Byte streams are primarily used to process byte or binary objects, and character streams are used to process character text or strings
InputStreamReader is used to convert an input byte stream into an input character stream
Reader reader = new InputStreamReader(inputStream);
Copy the code
Using OutputStreamWriter, you can turn output byte streams into output character streams
Writer writer = new OutputStreamWriter(outputStream)
Copy the code
We can read data from a data source in an application through InputStream and Reader, and then we can output data to a target medium in an application through OutputStream and Writer
When using byte streams, InputStream and OutputStream are abstract classes, and we instantiate their subclasses, each with its own scope
Reader and Writer are abstract classes, and we instantiate their subclasses, each of which has its own scope
Take reading and writing files as an example
Read data from the data source
Input byte stream: InputStream
public static void main(String[] args) throws Exception{
File file = new File("D:/a.txt");
InputStream inputStream = new FileInputStream(file);
byte[] bytes = new byte[(int) file.length()];
inputStream.read(bytes);
System.out.println(new String(bytes));
inputStream.close();
}
Copy the code
Input character stream: Reader
public static void main(String[] args) throws Exception{
File file = new File("D:/a.txt");
Reader reader = new FileReader(file);
char[] bytes = new char[(int) file.length()];
reader.read(bytes);
System.out.println(new String(bytes));
reader.close();
}
Copy the code
Output to the target medium
Output byte stream: OutputStream
public static void main(String[] args) throws Exception{
String var = "hai this is a test";
File file = new File("D:/b.txt");
OutputStream outputStream = new FileOutputStream(file);
outputStream.write(var.getBytes());
outputStream.close();
}
Copy the code
Output character stream: Writer
public static void main(String[] args) throws Exception{
String var = "hai this is a test";
File file = new File("D:/b.txt");
Writer writer = new FileWriter(file);
writer.write(var);
writer.close();
}
Copy the code
BufferedInputStream
When you use InputStream, you read or write byte by byte. BufferedInputStream provides a buffer for the InputStream, which reads one piece of data at a time and puts it into the buffer. When the data in the buffer is read, the InputStream fills the buffer. Until the input stream is read, having a buffer can improve I/O speed a lot
Use to wrap the input stream into BufferedInputStream
/** * inputStream inputStream * 1024 internal buffer size is 1024byte */
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream,1024);
Copy the code
BufferedOutputStream
BufferedOutputStream can provide a buffer for the output byte stream, similar to BufferedInputStream
Wrap the output stream in a BufferedOutputStream
/** * outputStream outputStream * 1024 internal buffer size is 1024 bytes */
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream,1024);
Copy the code
If the byte stream provides buffered characters, the character stream must also provide BufferedReader and BufferedWriter
BufferedReader
Provides a buffer for the input character stream as follows
BufferedReader bufferedReader = new BufferedReader(reader,1024);
Copy the code
BufferedWriter
Provides a buffer for output character streams as follows
BufferedWriter bufferedWriter = new BufferedWriter(writer,1024);
Copy the code
BIO model network programming
When using the BIO model for Socket programming, the server usually calls the Accept method in a while loop. When there is no client request, the Accept method blocks until the request is received and the corresponding processing is returned. This process is linear. Subsequent requests are processed only after the current request has been processed, usually resulting in the communication thread being blocked for a long time
The BIO model handles multiple connections:
In this mode we usually use a single thread to receive requests, and then use a single thread pool to process requests, and manage multiple Socket client connections concurrently, like this:
Using BIO model for network programming ability of problem is lack of flexibility, the client number of concurrent access and server is 1:1, the relationship between number of threads and due to obstruction at ordinary times there will be a large number of threads in a wait state, waiting for input or output data in place, cause the waste of resources, in the face of a large number of concurrent conditions, If you do not use the thread pool to directly new threads, it will be roughly ballooning, system performance will degrade, may lead to stack memory overflow, and frequent creation and destruction of threads, more waste of resources
Using a thread pool may be a bit better, but you can’t solve the problem of blocking IO obstruction, but also need to consider if the number of thread pool set will be refused to a lot of smaller Socket client connection, if the number of thread pool set is larger, can lead to a large number of context switches, and the program to allocate memory for each thread of the call stack, The default value ranges from 64 KB to 1 MB, which wastes VM memory
The BIO model is suitable for architectures with a fixed number of links and a small number of links, but the code written using this model is more intuitive, simpler and easier to understand
NIO
Since JDK 1.4, the JDK has released a new I/O class library, or NIO, which is a synchronous non-blocking IO model
Non-blocking IO model
Synchronous non-blocking IO model implementation:
Non-blocking IO model
The application calls the recvFROM system call, which returns an EWOULDBLOCK error if the kernel data is not ready. The application does not block, but needs to poll recvFROM continuously until the kernel data is ready. It then waits for the data to be copied from the kernel to user space (which blocks but takes very little time) and returns when the copy is complete
IO multiplexing model
IO reuse model, using the Select provided by Linux system, poll system call, pass one or more file handles (client links in network programming) to select or poll system call, and the application process blocks on the SELECT, thus forming a process corresponding to multiple Socket links. Select /poll then scans the set of Socket links linearly, resulting in a decrease in efficiency when only a few sockets have data, and is limited by the number of file handles held by select/poll. The default value is 1024
Signal driven IO model
The system call SIGAction executes a signal-handler function that does not block the application process. When the data is ready, a SIGIO signal is generated for the process, and the signal callback tells the application to call recvFROM to read the data
The core concepts of NIO
Buffer
Buffer is an object, it contains some to write or read data, all data is used in the NIO Buffer processing, reading data from the Buffer when you need to read, writing the data will be written to the Buffer, the Buffer is essentially a can write data, which can then be read from an array of data, Provides structured access to data and maintains information such as read and write locations internally
Instantiate a ByteBuffer
// Create a buffer of 1024 bytes
ByteBuffer buffer=ByteBuffer.allocate(1024);
Copy the code
How to use Buffer
- Writes data to Buffer
- call
flip()
The Buffer () method switches the Buffer from write to read mode - Read data from Buffer
- call
clear()
Methods orcompact()
Method to clear the buffer so that it can be written again
See this for more details: ifeve.com/buffers/
Channel
Data is always read from a Channel to a Buffer, or written from a Buffer to a Channel. A Channel is only responsible for transporting data, while the operational data is a Buffer
A channel is similar to a stream.
- A channel is bidirectional and can be read or written at the same time, while a stream is unidirectional and can only be read or written
- Read and write to streams are blocked, and channels can read and write asynchronously
Data is read from Channel to Buffer
inChannel.read(buffer);
Copy the code
Data is written from Buffer to Channel
outChannel.write(buffer);
Copy the code
See this for more details:
Take copying files as an example
FileInputStream fileInputStream=new FileInputStream(new File(src));
FileOutputStream fileOutputStream=new FileOutputStream(new File(dst));
// Get the input/output channel
FileChannel inChannel=fileInputStream.getChannel();
FileChannel outChannel=fileOutputStream.getChannel();
// Create a buffer of 1024 bytes
ByteBuffer buffer=ByteBuffer.allocate(1024);
while(true) {// Read data from inChannel. If no bytes are read, return -1
int eof =inChannel.read(buffer);
if(eof==-1) {break;
}
// Switch Buffer from write mode to read mode
buffer.flip();
// Start writing data to outChannel
outChannel.write(buffer);
/ / empty buffer
buffer.clear();
}
inChannel.close();
outChannel.close();
fileInputStream.close();
fileOutputStream.close();
Copy the code
A Selector for multiplexing.
Selector is the basis of NIO programming. The main function is to register multiple channels with a Selector. If a read or write event occurs on a Channel, the Channel will be in a ready state and will be polled by the Selector. The SelectionKey is then used to retrieve the set of channels in place for IO operations
The relationship between Selector and Channel and Buffer
See this for more details:
NIO model network programming
NIO uses the MULTIPLEXing IO model in JDK. By multiplexing multiple IO blocks to a single select block, NIO can simultaneously process multiple client requests in a single thread, saving system overhead. JDK Selector is implemented based on the Select /poll model. In JDK versions 1.5 and later, epoll is used instead of select/poll
Epoll has the following advantages over SELECT /poll:
- Epoll supports an unlimited number of open file descriptors. Select /poll can open a limited number of file descriptors
- Select /poll uses polling to traverse the entire set of file descriptors, with epoll based on the callback function for each file descriptor
Select, poll, and epoll are all MECHANISMS for I/O multiplexing. I/O multiplexing is a mechanism by which a process can monitor multiple descriptors and, once a descriptor is ready (usually read or write), inform the program to do the corresponding read/write operation. But SELECT, poll, and epoll are all synchronous I/ OS in nature, because they are responsible for reading and writing after the read and write event is ready, that is, the read and write process is blocked, while asynchronous I/ OS are not responsible for reading and writing
NIO provides two different sets of socket channels for network programming, ServerSocketChannel and client SocketChannel, both of which support blocking and non-blocking modes
Server code
The server accepts the message output sent by the client and sends a message to the client
// Create a multiplexer Selector
Selector selector=Selector.open();
// Create a Channel object, Channel, to listen on port 9001
ServerSocketChannel channel = ServerSocketChannel.open().bind(new InetSocketAddress(9001));
// Set channel to non-blocking
channel.configureBlocking(false);
//
/** * 1. Selectionkey. OP_CONNECT: connection event * 2. Read event * 4. selectionkey. OP_WRITE: Write event * * Bind channel to selector and register OP_ACCEPT event */
channel.register(selector,SelectionKey.OP_ACCEPT);
while (true) {Selector. Select () returns (a key) only if the OP_ACCEPT event arrives, and blocks until it does
selector.select();
// When an event arrives, select() is no longer blocking, and then selectionKeys () takes the set of selectionkeys that have reached the event
Set keys = selector.selectedKeys();
Iterator iterator = keys.iterator();
while (iterator.hasNext()){
SelectionKey key = (SelectionKey) iterator.next();
// Delete the SelectionKey to prevent the next select method from returning the processed channel
iterator.remove();
// Determine by SelectionKey state
if (key.isConnectable()){
// The connection succeeded
} else if (key.isAcceptable()){
/** * accept the client request ** Because we only registered the OP_ACCEPT event, so the client link will only go this far * all we need to do is read the client's data, So we need to get the serverChannel based on SelectionKey * get the client Channel based on the serverChannel, and then register it with an OP_READ event */
// 1, obtain ServerSocketChannel
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
// 2, the accept() method will not block because an event has already arrived
SocketChannel clientChannel = serverChannel.accept();
// 3, set channel to non-blocking
clientChannel.configureBlocking(false);
// 4, register OP_READ event
clientChannel.register(key.selector(),SelectionKey.OP_READ);
} else if (key.isReadable()){
// Channels can read data
/** * Since the client registers an OP_READ event to send some data after connecting to the server *, the client first needs to fetch clientChannel * and then read clientChannel data through Buffer */
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE);
long bytesRead = clientChannel.read(byteBuffer);
while (bytesRead>0){
byteBuffer.flip();
System.out.println("The client data."+new String(byteBuffer.array()));
byteBuffer.clear();
bytesRead = clientChannel.read(byteBuffer);
}
/** * After the server receives the message, we send another data to the client */
byteBuffer.clear();
byteBuffer.put("Hello client, I'm the server, look how hard NIO is.".getBytes("UTF-8"));
byteBuffer.flip();
clientChannel.write(byteBuffer);
} else if (key.isWritable() && key.isValid()){
// Channels can write data}}}Copy the code
Client code
After connecting to the server, the client sends a message to the server and receives the message from the server
Selector selector = Selector.open();
SocketChannel clientChannel = SocketChannel.open();
// Set channel to non-blocking
clientChannel.configureBlocking(false);
// Connect to the server
clientChannel.connect(new InetSocketAddress(9001));
// Register the OP_CONNECT event
clientChannel.register(selector, SelectionKey.OP_CONNECT);
while (true) {// If the event does not arrive, it will remain blocked
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
iterator.remove();
if (key.isConnectable()){
/** * Connect to server successfully ** Get clientChannel first, then write data to Buffer, then register OP_READ time for clientChannel */
clientChannel = (SocketChannel) key.channel();
if (clientChannel.isConnectionPending()){
clientChannel.finishConnect();
}
clientChannel.configureBlocking(false);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.clear();
byteBuffer.put("Hello server, I am the client, do you see this NIO difficult?".getBytes("UTF-8"));
byteBuffer.flip();
clientChannel.write(byteBuffer);
clientChannel.register(key.selector(),SelectionKey.OP_READ);
} else if (key.isReadable()){
// Channels can read data
clientChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE);
long bytesRead = clientChannel.read(byteBuffer);
while (bytesRead>0){
byteBuffer.flip();
System.out.println("Server data."+newString(byteBuffer.array())); byteBuffer.clear(); bytesRead = clientChannel.read(byteBuffer); }}else if (key.isWritable() && key.isValid()){
// Channels can write data}}}Copy the code
It is very complicated to use the native NIO class library. NIO class library and Api are complicated and troublesome to use. In order to write high quality NIO programs, it is not recommended to directly use the native NIO for network programming, but to use some mature frameworks, such as Netty
AIO
JDK1.7 upgraded the Nio library to Nio2.0, most importantly providing asynchronous file IO operations and event-driven IO. AIO’s asynchronous socket channel is truly asynchronous non-blocking IO
Asynchronous IO model
On Linux, an application initiates a read operation and is immediately ready to do something else. The kernel tells the application that the data has been copied to the used space and that the read operation is complete
Aio model network programming
Asynchronous operations
Aio simplifies NIO’s programming model by enabling asynchronous reads and writes without polling registered channels through a multiplexer
Aio implements asynchronous operations through asynchronous channels, which provide two ways to obtain operation results:
- Use the Future class to get the result of an asynchronous operation, but note that future.get() is a blocking method that blocks the thread
- Async is done by way of a callback, passing in an implementation class of CompletionHandler that defines two methods: Completed and failed, which correspond to success and failure, respectively
Channels in Aio support both methods
AIO provides corresponding asynchronous socket channel to realize the network programming, the service side: AsynchronousServerSocketChannel AsynchronousSocketChannel and client
The service side
The server sends messages to the client and receives messages sent by the client
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress("127.0.0.1".9001));
// Accept requests asynchronously
server.accept(null.new CompletionHandler<AsynchronousSocketChannel, Void>() {
/ / success
@Override
public void completed(AsynchronousSocketChannel result, Void attachment) {
try {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("I'm the server. Hello client.".getBytes());
buffer.flip();
result.write(buffer, null.new CompletionHandler<Integer, Void>(){
@Override
public void completed(Integer result, Void attachment) {
System.out.println("The server sent the message successfully");
}
@Override
public void failed(Throwable exc, Void attachment) {
System.out.println("Send failed"); }}); ByteBuffer readBuffer = ByteBuffer.allocate(1024);
result.read(readBuffer, null.new CompletionHandler<Integer, Void>() {
// Called on success
@Override
public void completed(Integer result, Void attachment) {
System.out.println(new String(readBuffer.array()));
}
// called on failure
@Override
public void failed(Throwable exc, Void attachment) {
System.out.println("Read failed"); }}); }catch(Exception e) { e.printStackTrace(); }}/ / failure
@Override
public void failed(Throwable exc, Void attachment) { exc.printStackTrace(); }});// Prevent the thread from finishing execution
TimeUnit.SECONDS.sleep(1000L);
Copy the code
The client
The client sends messages to and receives messages from the server
AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
Future<Void> future = client.connect(new InetSocketAddress("127.0.0.1".9001));
// block, get the connection
future.get();
ByteBuffer buffer = ByteBuffer.allocate(1024);
/ / read the data
client.read(buffer, null.new CompletionHandler<Integer, Void>() {
// Called on success
@Override
public void completed(Integer result, Void attachment) {
System.out.println(new String(buffer.array()));
}
// called on failure
@Override
public void failed(Throwable exc, Void attachment) {
System.out.println("Client failed to receive message"); }}); ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
writeBuffer.put("Hello, client. Hello, server.".getBytes());
writeBuffer.flip();
// block method
Future<Integer> write = client.write(writeBuffer);
Integer r = write.get();
if(r>0){
System.out.println("Client message sent successfully");
}
// Hibernate the thread
TimeUnit.SECONDS.sleep(1000L);
Copy the code
conclusion
Comparison of IO models:
Pseudo asynchronous IO refers to the Bio model that uses thread pools to process requests
Reference:
Netty’s Definitive Guide, second edition
Ifeve.com/java-nio-al… Concurrent programming network
Tech.meituan.com/2016/11/04/… Meituan technical team
If the picture in the article has infringement, contact me to delete