NIO summary (c) of the NIO Selector

introduction:

Before when we were using BIO socket programming, accept the method will always be blocked until the arrival of the client request and returns the socket corresponding treatment. The whole process is a pipeline, a request is processed in order to obtain and process the request later, of course, also possible to obtain separate socket and the socket handling process, a thread is responsible accept, a thread pool responsible for handling requests.

NIO but provides a better solution, using a selector (Selector) returns already prepared socket, and sequentially processing data for transmission based on the channel (Channel) and buffer (Buffer). We have previously described and Buffer Channel, so let's introduce in network programming is very important Selector

Selector

selector, specifically what kind of things?

Think of a scene: in a chicken farm, there is such a person, the daily work is to keep checking several special cages, if the chicken came in, chicken out, there are chicken, chicken and so sick, put the appropriate case record, if the person in charge of the chicken house'd like to find, just ask the man can be.

Here, the man is quite Selector, each of the cages is equivalent to a SocketChannel, each thread through a Selector can manage multiple SocketChannel.
Here Insert Picture Description
In order to achieve Selector manage multiple SocketChannel, must be registered to a specific target SocketChannel Selector, and the statement needs to listen to events (such Selector know what data needs to be recorded), a total of four kinds of events:
1, Connect: client connection server event, the corresponding value SelectionKey.OP_CONNECT (8)

2, accept: receiving client server connection event corresponding value SelectionKey.OP_ACCEPT (16)

3, read: read event corresponding value SelectionKey.OP_READ (1)

4, write: write event, the corresponding value of SelectionKey.OP_WRITE (4)

Here Insert Picture Description
Here Insert Picture Description

Let's through a simple Web chat room to understand what

TCP mode

Client

Here Insert Picture Description

Server
public  void  Server()  throws IOException{

        //获取通道
	ServerSocketChannel serverChannel = ServerSocketChannel.open();
	//切换非阻塞模式
	serverChannel.configureBlocking(false);
	//绑定连接
	serverChannel.socket().bind(new InetSocketAddress(9898));
	//获取选择器
	Selector selector = Selector.open();
	//将通道注册到选择器上,并且指定“监听器事件”
	serverChannel.register(selector, SelectionKey.OP_ACCEPT);
	//轮询式的获取选择器上已经“准备就绪事件”
	while(true){
	    int n = selector.select();
	    if (n == 0) continue;
	    //获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
	    Iterator ite = this.selector.selectedKeys().iterator();
	    while(ite.hasNext()){
	         //获取准备就绪的事件
	        SelectionKey key = (SelectionKey)ite.next();
                //若“接收就绪”,
	        if (key.isAcceptable()){
	            //若“接受就绪”,获取客户端连接
	            SocketChannel clntChan = ((ServerSocketChannel) key.channel()).accept();
	            //切换非阻塞模式
	            clntChan.configureBlocking(false);
	            //将通道注册到选择器上	        
	            clntChan.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufSize));
	        }
	        //若“读事件”准备就绪
	        if (key.isReadable()){
	             //获取当前读就绪的通道
	             SocketChannel clntChan = ((ServerSocketChannel) key.channel()).accept();
	             //读取数据
	             ByteBuffer buf = ByteBuffer.allocate(1024);
	             //
	             int len = 0 ;
	             while(len = clntChan.read(buf)> 0 ){
	             //切换到读模式
	             buf.flip();
	             System.out.println(new String(buf.array(),0,len));
	             buf.clear();
	             }	            
	        }
	        //若“写事件”准备就绪
	        if (key.isWritable() && key.isValid()){
	           //写和读差不多,都是个这意思
	        }
	        //连接失败
	        if (key.isConnectable()){
	            System.out.println("isConnectable = true");
	        }
	      ite.remove();
	    }
	}
}

End service process operation

  1. Creating ServerSocketChannel instance and bind the designated port;
  2. Create a Selector instance;
  3. The registration serverSocketChannel to the selector, and specify event OP_ACCEPT, the bottom of the socket and associate through channel selector;
  4. If not ready socket, select the method will be blocked for some time and return 0;
  5. If there are underlying socket is ready, selector's select method returns the number of socket, and selectedKeys method returns the corresponding socket events (connect, accept, read or write);
  6. The event type, different processing logic;
    In step 3, selector only registered OP_ACCEPT event ServerSocketChannel of
    6.1 if client A connection service, the implementation of select methods for obtaining Client A socketChannel by ServerSocketChannel, and. OP_READ selector on the event registration socketChannel.
    6.2 A If the client sends data, it will trigger read event, so that the next call to select polling method, the data can be read by socketChannel, while the socketChannel registered OP_WRITE event in the selector, implement the server to the client to write data.

UDP mode

receiver

Here Insert Picture Description

sender

Here Insert Picture Description

Presumably that everyone should know Selector is doing it, let's take a look at that next Selector implementation principle.

The principle Selector

SocketChannel, ServerSocketChannel Selector and initialization of instance implemented through SelectorProvider class, wherein the core of the NIO Socket Selector is achieved.

public static SelectorProvider provider() {
    synchronized (lock) {
        if (provider != null)
            return provider;
        return AccessController.doPrivileged(
            new PrivilegedAction<SelectorProvider>() {
                public SelectorProvider run() {
                        if (loadProviderFromProperty())
                            return provider;
                        if (loadProviderAsService())
                            return provider;
                        provider = sun.nio.ch.DefaultSelectorProvider.create();
                        return provider;
                    }
                });
    }
}

NOTE: SelectorProvider achieved at different windows and linux, provider corresponding implementation method returns.

A thinking: How do Selector manage multiple socket?

When Selector initialization, instantiates PollWrapper, SelectionKeyImpl arrays and Pipe.

WindowsSelectorImpl(SelectorProvider sp) throws IOException {
    super(sp);
    pollWrapper = new PollArrayWrapper(INIT_CAP);
    wakeupPipe = Pipe.open();
    wakeupSourceFd = ((SelChImpl)wakeupPipe.source()).getFDVal();

    // Disable the Nagle algorithm so that the wakeup is more immediate
    SinkChannelImpl sink = (SinkChannelImpl)wakeupPipe.sink();
    (sink.sc).socket().setTcpNoDelay(true);
    wakeupSinkFd = ((SelChImpl)sink).getFDVal();
    pollWrapper.addWakeupSocket(wakeupSourceFd, 0);
}

Unsafe pollWrapper application class with a pollfd physical memory, storage and Events fdVal socket handle, wherein a total of eight pollfd, 0-3-save socket handle, 4-7-save events.
Here Insert Picture Description
Providing fdVal pollWrapper respective operations and event data, such as adding Unsafe operation achieved by the putInt and putShort.

void putDescriptor(int i, int fd) {
    pollArray.putInt(SIZE_POLLFD * i + FD_OFFSET, fd);
}
void putEventOps(int i, int event) {
    pollArray.putShort(SIZE_POLLFD * i + EVENT_OFFSET, (short)event);
}

Take a look at serverChannel.register (selector, SelectionKey.OP_ACCEPT) is how to achieve it

public final SelectionKey register(Selector sel, int ops, Object att)
    throws ClosedChannelException {
    synchronized (regLock) {
        SelectionKey k = findKey(sel);
        if (k != null) {
            k.interestOps(ops);
            k.attach(att);
        }
        if (k == null) {
            // New registration
            synchronized (keyLock) {
                if (!isOpen())
                    throw new ClosedChannelException();
                k = ((AbstractSelector)sel).register(this, ops, att);
                addKey(k);
            }
        }
        return k;
    }
}
  1. If the channel selector and have already registered, add events directly and accessories.
  2. Otherwise the implementation of the registration process by the selector.
protected final SelectionKey register(AbstractSelectableChannel ch,
      int ops,  Object attachment) {
    if (!(ch instanceof SelChImpl))
        throw new IllegalSelectorException();
    SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);
    k.attach(attachment);
    synchronized (publicKeys) {
        implRegister(k);
    }
    k.interestOps(ops);
    return k;
}

protected void implRegister(SelectionKeyImpl ski) {
    synchronized (closeLock) {
        if (pollWrapper == null)
            throw new ClosedSelectorException();
        growIfNeeded();
        channelArray[totalChannels] = ski;
        ski.setIndex(totalChannels);
        fdMap.put(ski);
        keys.add(ski);
        pollWrapper.addEntry(totalChannels, ski);
        totalChannels++;
    }
}
  1. And a selector for the front channel parameters, initialize the object SelectionKeyImpl selectionKeyImpl, and add attachments attachment.
  2. The current channel number is equal to SelectionKeyImpl totalChannels size of the array, and the array of SelectionKeyImpl pollWrapper for expansion operation.
  3. totalChannels% MAX_SELECTABLE_FDS == 0, then open a multi-threaded processing selector.
  4. llWrapper.addEntry socket handle will selectionKeyImpl was added to the corresponding pollfd.
  5. interestOps (ops) Method eventually added to the corresponding event pollfd.

So, no matter serverSocketChannel, or socketChannel, registered in the event selector, ultimately stored in pollArray in.

Next, look at how the selector is implemented in the select channel has a plurality of acquisition of events underlying the method implemented by the selector doSelect implementation class, as follows:

 protected int doSelect(long timeout) throws IOException {
        if (channelArray == null)
            throw new ClosedSelectorException();
        this.timeout = timeout; // set selector timeout
        processDeregisterQueue();
        if (interruptTriggered) {
            resetWakeupSocket();
            return 0;
        }
        // Calculate number of helper threads needed for poll. If necessary
        // threads are created here and start waiting on startLock
        adjustThreadsCount();
        finishLock.reset(); // reset finishLock
        // Wakeup helper threads, waiting on startLock, so they start polling.
        // Redundant threads will exit here after wakeup.
        startLock.startThreads();
        // do polling in the main thread. Main thread is responsible for
        // first MAX_SELECTABLE_FDS entries in pollArray.
        try {
            begin();
            try {
                subSelector.poll();
            } catch (IOException e) {
                finishLock.setException(e); // Save this exception
            }
            // Main thread is out of poll(). Wakeup others and wait for them
            if (threads.size() > 0)
                finishLock.waitForHelperThreads();
          } finally {
              end();
          }
        // Done with poll(). Set wakeupSocket to nonsignaled  for the next run.
        finishLock.checkForException();
        processDeregisterQueue();
        int updated = updateSelectedKeys();
        // Done with poll(). Set wakeupSocket to nonsignaled  for the next run.
        resetWakeupSocket();
        return updated;
    }

Wherein subSelector.poll () to select the core, poll0 implemented by a native function, readFds, writeFds exceptFds array and save the results to select the bottom, the first position in the array is the total number of events stored in the socket, the remaining storage location fd socket handle events.

private final int[] readFds = new int [MAX_SELECTABLE_FDS + 1];
private final int[] writeFds = new int [MAX_SELECTABLE_FDS + 1];
private final int[] exceptFds = new int [MAX_SELECTABLE_FDS + 1];
private int poll() throws IOException{ // poll for the main thread
     return poll0(pollWrapper.pollArrayAddress,
          Math.min(totalChannels, MAX_SELECTABLE_FDS),
             readFds, writeFds, exceptFds, timeout);
}

Execution selector.select (), poll0 a function of the point socket handle memory address to the event and the underlying function.

  1. Before the event did not happen, the program blocked select at, of course, would not have been blocked because epoll If no event will return within the timeout period;
  2. There are corresponding events, poll0 method will return;
  3. ocessDeregisterQueue method will clean up those already cancelled of SelectionKey;
  4. dateSelectedKeys statistical Number SelectionKey events and the occurrence of an event SelectionKey qualified selectedKeys added to the hash table, to the subsequent use.

Note : Before JDK1.4 and earlier versions 1.5 update10, Selector select / poll model-based implementation is based on the non-blocking IO IO multiplexing, not asynchronous IO. In core2.6 above JDK1.5 update10 and linux, sun Selctor optimized to achieve the underlying use epoll replace the SELECT / poll (see, said on the video because of the presence of epoll, it will run on Linux NIO mode programs faster than Windows and more, but I guess it Windows would not be so fishing, said on Baidu has a similar IOCP on the window, do not know is not used).

select (Windows system function): Registered socket is limited by an array of events management, length, polling need to loop through the array to find.

poll (Linux system function): Registered socket events implemented by the list, the number is not limited, traverse the list to find polling.

epoll (function core2.6 above system Liunx, not the Window): Based on an event-driven thinking, the use of reactor mode, the event callback, without the use of some way actively check socket state, ready to receive a passive event can be.

epoll principle

IO is a multiplexing technique epoll under Linux, can be very efficiently processed millions socket handle.

epoll internal implementation is probably as follows:

  1. When epoll initialization, the kernel will register a file system for file storage handle being monitored, when calling epoll_create, creates a file in the file system node. Meanwhile epoll will open up your own kernel cache area to handle red-black tree structure to save, to support fast search, insert, delete. The list will then create a list for storing ready event.

  2. When performing epoll_ctl, in addition to the red-black tree outside into the socket handle epoll file system file objects corresponding to the kernel interrupt handler will register a callback function to tell the kernel, if this interrupt to handle, and put it is ready to put the list in the list. So, when there is data on a socket to the kernel copy the data on the card to the kernel, put the socket inserted in the ready list.

  3. When epoll_wait call, only to observe the ready list, there are no data, if the data is returned, otherwise sleep, timeout returns immediately.

Well, this time, we should also be thinking to answer a question

When we register to the channel selector, we'll add the socket of management to the operating system, when the behavior registered in Socket, the operating system will notify the application immediately, this time we will take out this socket connections to handle it inside the data just fine.

Published 45 original articles · won praise 3 · Views 2320

Guess you like

Origin blog.csdn.net/weixin_44046437/article/details/99652774