NIO (selector) of Java network programming

                    This article is intended to be progressive, but just read the last one

 

 Introduction to Selector

  【1】Create Selector

             Selector selector = Selector.open();

  【2】channel registered to Selector

               First of all, the channel must be non-blocking

                channel.register(selector, type of operation, bound component); returns the selection key             

1) After the Channel is registered, and once the channel is in a certain ready state, it can be queried by the selector.
This work is done using the select() method of the Selector. The role of the select method, interested in
Interesting channel operations to query the readiness status.
2) The Selector can continuously query the readiness status of the operations occurring in the Channel. and select interested
ready state for operation. Once the channel is ready for operations and is an operation of interest to the Selector,
It will be selected by the Selector and put into the selection key collection .
3) A selection key, which first contains the type of channel operation registered in the Selector, for example
SelectionKey.OP_READ. It also contains the registration relationship between a specific channel and a specific selector.
When developing an application, the selection key is the key to programming. The programming of NIO is to perform the operation according to the corresponding selection key.
Different business logic processing.
4) The concept of selection key is similar to the concept of event. A selection key similar to the one in listener mode
event. Since Selector is not an event-triggered mode, but an active query mode, it is not called an event
Event, but called SelectionKey selection key.

【3】Poll query ready operation

1 ) Through the select () method of the Selector , you can query the channel operations that are ready, and these ready
The state collection is stored in a Set collection whose elements are SelectionKey objects .
2 ) The following are several overloaded query select() methods of Selector :
           - select(): Blocks until at least one channel is ready on the event you registered for.
           - select(long timeout) : Same as select() , but the longest blocking event is timeout milliseconds.
           - selectNow(): non-blocking, returns immediately as long as there is a channel ready.
The int value returned by the select() method indicates how many channels are ready, more precisely, since the previous select
How many channels become ready in the time period between the method and the select method.
For example: the select() method is called for the first time, if a channel becomes ready, it returns 1, if it is called again
select() method, which returns 1 again if another channel is ready. If the first ready
channel does nothing, there are now two channels ready, but between each select() method call,
Only one channel is ready.

 【4】Method to stop selection

The selector performs the selection process, and the bottom layer of the system will ask whether each channel is ready in turn . This process may cause the calling thread to enter a blocked state. Then we have the following methods to wake up the thread blocked in the select() method.
        wakeup () method : By calling the wakeup () method of the Selector object, the select () method in the blocked state returns immediately. This method makes the first selection operation on the selector that has not returned immediately return. If no selection operation is currently in progress, the next call to the select() method will return immediately.
        close() method : Close the Selector through the close() method. This method wakes up any thread blocked in the selection operation (similar to wakeup()), and at the same time causes all Channels registered to the Selector to be unregistered, and all keys will be cancelled, but the Channel itself will not be closed .

NIO programming steps

Step 1: Create a Selector selector
Step 2: Create a ServerSocketChannel channel and bind the listening port
Step 3: Set the Channel channel to non-blocking mode
Step 4: Register the Channel to the Socketor selector and listen for connection events
Step 5: Call Selector's select method (cyclic call) to monitor the readiness of the channel
Step 6: Call the selectKeys method to get the ready channel collection
Step 7: Traverse the set of ready channels, determine the type of ready events, and implement specific business operations
Step 8: According to the business, decide whether you need to register the monitoring event again, and repeat the third step

Code 1.0

public class SelectorServer {
    public static void main(String[] args) throws Exception {
        ServerSocketChannel  ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(8080));
        ssc.configureBlocking(false);
        //创建selector选择器
        Selector selector = Selector.open();
        //将ssc注册到选器器(建立两者的联系)
        SelectionKey sscKey = ssc.register(selector, 0, null);
        //选择哪种监听的事件
        sscKey.interestOps(SelectionKey.OP_ACCEPT);

        while (true){
            selector.select();//阻塞方法,如果没有事件发生,线程将在此处停止
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();//返回所有可能发生事件的key集合(set)
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                System.out.println("key::"+key);
                ServerSocketChannel channel = (ServerSocketChannel) key.channel();//获取相对应的channel
                SocketChannel sc = channel.accept();
                sc.configureBlocking(false);
                SelectionKey scKey = sc.register(selector, 0, null);
                System.out.println("scKey---->"+scKey);
                scKey.interestOps(SelectionKey.OP_READ);
                System.out.println("sc已经在selector中注册了!");
            }
        }
    }
}

 Result analysis:

     

 solution

[1] Distinguish the event that triggers selector.select()

[2] After processing an event, delete it in the corresponding registered keys set.

 Code 2.0

public class SelectorServer {
    public static void main(String[] args) throws Exception {
        ServerSocketChannel  ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(8080));
        ssc.configureBlocking(false);
        Selector selector = Selector.open();
        SelectionKey sscKey = ssc.register(selector, 0, null);
        sscKey.interestOps(SelectionKey.OP_ACCEPT);

        while (true){
            selector.select();//阻塞方法,如果没有事件发生,线程将在此处停止
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();//返回所有可能发生事件的key集合(set)
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();//解决处理后事件在set集合中还有的现象
                if (key.isAcceptable()){//区分不同事件触发的结果
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();//获取相对应的channel
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false);
                    SelectionKey scKey = sc.register(selector, 0, null);
                    System.out.println("scKey---->"+scKey);
                    scKey.interestOps(SelectionKey.OP_READ);
                    System.out.println("sc已经在selector中注册了!");
                }else if (key.isReadable()){
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(32);
                    int len = channel.read(buffer);
                    buffer.flip();
                    System.out.println(StandardCharsets.UTF_8.decode(buffer).toString());
                    buffer.clear();
                }


            }
        }
    }
}

 New question What will happen if the space allocated by ByteBuffer is not enough (analysis)

       When a reading is not completed, the next reading will be triggered.

 Solution: Dynamically expand the ByteBuffer space of each channel connected to the client (it can avoid the situation of sticking and half-packing!)

 new question:

When the client is forced to shut down, the server stops

 When the client ends normally, the server enters an infinite loop

solution:

The client is forced to shut down, the server reports an exception, use the try--catch statement key.cancel() to do nothing

The client ends normally, the client sends data to the server, read = -1;

Code 3.0 (Final)

public class SelectorServer {
    
    public static void main(String[] args) throws Exception {
        ServerSocketChannel  ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(8080));
        ssc.configureBlocking(false);
        Selector selector = Selector.open();
        SelectionKey sscKey = ssc.register(selector, 0, null);
        sscKey.interestOps(SelectionKey.OP_ACCEPT);

        while (true){
            selector.select();//阻塞方法,如果没有事件发生,线程将在此处停止
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();//返回所有可能发生事件的key集合(set)
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();//解决处理后事件在set集合中还有的现象
                if (key.isAcceptable()){//区分不同事件触发的结果
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();//获取相对应的channel
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false);
                    SelectionKey scKey = sc.register(selector, 0, null);
                    System.out.println("scKey---->"+scKey);
                    ByteBuffer buffer = ByteBuffer.allocate(4);
                    scKey.attach(buffer);//为每一个注册到set集合中的channel分配独立的缓冲区
                    scKey.interestOps(SelectionKey.OP_READ);
                    System.out.println("sc已经在selector中注册了!");
                }else if (key.isReadable()){
                    try {
                        SocketChannel channel = (SocketChannel) key.channel();
                        ByteBuffer buffer = (ByteBuffer) key.attachment();
                        int read = channel.read(buffer);
//                    System.out.println("read::"+read);
//                    System.out.println("positon:"+buffer.position()+"limit:"+buffer.limit());
                        System.out.println(buffer);
                        if (read == -1){//客户端正常结束,read的值等于-1
                            key.cancel();
                            continue;
                        }
                        if(read == buffer.capacity()){
                            ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity()*2);
                            newBuffer.flip();
                            newBuffer.put(buffer);
                            key.attach(newBuffer);
                        }else{
                            buffer.flip();
                            System.out.println(StandardCharsets.UTF_8.decode(buffer).toString());
                            buffer.clear();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        key.cancel();
                    }

                }


            }
        }
    }
}

result:

Client ends normally

 Client forced to end

 

 Selector write events are similar to read events

server

public class SelectorWriter {
    public static void main(String[] args) throws Exception {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ssc.bind(new InetSocketAddress(8080));

        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT, null);

        while (true){
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();
                if (key.isAcceptable()){
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    SelectionKey sckey = sc.register(selector, SelectionKey.OP_READ);

                    //发送大量的数据
                    StringBuilder str = new StringBuilder();
                    for (int i = 0; i < 300000; i++) {
                        str.append("a");
                    }
                    ByteBuffer buffer = Charset.defaultCharset().encode(str.toString());
                    int write = sc.write(buffer);
                    System.out.println(write);
                    if (buffer.hasRemaining()){
                        // 4. 关注可写事件
                        sckey.interestOps(sckey.interestOps() + SelectionKey.OP_WRITE);
                       // sckey.interestOps(sckey.interestOps() | SelectionKey.OP_WRITE);
                        // 5. 把未写完的数据挂到 sckey 上
                        sckey.attach(buffer);
                    }

                }else if(key.isWritable()){
                    SocketChannel sc = (SocketChannel) key.channel();
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    int write = sc.write(buffer);
                    System.out.println(write);
                    //清理工作
                    if (!buffer.hasRemaining()){
                        key.attach(null);
                        key.interestOps(key.interestOps() - SelectionKey.OP_WRITE);
                    }
                
                }
            }
        }
    }
}

 client

public class WriterClient {
    public static void main(String[] args) throws Exception {
        SocketChannel sc = SocketChannel.open();
        sc.connect(new InetSocketAddress("localhost", 8080));

        // 3. 接收数据
        int count = 0;
        while (true) {
            ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
            count += sc.read(buffer);
            System.out.println(count);
            buffer.clear();
        }
    }
}

Guess you like

Origin blog.csdn.net/qq_57533658/article/details/130004236