Detailed Java Network Programming with NIO 2: request to build step by step a model the JAVA NIO I / O multiplexing

Micro-channel public number [yellow] small ramp of ants gold dress JAVA engineer, specializing in JAVA backend technology stack: SpringBoot, SSM family bucket, MySQL, distributed, middleware, services, but also understand the point of finance and investment, adhere to the study and writing, believe in the power of lifelong learning! No reply after public concern "architect" to receive a Java-based, advanced, project architects and other free learning materials and, more databases, distributed, service and other popular micro learning video technology, rich content, both theory and practice, also presented will be the original author of the study guide Java, Java programmer interview guide and other resources dry.

The current environment

  1. jdk == 1.8

Code address

git Address: https://github.com/jasonGeng88/java-network-programming

Knowledge Point

  • nio the I / O to achieve non-blocking and blocking
  • Introduction SocketChannel
  • I / O multiplexing principle
  • The relationship between the event selector and SocketChannel
  • Type event listeners
  • Byte buffer data structure ByteBuffer

Scenes

Then on the site of an access problem, if we need concurrent access to 10 different sites, how do we deal with?

In the last article, we use the java.net.socketclass to implement such a demand, a way to deal with a thread connection, and with control of the thread pool, got the current seemingly optimal solution. But there is also a problem, connection processing is synchronized, i.e. the number of concurrent increases, a large number of requests waiting in the queue, or direct exception is thrown.

To solve this problem, we find the culprit in the "one thread a request", if a thread can handle multiple requests at the same time, it will greatly improve the performance under high concurrency. Here to achieve this model is staying in nio JAVA technology.

nio blocking implement

About What is nio, literally as New IO, it is to compensate for the lack of the original I / O, and in a novel I JDK 1.4 introduced / O implementation. Simple to understand, it is that it provides two blocking I / O and non-blocking implementation ( of course, the default implementation is blocked. ).

Let's first look at the nio to block the way how to deal with.

establish connection

With the experience of a socket of a first step we must also establish a socket connection. But, this is not the use of  new socket() the way, but introduces a new concept  SocketChannel. It can be seen as a perfect type of socket, Socket addition to providing related functions, but also provides many other features, such later want to address registered with the selector function.

FIG class as follows: 

To establish a connection code to achieve:

// initialize the socket, socket binding relationship is established with the channel of 
the SocketChannel the SocketChannel.open SocketChannel = (); 
// initialize remote connection address 
the SocketAddress the InetSocketAddress new new Remote = (this.host, Port); 
// the I / O processing settings blocked, this is the default mode, do not set 
socketChannel.configureBlocking (to true); 
// establish a connection 
socketChannel.connect (remote);

Gets socket connection

I O is the same as the realization of blocking /, the processing on the input and output socket streams, and substantially the same as the one behind. The only difference is that here the need to get the socket connection channel.

  • Gets socket connection
Socket socket = socketChannel.socket();
  • Processing the input and output streams
PrintWriter pw = getWriter(socketChannel.socket());
BufferedReader br = getReader(socketChannel.socket());

Complete example

package com.jason.network.mode.nio;

import com.jason.network.constant.HttpConstant;
import com.jason.network.util.HttpUtil;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.channels.SocketChannel;

public class NioBlockingHttpClient {

    private SocketChannel socketChannel;
    private String host;

    public static void main(String[] args) throws IOException {

        for (String host: HttpConstant.HOSTS) {

            NioBlockingHttpClient client = new NioBlockingHttpClient(host, HttpConstant.PORT);
            client.request();

        }

    }

    public NioBlockingHttpClient(String host, int port) throws IOException {
        this.host = host;
        socketChannel = SocketChannel.open();
        socketChannel.socket().setSoTimeout(5000);
        SocketAddress remote = new InetSocketAddress(this.host, port);
        this.socketChannel.connect(remote);
    }

    public void request() throws IOException {
        PrintWriter pw = getWriter(socketChannel.socket());
        BufferedReader br = getReader(socketChannel.socket());

        pw.write(HttpUtil.compositeRequest(host));
        pw.flush();
        String msg;
        while ((msg = br.readLine()) != null){
            System.out.println(msg);
        }
    }

    private PrintWriter getWriter(Socket socket) throws IOException {
        OutputStream out = socket.getOutputStream();
        return new PrintWriter(out);
    }

    private BufferedReader getReader(Socket socket) throws IOException {
        InputStream in = socket.getInputStream();
        return new BufferedReader(new InputStreamReader(in));
    }
}

nio achieve non-blocking

Principle Analysis

nio blocking implementation, basic and socket using native-like, nothing special big difference.

Let us look at it real strong place. So far, we are blocking I / O. What is blocking I / O, see figure below:

The first three I / O model we mainly observed in the figure, on asynchronous I / O, generally need to rely on the support of the operating system, are not discussed here.

Can be found from the figure, the blocking process mainly occurs in two stages:

  • Phase I: wait for data ready;
  • Second stage: ready to copy data from user space to the kernel buffer;

Here produces a copy from the kernel space to the user, mainly in order to optimize performance of the system considered. Assumptions, return the card to read data directly from user space, it is bound to cause frequent system outages, as read from the card data is not necessarily complete and there may intermittent over. By kernel buffer as the buffer, the buffer has enough data waiting or after the end of reading, for a system interrupt, the data returned to the user, so as to avoid frequent interrupt generation.

Learn the two stages I / O blocking, let's get to the point. Look at how to achieve a thread to handle multiple I / O calls. From the figure above non-blocking I / O can be seen only needs to block only the second phase, the first phase of data waiting process, we do not need to care about. But this model is frequently to check readiness, caused an invalid CPU processing, but not good results. If Hollywood had a similar principle - "Do not call us, we'll call you." Such a thread can initiate multiple simultaneous I / O calls, and do not need to wait for data synchronization ready. When the data is ready to complete, it will be an event mechanism to inform us. This is not to achieve a single-threaded to handle multiple problems IO calls yet? That is called "I / O multiplexer model."


Talk a lot of nonsense, here's a look at the actual surgeon.

Creating a selector

From the above analysis may be, we have to have a selector that can listen to all I / O operations, and as an event to inform us which I / O is ready a.

code show as below:

import java.nio.channels.Selector;

...

private static Selector selector;
static {
    try {
        selector = Selector.open();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Create a non-blocking I / O

Now, let's create a non-blocking  SocketChannelcode with blocking implementation type, the only difference is socketChannel.configureBlocking(false).

Note: Only socketChannel.configureBlocking(false)the code later, is non-blocking, if socketChannel.connect()before setting the non-blocking mode, then the connection operation is still blocking calls.

SocketChannel socketChannel = SocketChannel.open();
SocketAddress remote = new InetSocketAddress(host, port);
// 设置非阻塞模式
socketChannel.configureBlocking(false);
socketChannel.connect(remote);

Associated with the socket of selector

Selector and socket are created, the next step is to associate the two, so select and monitor changes to the Socket. There is employed to  SocketChannel take the initiative to register selectors way bind the association, which explains why not directly new Socket(), but in a SocketChannelway to create socket.

code show as below:

socketChannel.register(selector,
                        SelectionKey.OP_CONNECT
                        | SelectionKey.OP_READ
                        | SelectionKey.OP_WRITE);

The above code, we will socketChannel registered with a selector, and its connections, read, write event listener.

Specific event listener types are as follows:

Action Type value description Owning object
OP_READ 1 << 0 Read SocketChannel
OP_WRITE 1 << 2 Write SocketChannel
OP_CONNECT 1 << 3 Socket connection operation SocketChannel
OP_ACCEPT 1 << 4 Receiving socket operation ServerSocketChannel

Selector listening socket changes

Now, the selector has been associated with the socket we are concerned. Here is the perception of change events, then calls the appropriate handling mechanism.

Here and selector under Linux a bit different, selecotr under nio not going to traverse all of the associated socket. When we set up a registration event type we are concerned, each obtained from the selector, only those socket in line with the type of event, and is ready to complete the operation, reducing the large number of invalid traversal operation.

public void select() throws IOException {
    // 获取就绪的 socket 个数
    while (selector.select() > 0){

        // 获取符合的 socket 在选择器中对应的事件句柄 key
        Set keys = selector.selectedKeys();

        // 遍历所有的key
        Iterator it = keys.iterator();
        while (it.hasNext()){

            // 获取对应的 key,并从已选择的集合中移除
            SelectionKey key = (SelectionKey)it.next();
            it.remove();

            if (key.isConnectable()){
                // 进行连接操作
                connect(key);
            }
            else if (key.isWritable()){
                // 进行写操作
                write(key);
            }
            else if (key.isReadable()){
                // 进行读操作
                receive(key);
            }
        }
    }
}

Note: This selector.select()post is synchronous blocking, waiting for an event occurs, it will be awakened. This prevents the generation of CPU idle. Of course, we can also set the timeout to it, selector.select(long timeout)to end the blocking process.

Handle connections ready event

Below, we look at, respectively, a socket is how to handle the connection, write data and read data ( the course of these operations are blocking the process, but we will wait ready to become a non-blocking ).

Processing connection code:

// SelectionKey Representative SocketChannel registered event handler selector 
Private void Connect (the SelectionKey Key) throws IOException { 
    // Get event handler corresponding SocketChannel 
    SocketChannel = Channel (SocketChannel) key.channel (); 

   // real socket connection is completed 
    channel.finishConnect (); 

   // print the link information 
    the InetSocketAddress Remote = (the InetSocketAddress) channel.socket () The getRemoteSocketAddress ();. 
    String Host remote.getHostName = (); 
    int = remote.getPort Port (); 
    System.out.println (String.format ( "access address:% s:% s successful connection!", Host, Port)); 
}

Process of writing the ready event

// character set processing based 
Private Charset.forName the Charset charset = ( "UTF8"); 

Private void Write (the SelectionKey Key) throws IOException { 
    the SocketChannel Channel = (the SocketChannel) key.channel (); 
    the InetSocketAddress Remote = (the InetSocketAddress) channel.socket () .getRemoteSocketAddress (); 
    String Host = remote.getHostName (); 

    // get the HTTP request, ditto a 
    String request = (Host) HttpUtil.compositeRequest; 

    // write events to SocketChannel 
    channel.write (Charset.encode ( Request)); 

    // modify SocketChannel event of interest 
    key.interestOps (SelectionKey.OP_READ); 
}

There are two caveats:

  • The first is to use a  channel.write(charset.encode(request)); writing data. Some people will say, Why can not synchronous blocking above, by PrintWriteroperating the wrapper class. Because PrintWriterthe  write() method is blocked, that is to wait before real data sent from the socket returned.

This we are talking about here is inconsistent blocked, although the operation here is also blocked, but the process is that it occurs in the data from user space to kernel buffer copy process. As the buffer system data sent through socket, which is not within the scope of obstruction. Also explains why the use  Charset of the written content is encoded, because the buffer to receive the format ByteBuffer.

  • Second, the selectors are used to monitor events change two parameters are  interestOps and  readyOps.

    • interestOps: represents the  SocketChannel type of event are concerned, that is, tell the selectors, when there are these types of events, came to tell me. Here by key.interestOps(SelectionKey.OP_READ);telling selector, then I only care about "reading readiness" event, others do not notice me.

    • readyOps: represents the  SocketChannel current readiness of the event type. With key.isReadable(), for example, is judged based on:return (readyOps() & OP_READ) != 0;

Ready to read event processing

void the receive Private (the SelectionKey Key) throws IOException { 
    the SocketChannel Channel = (the SocketChannel) key.channel (); 
    the ByteBuffer Buffer = ByteBuffer.allocate (1024); 
    channel.read (Buffer); 
    buffer.flip (); 
    String charset = receiveData. decode (Buffer) .toString (); 

    // if no more data is read, the unlink the selector, and closes the socket connection 
    iF ( "" .equals (receiveData)) { 
        key.cancel (); 
        Channel. Close (); 
        return; 
    } 

    System.out.println (receiveData); 
}

Here's the basic process and write the same, only to note that here we need the discretion to buffer the data read operation. First it will allocate a fixed size buffer, and then from the kernel buffer, we just copy the data to the fixed buffer allocation. Here there are two cases:

  • We allocated buffer is too large, then the excess portion of the supplement to 0 ( initialization, in fact, will automatically fill 0 ).
  • We went to a small buffer allocation, because the choice will not stop traversal. As long as the  SocketChannel process reading readiness, and that next time it will continue to read. Of course, the allocation is too small, it will increase the number of traverse.

Finally, look at  ByteBuffer the structure, which are mainly position, limit, capacity and mark property. With  buffer.flip(); , for example, about the role of each attribute ( Mark mainly used to mark the position of a position before, is used in the current postion can not be met, not discussed here ).

It is seen from the figure,

  • Capacity (capacity): indicates that the buffer capacity of data can be stored;
  • Limit (limit): indicates that the buffer current endpoint, writing, reading, the focus should not be exceeded;
  • Position (position): indicates the position of a buffer read unit;

The complete code

package com.jason.network.mode.nio;

import com.jason.network.constant.HttpConstant;
import com.jason.network.util.HttpUtil;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

public class NioNonBlockingHttpClient {

    private static Selector selector;
    private Charset charset = Charset.forName("utf8");

    static {
        try {
            selector = Selector.open();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {

        NioNonBlockingHttpClient client = new NioNonBlockingHttpClient();

        for (String host: HttpConstant.HOSTS) {

            client.request(host, HttpConstant.PORT);

        }

        client.select();

    }

    public void request(String host, int port) throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.socket().setSoTimeout(5000);
        SocketAddress remote = new InetSocketAddress(host, port);
        socketChannel.configureBlocking(false);
        socketChannel.connect(remote);
        socketChannel.register(selector,
                        SelectionKey.OP_CONNECT
                        | SelectionKey.OP_READ
                        | SelectionKey.OP_WRITE);
    }

    public void select() throws IOException {
        while (selector.select(500) > 0){
            Set keys = selector.selectedKeys();

            Iterator it = keys.iterator();

            while (it.hasNext()){

                SelectionKey key = (SelectionKey)it.next();
                it.remove();

                if (key.isConnectable()){
                    connect(key);
                }
                else if (key.isWritable()){
                    write(key);
                }
                else if (key.isReadable()){
                    receive(key);
                }
            }
        }
    }

    private void connect(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        channel.finishConnect();
        InetSocketAddress remote = (InetSocketAddress) channel.socket().getRemoteSocketAddress();
        String host = remote.getHostName();
        int port = remote.getPort();
        System.out.println(String.format("访问地址: %s:%s 连接成功!", host, port));
    }

    private void write(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        InetSocketAddress remote = (InetSocketAddress) channel.socket().getRemoteSocketAddress();
        String host = remote.getHostName();

        String request = HttpUtil.compositeRequest(host);
        System.out.println(request);

        channel.write(charset.encode(request));
        key.interestOps(SelectionKey.OP_READ);
    }

    private void receive(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        channel.read(buffer);
        buffer.flip();
        String receiveData = charset.decode(buffer).toString();

        if ("".equals(receiveData)) {
            key.cancel();
            channel.close();
            return;
        }

        System.out.println(receiveData);
    }
}

Example Effect

to sum up

From the blocking mode nio Let's talk about, describe the differences blocking I / O and non-blocking I / O, as well as in the nio is a step by step how to build a model client IO multiplexing. This paper need to understand more content, if there is to understand the wrong place, please correct me -

Supplement 1: NIO-based multiplexing client's (thread pool Edition)

public static void main (String [] args) { 
    based on a pseudo asynchronous thread pool model NIO a = new model based on a pseudo NIO asynchronous thread pool (); 
  a.startServer ();} 
Private Charset.forName the Charset charset = ( "UTF8 "); class WriteThread the implements the Runnable { 
    Private Key the SelectionKey; 
 public WriteThread (the SelectionKey Key) { 
        this.key = Key; 
  } 
    @Override 
  public void RUN () { 
        the SocketChannel SocketChannel = (the SocketChannel) key.channel (); 
  the Socket Socket = SocketChannel .socket (); 
 the try { 
            socketChannel.finishConnect (); 
  } the catch (IOException E) { 
            e.printStackTrace (); 
  }
        InetSocketAddress remote = (InetSocketAddress) socketChannel.socket().getRemoteSocketAddress();
  String host = remote.getHostName();
 int port = remote.getPort();
  System._out_.println(String.format("访问地址: %s:%s 连接成功!", host, port));    }
}
class ReadThread implements Runnable {
    private SelectionKey key;
 public ReadThread(SelectionKey key) {
        this.key = key;
  }
    @Override
  public void run() {
        SocketChannel socketChannel = (SocketChannel) key.channel();
  ByteBuffer buffer = ByteBuffer.allocate(1024);
 try {
            socketChannel.read(buffer);
  } catch (IOException e) {
            e.printStackTrace();
  }
        buffer.flip();
  String receiveData = null;
 try {
            receiveData = new String(buffer.array(), "utf8");
  } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
  }

        if ("".equals(receiveData)) {
            key.cancel();
 try {
                socketChannel.close();
  } catch (IOException e) {
                e.printStackTrace();
  }
            return;
  }

        System._out_.println(receiveData);
  }
}
class ConnectThread implements Runnable {
    private SelectionKey key;
 public ConnectThread(SelectionKey key) {
        this.key = key;
  }
    @Override
  public void run() {
        SocketChannel socketChannel = (SocketChannel) key.channel();
  ByteBuffer byteBuffer = charset.encode("hello world");
 try {
            socketChannel.write(byteBuffer);
  System._out_.println("hello world");
  } catch (IOException e) {
            e.printStackTrace();
  }
        key.interestOps(SelectionKey._OP_READ_);
  }
}
public void startServer() {
    ExecutorService executorService = Executors.newFixedThreadPool(10);
 try {
        SocketChannel socketChannel = SocketChannel.open();
  Selector selector = Selector.open();    socketChannel.configureBlocking(false);
  InetSocketAddress inetAddress = new InetSocketAddress(1234);    socketChannel.connect(inetAddress);
  socketChannel.register(selector, SelectionKey._OP_CONNECT_ |
                SelectionKey._OP_READ_ |
                SelectionKey._OP_WRITE_);   while (selector.select(500) > 0) {
            Iterator
 
 
  
   keys = selector.selectedKeys().iterator();
 while (keys.hasNext()) {
                SelectionKey key = keys.next();
 if (key.isConnectable()) {
                    executorService.submit(new ConnectThread(key));
  }else if(key.isReadable()) {
                    executorService.submit(new ReadThread(key));
  }else {
                    executorService.submit(new WriteThread(key));
  }
            }
        }

    } catch (IOException e) {
        e.printStackTrace();
  }
}
 
 

Supplement 2: server-based multiplexing of NIO

class NioNonBlockingHttpServer {

    private static Selector _selector_;
 private Charset charset = Charset.forName("utf8");   static {
        try {
            _selector_ = Selector.open();
  } catch (IOException e) {
            e.printStackTrace();
  }
    }

    public static void main(String[] args) throws IOException {

        NioNonBlockingHttpServer httpServer = new NioNonBlockingHttpServer();
  httpServer.select();    }

    public void request(int port) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  serverSocketChannel.socket().setSoTimeout(5000);
  serverSocketChannel.configureBlocking(false);
  serverSocketChannel.socket().bind(new InetSocketAddress(8383)); //        serverSocketChannel.register(selector, //                SelectionKey.OP_CONNECT //                        | SelectionKey.OP_READ //                        | SelectionKey.OP_WRITE);
  }

    public void select() throws IOException {
        while (_selector_.select(500) > 0) {
            Set keys = _selector_.selectedKeys();    Iterator it = keys.iterator();   while (it.hasNext()) {

                SelectionKey key = (SelectionKey) it.next();
  it.remove();   if (key.isAcceptable()) {
                    accept(key);
  } else if (key.isWritable()) {
                    write(key);
  } else if (key.isReadable()) {
                    receive(key);
  }
            }
        }
    }

    private void accept(SelectionKey key) throws IOException {
        SocketChannel socketChannel;
  ServerSocketChannel channel = (ServerSocketChannel) key.channel();
  socketChannel = channel.accept();//接受连接请求
  socketChannel.configureBlocking(false);    socketChannel.register(_selector_, SelectionKey._OP_READ_ | SelectionKey._OP_WRITE_);    InetSocketAddress local = (InetSocketAddress) channel.socket().getLocalSocketAddress();
  String host = local.getHostName();
 int port = local.getPort();
  System._out_.println(String.format("请求地址: %s:%s 接收成功!", host, port));      }

    private void write(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();    InetSocketAddress local = (InetSocketAddress) channel.socket().getRemoteSocketAddress();
  String host = local.getHostName();
  String msg = "hello Client";
  channel.write(charset.encode(msg));    System._out_.println(msg);
  key.interestOps(SelectionKey._OP_READ_);
  }

    private void receive(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
  ByteBuffer buffer = ByteBuffer.allocate(1024);
  channel.read(buffer);
  buffer.flip();
  String receiveData = charset.decode(buffer).toString();   if ("".equals(receiveData)) {
            key.cancel();
  channel.close();
 return;  }

        System._out_.println(receiveData);
  }
}

Guess you like

Origin www.cnblogs.com/xll1025/p/11402454.html