Java Foundation of Java perplexed-NIO (ii)

Source: Internet
Author: User

The above mainly explains the Java IO class and how to use it, this article mainly about the NIO principle.

NiO principle Blocking IO

Non-blocking and blocking is what we often call Io and nio. Before we talk about non-blocking, let's take a look at the blocking in the network communication.

Common network IO communication flows

Above is a network communication IO flowchart, what is blocking it?

    • In the above process if the connection has not yet arrived, then the accept will block, the program runs here to suspend, the CPU to execute other threads.
    • If the data is not ready in the process above, read will be blocked as well.

Blocking Network IO Features: multi-threaded processing of multiple connections. Each thread has its own stack space and consumes some CPU time. Each thread will block when it encounters external readiness. The result of blocking is a lot of process context switching. And most of the process context switches may be meaningless. For example, if a thread listens on a port, only a few requests come in a day, but the CPU has to do a context-switching attempt for that thread, and most of the switching ends up blocking.

blocking I/O communication model

If you have a certain understanding of blocking I/O now, we know that blocking I/O is blocked when the Inputstream.read () method is called, and it waits until the data arrives (or times out) to return, as well as when the Serversocket.accept () method is called. will also block until a client connection is returned, and after each client connects, the server initiates a thread to process the client's request. The communication model for blocking I/O is as follows:

Give an example to understand briefly:
In the IO design, we read data byte by bit from InputStream or reader. Let's say you're working on a line-based text stream, for example:

    Name: Anna    Age: 25    Email: [email protected]    Phone: 1234567890
new BufferedReader(new InputStreamReader(input));String nameLine   = reader.readLine();String ageLine    = reader.readLine();String emailLine  = reader.readLine();String phoneLine  = reader.readLine();

Note how long the processing status is determined by the program execution. In other words, once the Reader.readline () method returns, you know that the text line is read and readLine () blocks until the entire line is read, which is why. You also know that this row contains the name; again, the second readline () call returns, and you know that this line contains age. As you can see, the handler runs only when new data is read, and knows what the data is for each step. Once a running thread has processed some of the data that is being read, the thread will no longer roll back the data (mostly so). It also illustrates this principle:

non-blocking IO

The whole process is replaced by small tasks, which are accomplished through collaboration between tasks.

    • All IO events are handled by a dedicated thread and are responsible for distribution. (That's what selector is going to say later)
    • Event-driven: events are triggered at the time of event, while asynchronous to monitor events.
    • Thread communication: The threads communicate through wait,notify and other means. Ensure that each context switch is meaningful. Reduce the unnecessary process switching.

The following is the structure of the asynchronous IO, the non-blocking basic diagram:

Reactor is the role of the conductor of the metaphor above (this is the reactor principle). The process of each thread is probably to read data, decode, calculate processing, encode, send the response.

related class diagram of NIO


You can see NIO, there are several key points: Buffer, channel and selector below we will explain to you.

buffer (buffers)

Buffer can be simply understood as a list of elements of a set of basic data types, which holds the current position state of the data by several variables, that is, four indexes.

    • position: Specifies the next element index to be written or read, and its value is updated automatically by the Get ()/put () method, and position is initialized to 0 when a new buffer object is created;
    • Limit: Specifies how much data needs to be fetched (when the channel is written from the buffer), or how much space can be placed in the data (when the buffer is read from the channel);
    • capacity: Specifies the maximum amount of data that can be stored in the buffer, in fact, it specifies the size of the underlying array, or at least specifies the capacity of the underlying array to which we are allowed to use;
    • Mark is used to record the previous position of the current position or the default is 0.

There are some relative size relationships between the above four attribute values: 0 <= position <= limit <= capacity. If we create a new Bytebuffer object with a capacity size of 10, at initialization time, position is set to 0,limit and capacity is set to 10, in the process of using Bytebuffer object later, The value of the capacity will no longer change, while the other two will change with use. The four attribute values are:

Now we can read some data from the channel into the buffer, noting that reading the data from the channel is equivalent to writing the data into the buffer. If you read 4 of your own data, then the value of position is 4, that is, the next byte index to be written is 4, and the limit is still 10, as shown in:

The next step is to write the read data to the output channel, which is equivalent to reading the data from the buffer, before which the flip () method must be called, and the method will do two things:

    1. Set limit to the current position value
    2. Set the position to 0

Since position is set to 0, it is guaranteed that the first byte in the buffer is read at the next output, and that the limit is set to the current position, which guarantees that the data read is exactly the data that was written to the buffer before, as shown in:

Now call the Get () method to read the data from the buffer to the output channel, which causes the position to increase while the limit remains the same, but the position does not exceed the limit value, so after reading us to 4 of ourselves in the buffer, The values for both position and limit are 4, as shown in:

After reading the data from the buffer, the value of limit remains at the value we call the Flip () method, and the clear () method is able to set all state changes to the value at the time of initialization, as shown in:

Validation Code
Importjava.io.*;Importjava.nio.*;Importjava.nio.channels.*; Public  class  program {     Public Static void Main(String args[])throwsException {FileInputStream fin =NewFileInputStream ("D:\\test.txt");        FileChannel FC = Fin.getchannel (); Bytebuffer buffer = Bytebuffer.allocate (Ten); Output"Initialize", buffer);        Fc.read (buffer); Output"Call Read ()", buffer);        Buffer.flip (); Output"Call Flip ()", buffer); while(Buffer.remaining () >0) {byteb = Buffer.get ();//System.out.print (((char) b));} output ("Call Get ()", buffer);        Buffer.clear (); Output"Call Clear ()", buffer);    Fin.close (); } Public Static void Output(String step, buffer buffer) {System.out.println (step +" : "); System.out.print ("Capacity:"+ buffer.capacity () +", "); System.out.print ("Position:"+ buffer.position () +", "); System.out.println ("Limit:"+ Buffer.limit ());    System.out.println (); }}

The finished output is:

Introduction to methods (see API)

clear, invert, and re-wrap
In addition to accessing locations, limits, methods of capacity values, and methods for marking and resetting, this class defines the following operations that can be performed on buffers:

    • Clear () prepares the buffer for a series of new channel reads or relative placement operations: it sets the limit to the capacity size and sets the position to 0.
    • Flip () prepares the buffer for a series of new channel writes or relative fetches: it sets the limit to the current position, and then sets the position to 0.
    • Rewind () prepares the buffer for re-reading the contained data: it keeps the limit unchanged and sets the position to 0.

If you also know about the partitioning of a buffer with data sharing, read-only buffers, and so on, see Java NIO Usage and rationale analysis (iii), this blog post.

channel (Channels)

Java NiO channels are similar to streams, but are somewhat different:

    • The data can be read from the channel, and the data can be written to the channel. But the reading and writing of the stream is usually unidirectional;
    • The channel can read and write asynchronously;
    • The data in the channel is always read to a buffer first, or it is always written from a buffer.
Channel Implementation

These are the most important implementations of the channel in Java NIO:

    • FileChannel read and write data from a file;
    • Datagramchannel can read and write data in the network via UDP;
    • Socketchannel can read and write data in the network via TCP;
    • Serversocketchannel can listen for incoming TCP connections, like a Web server. A socketchannel is created for each new incoming connection.
Serversocketchannel
// 打开 ServerSocketChannelServerSocketChannel serverSocketChannel = ServerSocketChannel.open();// 绑定9999端口serverSocketChannel.socket().bind(new InetSocketAddress(9999));// 常不会仅仅只监听一个连接,在while循环中调用 accept()方法。while(true){// 监听新进来的连接.// 通过 ServerSocketChannel.accept() 方法监听新进来的连接。当 accept()方法返回的时候,它返回一个包含新进来的连接的 SocketChannel。因此, accept()方法会一直阻塞到有新连接到达。    SocketChannel socketChannel =            serverSocketChannel.accept();    //do something with socketChannel...}
Selector (selector)

Let's review the traditional blocking IO. Typically, when synchronizing I/O operations, if the data is read, the code blocks until there is data available for reading. Similarly, the write call will block until the data can be written. The traditional server/client mode is based on TPR (thread per request), and the server establishes a thread for each client request, which is solely responsible for processing a customer request. One problem with this pattern is that the number of threads increases, and a large number of threads increase the cost of the server. Most implementations, in order to avoid this problem, have adopted a thread pool model and set the maximum number of thread pool threads, which brings new problems, if there are 200 threads in the thread pool, and 200 users are doing large file downloads, the No. 201 user's request will not be processed in time. Even if the No. 201 user only wants to request a page of a few KB size. The traditional server/client pattern is as follows:

NiO non-blocking I/O is based on the reactor mode of operation, I/O calls will not be blocked, instead of registering interested in specific I/O events, such as the arrival of readable data, new socket connections and so on, in the event of a particular incident, the system informs us. The core object of implementing nonblocking I/O in NiO is that Selector,selector is registering various I/O events, and when those events occur, it is the object that tells us what happened, as shown here:

When there are any registered events such as read or write, the corresponding selectionkey can be obtained from the selector, and the events occurring and the specific selectablechannel of the event can be found from the Selectionkey. To get the data that the client sent over.

Using NiO non-blocking I/O to write server handlers can be broadly divided into the following three steps:

    1. Registering an event of interest to a selector object
    2. Get events of interest from selector
    3. According to the different events to deal with the corresponding
registering an event of interest to a selector object
/* * Register Event * */ protected  Selector getselector  () throws     IOException {//create Selector object  Selector sel = Selector.open ();    //creates selectable channels and is configured as nonblocking mode     Serversocketchannel Server = Serversocketchannel.open ();    Server.configureblocking (false );    //bind channel to specified port     ServerSocket socket = Server.socket ();    inetsocketaddress address = new  inetsocketaddress (port);    Socket.bind (address);     //Register the event of interest to selector  server.register (sel, selectionkey.op_accept); return  sel;}  

The Serversocketchannel object is created and the Configureblocking () method is called, configured as nonblocking mode, the next three lines of code bind the channel to the specified port, and finally the event is registered to selector, where the parameter is specified as Op_ Accept, which specifies that we want to listen to the Accept event, which is the event generated when a new connection occurs, the only parameter we can specify for the Serversocketchannel channel is op_accept.

get events of interest from selector
/* * Start listening * */  Public  void  listen  () {System.out.println ( "listen on"  + port); try  {while  (true ) {//the call will block until at least one event occurs  selector.select (); set<selectionkey> keys = Selector.selectedkeys (); Iterator<selectionkey> iter = Keys.iterator (); while  (Iter.hasnext ()) {Selectionkey key = (Selectionkey) iter.next (); Iter.remove (); Process (key); }}} catch  (IOException e) {e.printstacktrace (); } }

In non-blocking I/O, the internal loop pattern basically follows this approach. First call the Select () method, which blocks until at least one event occurs, and then uses the Selectedkeys () method to get the selectionkey of the event, and then uses the iterator to loop.

write the appropriate processing code according to the different events
/ * * * * * * * * * * * based on different events * */protected void Process(Selectionkey key)throwsioexception{//Receive requests    if(Key.isacceptable ())        {Serversocketchannel Server = (Serversocketchannel) key.channel ();        Socketchannel channel = Server.accept (); Channel.configureblocking (false);    Channel.register (selector, selectionkey.op_read); }//Read information    Else if(Key.isreadable ()) {Socketchannel channel = (Socketchannel) key.channel ();intCount = channel.read (buffer);if(Count >0) {Buffer.flip ();             Charbuffer Charbuffer = decoder.decode (buffer);             Name = Charbuffer.tostring ();             Selectionkey SKey = Channel.register (selector, selectionkey.op_write);         Skey.attach (name); }Else{Channel.close ();     } buffer.clear (); }//write event    Else if(Key.iswritable ())         {Socketchannel channel = (Socketchannel) key.channel ();         String name = (string) key.attachment (); Bytebuffer block = Encoder.encode (Charbuffer.wrap ("Hello"+ name));if(Block! =NULL) {Channel.write (block); }Else{Channel.close (); }     }}
Event name corresponding Values
Server-side Receive client connection events Selectionkey.op_accept
Client Connection service-side events Selectionkey.op_connect
Read Events Selectionkey.op_read
Write events Selectionkey.op_write

Overall code, please click here

I do not want to write a blog post into the "Xinhua dictionary", If you want more detailed understanding of the NIO Selector blocking wake principle. If you still want to know the principle of the reactor, look at the Reactor (reactor) mode, so you can know that the reactor is divided into five members: Reactor (reactor), synchronous event Demultiplexer (synchronous event splitter), Handle (descriptor), event Handler (Events handler interface), concrete event Handler (the specific events handling interface).

how Java sockets work (easy to understand)

The concept of the Socket does not correspond to a specific entity, it is an abstraction that describes the communication between computers to accomplish each other. For example, the Socket can be used as a means of transport between two cities, with which it can travel back and forth between cities. There are many modes of transport, and there are traffic rules for each type of vehicle. Sockets are the same, there are many. In most cases, we use TCP/IP-based streaming sockets, which is a stable communication protocol.

is a typical Socket-based communication scenario:
Figure 8.Socket Communication Example

Host A's application communicates with host B's application and must establish a connection through the socket, and the socket connection must be built with the underlying TCP/IP protocol to establish a TCP connection. Establishing a TCP connection requires the underlying IP protocol to address the hosts in the network. We know that the IP protocol used by the network layer can help us to locate the target host based on the IP address, but there may be multiple applications running on one host, and how to communicate with the specified application is specified by the TCP or UPD address, or the port number. This makes it possible for a Socket instance to uniquely represent a communication link for an application on a host.

Establish a communication link

When the client wants to communicate with the server, the client first creates a socket instance, the operating system assigns the socket instance a local port number that is not used, and creates a socket data structure that contains local and remote addresses and port numbers. This data structure will remain in the system until the connection is closed. Before the constructor for the socket instance is returned correctly, the three-time handshake protocol for TCP will be made, and after the TCP handshake protocol completes, the socket instance object will be created, or a IOException error will be thrown.
The corresponding server will create a ServerSocket instance, ServerSocket creation is simpler as long as the specified port number is not occupied, the general instance creation will succeed, and the operating system will create an underlying data structure for the ServerSocket instance. This data structure contains the port number of the specified listener and the wildcard character that contains the listening address, usually "*" which listens to all addresses. Then when the accept () method is called, it goes into a blocking state and waits for the client's request. When a new request arrives, a new socket data structure is created for the connection, which contains the address and port information for the requested source address and port. This newly created data structure will be associated to an incomplete list of connection data structures for the ServerSocket instance, and note that the server's corresponding socket instance is not created, and the socket instance of the server is not returned until the three handshake with the client is completed. and move the data structure of the Socket instance to the completed list in the never-complete list. Therefore, each data structure in the list associated with ServerSocket represents a TCP connection that is established with a client.

Data Transfer

Transferring data is the primary purpose of our connection, and how to transfer data through the Socket is described in detail below.
When the connection is successful, both the server and the client have a socket instance, and each socket instance has a InputStream and OutputStream, which are the two objects that exchange data. We also know that network I/O is transmitted by byte stream. When the Socket object is created, the operating system allocates a buffer of a certain size for InputStream and OutputStream, and the writing and reading of the data is done through this buffer. The write side writes the data to the outputstream corresponding SENDQ queue, and when the queue fills up, the data is sent to the RECVQ queue at the other end InputStream, and if the RECVQ is full then the OutputStream write method will Blocking until the RECVQ queue has enough space to hold the data sent by SENDQ. It is important to note that the size of this buffer and the speed of the write end and the speed of the read side greatly affect the data transfer efficiency of the connection, because of the possible blocking, so the network I/O and disk I/O in the data write and read also have a coordinated process, if both sides of the simultaneous transmission of data may produce a deadlock, In the following NIO section will be introduced to avoid this situation.

Reference:
Deep analysis of the working mechanism of Java IO
Analysis and code implementation of Java NiO principle and text
Introduction to JAVA NIO
Java NIO usage and Principle Analysis (II.)
Java NiO Series Tutorial (II.) Channel
Java NIO usage and Principle Analysis (iv)

Java Foundation of Java perplexed-NIO (ii)

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.