Netty learning notes NIO basics-2

Posted by wkoneal on Sun, 16 Jan 2022 22:58:44 +0100

1. Preface

Notes: Netty teaching based on dark horse, video address: Black horse Netty


2. Network programming (single thread)

1. Block

  • When there is no data readability, including data replication, the thread must block and wait, which will not occupy the cpu, but the thread is equivalent to idle
  • A 32-bit jvm has 320k threads and a 64 bit jvm has 1024k threads. In order to reduce the number of threads, thread pool technology needs to be adopted
  • However, even if the thread pool is used, if many connections are established but inactive for a long time, all threads in the thread pool will be blocked.

The following example demonstrates blocking using client and server

  1. One client, but the following code cannot handle the case that one client sends multiple requests, because socketchannel SC = SSc Accept () is blocked again, and a new client is needed to continue running. Blocking mode does not work well for a single thread.
@Slf4j
public class Server {
    public static void main(String[] args) throws IOException {
        //Using NIO to understand blocking patterns
        ByteBuffer buffer = ByteBuffer.allocate(16);
        //1. Create a server object
        ServerSocketChannel ssc = ServerSocketChannel.open();

        //2. Binding port
        ssc.bind(new InetSocketAddress(8080));

        //3. Set of connections
        List<SocketChannel> channels = new LinkedList<>();

        while (true) {
            //4. Establish a connection with the client accept
            //SocketChannel is used to communicate with clients
            log.debug("connecting...");
            /**
             accept The default is blocking. In the case of a single thread, the thread stops running and can continue to point to the server only after the connection is established
             */
            SocketChannel sc = ssc.accept();
            log.debug("connected... {}", sc);
            channels.add(sc);
            for (SocketChannel channel : channels) {
                //connected local=/127.0.0.1:8080 remote=/127.0.0.1:61314
                log.debug("before read... {}", channel);
                //5. Accept data sent by client
                /**
                 read It is also a blocking method. If the thread stops running and the client does not send data, it will not continue here
                 */
                channel.read(buffer);
                //Read mode
                buffer.flip();
                debugAll(buffer);
                //Write mode
                buffer.clear();
                log.debug("after read... {}", channel);
            }
        }
    }
}
public class Client {
    public static void main(String[] args) throws IOException {
        SocketChannel sc = SocketChannel.open();
        sc.connect(new InetSocketAddress("localhost", 8080));
        System.out.println("waiting");
    }
}
  1. Of course, the solution can be a client with a connection


2. Non blocking

  • When a Channel has no readable events, the thread does not have to block. It can handle other channels with readable events
  • During data replication, the line b process is actually blocked (where AIO improves)
  • When writing data, the thread just waits for the data to be written to the Channel without waiting for the Channel to send the data through the network

The following example demonstrates non blocking using client and server

  1. In the non blocking mode, configure blocking (false) is used to specify. At this time, both the accept method and the read method are non blocking
  2. However, there is also a disadvantage, that is, the cpu occupancy rate is too high. In any case, it is in an endless loop and the efficiency is very low. Therefore, we need to improve. Can we wait until there is a connection.
@Slf4j
public class Server {
    public static void main(String[] args) throws IOException {
        //Using NIO to understand blocking patterns
        ByteBuffer buffer = ByteBuffer.allocate(16);
        //1. Create a server object
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);   //Switch to non blocking mode and the accept method becomes non blocking
        //2. Binding port
        ssc.bind(new InetSocketAddress(8080));

        //3. Set of connections
        List<SocketChannel> channels = new LinkedList<>();

        while (true) {
            //4. Establish a connection with the client accept
            //SocketChannel is used to communicate with clients
            log.debug("connecting...");
            /**
             accept The default is blocking. In the case of a single thread, the thread stops running and can continue to point to the server only after the connection is established
             In non blocking mode, the thread will continue to run. If no connection is established, the sc returns null
             */
            SocketChannel sc = ssc.accept();
            if(sc != null){
                log.debug("connected... {}", sc);
                channels.add(sc);
                /**
                    channel If it is set to non blocking mode, the following read is non blocking
                 */
                sc.configureBlocking(false);
            }
            for (SocketChannel channel : channels) {
                log.debug("before read... {}", channel);
                //5. Accept data sent by client
                /**
                 At this time, the read is non blocking and will continue to run. If no data is read, 0 will be returned
                 */
                int read = channel.read(buffer);
                if(read > 0){
                    //Read the data
                    //Read mode
                    buffer.flip();
                    debugAll(buffer);
                    //Write mode
                    buffer.clear();
                    log.debug("after read... {}", channel);
                }
            }
        }
    }
}



3. Multiplexing and event handling

1. Event handling

Threads must cooperate with selectors to monitor multiple Channel read-write events. This is called multiplexing. Note that only events have selectors

  • Multiplexing is only for network IO, and ordinary file IO cannot use multiplexing
  • If you do not use the non blocking mode of the Selector. Then the bytes read by the Channel are often 0, and the Selector ensures that the readable events are read
  • Once the data entered by the Channel is ready, the readable event of the Selector will be triggered

The following example demonstrates blocking using client and server

  1. Step 1: first demonstrate listening to client connection events. The steps are as follows:
  • Create a selector to manage multiple channel s
  • Establish the connection between channel and selector (Registration)
  • selector. The select () method blocks when there is no event. Once the event is sent, the operation will be resumed and the downward processing will continue
  • Handle events, and selectionKeys get all readable and writable events

There are four types of events:
1. accept - will be triggered when a connection request is made
2. connect - the event triggered after the client connection is established
3. read - triggered when the client sends data
4. write writable event
Note: select will not block when the event is not processed.

The specific process is to first create a Selector, and then create a channel ServerSocketChannel, which is set to non blocking. Bind the channel with the Selector, then set the event of interest for the channel as the accept event, and then bind the port. Call Selector The select method blocks. When an event occurs, get all the events from the Selector, and then find the channel through the event. For example, when connecting, find the channel interested in accept, and then call accept to accept the client.

@Slf4j
public class Server {
    public static void main(String[] args) throws IOException {
        //1. Create a selector to manage multiple channel s
        Selector selector = Selector.open();

        ByteBuffer buffer = ByteBuffer.allocate(16);
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);

        //2. Establish the connection between channel and selector (Registration)
        //SelectionKey: after an event occurs, you can get the event through this, and you can also know which channel the event occurred
        /**
            There are four types of events:
                1.accept-It will be triggered when the connection request is used
                2.connect-Event triggered after the client connection is established
                3.read-Triggered when the client sends data
                4.write-Writable event
         */
        SelectionKey sscKey = ssc.register(selector, 0, null);
        //Table name our key only focuses on the accept event
        sscKey.interestOps(SelectionKey.OP_ACCEPT);
        log.debug("register key:{}", sscKey);
        ssc.bind(new InetSocketAddress(8080));

        while (true) {
            //3. selector. The select () method blocks when there is no event. Once the event is sent, the operation will be resumed and the downward processing will continue
            //The problem of wasting CPU in white circulation is solved
            selector.select();

            //4. Handle events, and selectionKeys gets all readable and writable events
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

            while(iterator.hasNext()){
            	//Note that if the event does not call accept for processing, it will not be blocked, because the event cannot be blocked if it is not processed
            	//In other words, the event is either processed or cancelled, and cannot be ignored
                SelectionKey key = iterator.next();
				//key. Cancel(): cancel event
                log.debug("key:{}", key);
                //Get the channel that triggered the event
                ServerSocketChannel channel = (ServerSocketChannel)key.channel();
                SocketChannel sc = channel.accept();
                log.debug("{}", sc);
            }
        }
    }
}

Handling different events

According to the above picture, all the ssckeys at the beginning are stored in the selector. Later, when an event occurs, a copy will be copied from the selector to the selectedKeys collection for traversal. At the same time, pay attention to several problems:

  • The event key in selectKeys must be closed after processing, otherwise it will cause null pointer exception, because the channel obtained by the key at this time is null, and an error will be reported when calling sc.configureBlocking(false)
  • Pay attention to the disconnection of the client. When disconnecting the client, you must manually cancle(), and you need to cancel the key (really delete it from the key set of the selector). Secondly, it is necessary to judge whether it is abnormal disconnection or normal disconnection. Otherwise, the read request sent during shutdown is likely to cause an exception (client disconnection exception)
  • When processing, the key should be divided into multiple types, such as accept type, event reading type, etc. when the client is closed, note that a read event will be sent automatically.
@Slf4j
public class Server {
    public static void main(String[] args) throws IOException {
        //1. Create a selector to manage multiple channel s
        Selector selector = Selector.open();

        ByteBuffer buffer = ByteBuffer.allocate(16);
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);

        //2. Establish the connection between channel and selector (Registration)
        //SelectionKey: after an event occurs, you can get the event through this, and you can also know which channel the event occurred
        /**
            There are four types of events:
                1.accept-It will be triggered when the connection request is used
                2.connect-Event triggered after the client connection is established
                3.read-Triggered when the client sends data
                4.write-Writable event
         */
        SelectionKey sscKey = ssc.register(selector, 0, null);
        //The table name, our key, only focuses on the accept event. When all clients connect, messages will enter this channel
        sscKey.interestOps(SelectionKey.OP_ACCEPT);
        log.debug("register key:{}", sscKey);
        ssc.bind(new InetSocketAddress(8080));

        while (true) {
            //3. selector. The select () method blocks when there is no event. Once the event is sent, the operation will be resumed and the downward processing will continue
            //The problem of wasting CPU in white circulation is solved
            selector.select();

            //4. Handle events, and selectionKeys gets all readable and writable events
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

            //When there are multiple key s, the accept and read methods will trigger events, so the event types should be distinguished
            while(iterator.hasNext()){
                SelectionKey key = iterator.next();
                //When processing keys, delete them from the green selectkeys, otherwise an error will be reported
                iterator.remove();
                log.debug("key:{}", key);

                //5. Distinguish event types
                if (key.isAcceptable()) {
                    //Get the channel that triggered the event
                    ServerSocketChannel channel = (ServerSocketChannel)key.channel();
                    SocketChannel sc = channel.accept();
                    //Set to non blocking
                    sc.configureBlocking(false);
                    //channel of sc key tube
                    SelectionKey scKey = sc.register(selector, 0, null);
                    //scKey pays attention to read events, that is, the channel of the client pays attention to read events
                    scKey.interestOps(SelectionKey.OP_READ);
                    log.debug("{}", sc);
                }else if(key.isReadable()){
                    //After the client is closed, a read event will also be triggered. At this time, you need to remove the key, otherwise you can't get the channel and an error will be reported
                    try {
                        //Readable event
                        SocketChannel channel = (SocketChannel)key.channel();//The channel that triggered the event
                        ByteBuffer buffer1 = ByteBuffer.allocate(16);
                        //The client is disconnected normally, and the return value of read is - 1
                        int read = channel.read(buffer1);
                        if(read == -1){
                            //Normal disconnection
                            key.cancel();
                        }
                        buffer1.flip();
                        debugAll(buffer);
                    } catch (IOException e) {
                        e.printStackTrace();
                        key.cancel();   //When the client is disconnected, you need to cancel the key (delete it from the key collection of the selector)
                    }
                }

            }
        }
    }
}



2. Boundary of processing message

For the above code, reduce the buffer capacity. For example, the following example is reduced to 4 bytes. When 2 Chinese characters are sent, the bytes exceed the boundary, which will lead to incomplete reading. Some Chinese characters will be read separately, which will lead to the problem of incomplete reading.

ByteBuffer buffer = ByteBuffer.allocate(4);
// Decode and print
System.out.println(StandardCharsets.UTF_8.decode(buffer));

//You
//��


In text transmission, there will also be half packets and sticky packets, so how to solve it?

  • One is a fixed message length, the packet size is the same, and the server reads according to the predetermined length. The disadvantage is a waste of bandwidth. If the length of the data is not enough, it should be supplemented, that is, it is the same as the set maximum length
  • Another idea is to split according to the separator. The disadvantage is low efficiency. ByteBuffer needs to match character by character and split according to the separator
  • TLV format, i.e. Type, Length and Value data, sends the Length of the data together. When the Type and Length are known, it is convenient to obtain the message size and allocate the appropriate buffer. The disadvantage is that the buffer needs to be allocated in advance. If the content is too large, it will affect the server throughput. TLV article
    1. Http 1.1 is a TLV format
    2. Http 2.0 is an LTV format
    3. There is a content type in the request header in Http

The second idea of dealing with boundary is given below:

else if(key.isReadable()){
//After the client is closed, a read event will also be triggered. At this time, you need to remove the key, otherwise you can't get the channel and an error will be reported
  try {
      //Readable event
      SocketChannel channel = (SocketChannel)key.channel();//The channel that triggered the event
      ByteBuffer buffer1 = ByteBuffer.allocate(16);
      //The client is disconnected normally, and the return value of read is - 1
      int read = channel.read(buffer1);
      if(read == -1){
          //Normal disconnection
          key.cancel();
      }else{
      //split
          split(buffer1);
      }
  } 
  
 //Split by \ n
    private static void split(ByteBuffer source) {
        source.flip();
        for (int i = 0; i < source.limit(); i++) {
            //Find a complete message
            if (source.get(i) == '\n') {
                //The full length of a message
                int length = i + 1 - source.position();
                //Store this complete message in a new byteBuffer
                ByteBuffer target = ByteBuffer.allocate(length);
                for (int j = 0; j < length; j++) {
                    target.put(source.get());
                }
                debugAll(target);
            }
        }
    }

However, there is a problem with the above processing, because when the transmitted data is > 16, it cannot be output because split cannot detect it. It will keep reading until it is read.
resolvent:

  • byteBuffer cannot be a local variable, because if it is a local variable, the first data has been lost during the second entry, and it is too late to expand
  • ByteBuffer will start to expand when there are not enough bytes. You can use the processing in HashMap to double the space.

1. Use buffer as an attachment to connect with scKey.

//A buffer is associated with the SelectionKey to prevent multiple channel s from using a buffer at the same time
ByteBuffer buffer1 = ByteBuffer.allocate(16);   //Attachment: attachment
SelectionKey scKey = sc.register(selector, 0, buffer1);

2. Expand the capacity when the capacity is insufficient, and don't forget to copy the attar again

 split(buffer1);
 if(buffer1.position() == buffer1.limit()){
        //Capacity expansion
        ByteBuffer newBuffer = ByteBuffer.allocate(buffer1.capacity() * 2);
        buffer1.flip();
        newBuffer.put(buffer1);
        //Replace attachment
        key.attach(newBuffer);
    }

3. All codes

@Slf4j
public class Server {

    //Split by \ n
    private static void split(ByteBuffer source) {
        source.flip();
        for (int i = 0; i < source.limit(); i++) {
            //Find a complete message
            if (source.get(i) == '\n') {
                //The full length of a message
                int length = i + 1 - source.position();
                //Store this complete message in a new byteBuffer
                ByteBuffer target = ByteBuffer.allocate(length);
                for (int j = 0; j < length; j++) {
                    target.put(source.get());
                }
                debugAll(target);
            }
        }
        //compact switches the write mode
        //The bottom layer of compact is to change the bytes in the buffer into unread bytes, but since we didn't read them, there are still 16 bytes left
        source.compact();
    }

    public static void main(String[] args) throws IOException {
        //1. Create a selector to manage multiple channel s
        Selector selector = Selector.open();

        ByteBuffer buffer = ByteBuffer.allocate(16);
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);

        //2. Establish the connection between channel and selector (Registration)
        //SelectionKey: after an event occurs, you can get the event through this, and you can also know which channel the event occurred
        /**
            There are four types of events:
                1.accept-It will be triggered when the connection request is used
                2.connect-Event triggered after the client connection is established
                3.read-Triggered when the client sends data
                4.write-Writable event
         */
        SelectionKey sscKey = ssc.register(selector, 0, null);
        //The table name, our key, only focuses on the accept event. When all clients connect, messages will enter this channel
        sscKey.interestOps(SelectionKey.OP_ACCEPT);
        log.debug("register key:{}", sscKey);
        ssc.bind(new InetSocketAddress(8080));

        while (true) {
            //3. selector. The select () method blocks when there is no event. Once the event is sent, the operation will be resumed and the downward processing will continue
            //The problem of wasting CPU in white circulation is solved
            selector.select();

            //4. Handle events, and selectionKeys gets all readable and writable events
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

            //When there are multiple key s, the accept and read methods will trigger events, so the event types should be distinguished
            while(iterator.hasNext()){
                SelectionKey key = iterator.next();
                //When processing keys, delete them from the green selectkeys, otherwise an error will be reported
                iterator.remove();
                log.debug("key:{}", key);

                //5. Distinguish event types
                if (key.isAcceptable()) {
                    //Get the channel that triggered the event
                    ServerSocketChannel channel = (ServerSocketChannel)key.channel();
                    SocketChannel sc = channel.accept();
                    //Set to non blocking
                    sc.configureBlocking(false);
                    //channel of sc key tube
                    //A buffer is associated with the SelectionKey to prevent multiple channel s from using a buffer at the same time
                    ByteBuffer buffer1 = ByteBuffer.allocate(16);   //Attachment: attachment
                    SelectionKey scKey = sc.register(selector, 0, buffer1);
                    //scKey pays attention to read events, that is, the channel of the client pays attention to read events
                    scKey.interestOps(SelectionKey.OP_READ);
                    log.debug("{}", sc);
                }else if(key.isReadable()){
                    //After the client is closed, a read event will also be triggered. At this time, you need to remove the key, otherwise you can't get the channel and an error will be reported
                    try {
                        //Readable event
                        SocketChannel channel = (SocketChannel)key.channel();//The channel that triggered the event
                        //Get unique ByteBuffer attachment from SelectionKey
                        ByteBuffer buffer1 = (ByteBuffer)key.attachment();
                        //The client is disconnected normally, and the return value of read is - 1
                        int read = channel.read(buffer1);
                        if(read == -1){
                            //Normal disconnection
                            key.cancel();
                        }else{
                            split(buffer1);
                            if(buffer1.position() == buffer1.limit()){
                                //Capacity expansion
                                ByteBuffer newBuffer = ByteBuffer.allocate(buffer1.capacity() * 2);
                                buffer1.flip();
                                newBuffer.put(buffer1);
                                //Replace attachment
                                key.attach(newBuffer);
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        key.cancel();   //When the client is disconnected, you need to cancel the key (delete it from the key collection of the selector)
                    }
                }

            }
        }
    }
}



3. ByteBuffer size allocation

  • Each channel needs to record messages that may be segmented. Because ByteBuffer cannot be used by multiple channels, it is necessary to maintain an independent ByteBuffer for each channel
  • The ByteBuffer cannot be too large. For example, if a ByteBuffer is 1MB, 1Tb memory is required to support millions of connections. Therefore, a ByteBuffer with variable size needs to be designed

The idea of size allocation is as follows:

  1. One idea is to allocate a smaller buffer, such as 4k, first. If you find that the data is insufficient, allocate 8k buffer and copy the 4k buffer content to 8k buffer. The advantage is that the messages are continuous and easy to process, but the disadvantage is that the data copy consumes performance. Refer to the implementation: Assign address
  2. Another idea is to use multiple arrays to form a buffer. If one array is not enough, write the extra contents into a new array. The difference from the previous one is that the message storage is discontinuous and the parsing is complex. The advantage is to avoid the performance loss caused by copying



4. write event

1. The initial code is written continuously using the while loop

public class WriteServer {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);

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

        ssc.bind(new InetSocketAddress("localhost", 8080));

        while(true){
            selector.select();

            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while(iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();
                if(key.isAcceptable()){
                    //This is actually ssc, because there is only one OP_ACCEPT
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    //Send a large amount of data to the client
                    StringBuilder sb = new StringBuilder();
                    for(int i = 0; i < 3000000; i++){
                        sb.append("a");
                    }
                    ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());
                    //The return value is the number of bytes actually written
                    while(buffer.hasRemaining()){
                        //2. Write to client
                        int write = sc.write(buffer);
                        System.out.println("Bytes:" + write);
                    }
                }
            }
        }
    }
}

Client acceptance

public class WriteClinent {
    public static void main(String[] args) throws IOException {
        SocketChannel sc = SocketChannel.open();
        sc.connect(new InetSocketAddress("localhost", 8080));
        ByteBuffer buffer = ByteBuffer.allocate(1024*1024);
        int count = 0;
        //3. Accept data
        while(true){
            count += sc.read(buffer);
            System.out.println(count);
            buffer.clear();
        }
    }
}

2. Improvement
The above method can be written, but we have an idea that we can send data to the client when the buffer is completely written, rather than sending a little bit after the buffer is written, which will consume a lot of CPU resources.
The idea of the step is: first store 50 million bytes of data in the buffer, then write once at the beginning, add the concerned event to the writable event (writable as long as it is not blocked), and enter the key the second time If the iswritable judgment is successful, enter it and start writing the buffer, and then continue to cycle to judge whether to write the buffer. After writing, we need to clear the buffer from the key and set the attach to null, because if the buffer memory is too large, it will affect our efficiency and memory loss.

public class WriteServer {
    public static void main(String[] args) throws IOException {
    	//Create a channel
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //Set to non blocking
        ssc.configureBlocking(false);
		//selector
        Selector selector = Selector.open();
        //Register binding selector
        ssc.register(selector, SelectionKey.OP_ACCEPT);
		//Binding port number
        ssc.bind(new InetSocketAddress("localhost", 8080));
		
        while(true){
        	//Wait event
            selector.select();
			//iteration
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while(iterator.hasNext()){
            	//Get SelectionKey 
                SelectionKey key = iterator.next();
                iterator.remove();
                //Connection event
                if(key.isAcceptable()){
                    //This is actually ssc, because there is only one OP_ACCEPT
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    SelectionKey sckey = sc.register(selector, 0, null);

                    //Send a large amount of data to the client
                    StringBuilder sb = new StringBuilder();
                    for(int i = 0; i < 5000000; i++){
                        sb.append("a");
                    }
                    ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());
                    //2. Write to client
                    int write = sc.write(buffer);
                    System.out.println("Bytes:" + write);
                    //3. Judge whether there is any remaining content, because the data that can be written to the channel at one time is limited, and the contents in the buffer may not be finished
                    //If there is still data in the buffer, don't write it. Just keep it until the next wave is enough
                    if(buffer.hasRemaining()){
                        //4. Pay attention to writable events while retaining the original events
                        sckey.interestOps(sckey.interestOps() | SelectionKey.OP_WRITE);
                        //5. Hang the unfinished data on the selectionkey
                        sckey.attach(buffer);
                    }
                    //Writable events are actually writable events as long as they are not blocked
                }else if(key.isWritable()){
                //Get the buffer. The buffer has some data that has not been written out
                    ByteBuffer buffer = (ByteBuffer)key.attachment();
                    //Get channel
                    SocketChannel sc = (SocketChannel)key.channel();
					//Write buffer
                    int write = sc.write(buffer);
                    System.out.println(write);
                    //6. When the buffer is written, it is cleared to prevent memory occupation
                    if(!buffer.hasRemaining()){
                        key.attach(null);   //Clear buffer
                        key.interestOps(SelectionKey.OP_READ);//No longer focus on writable events
                    }
                }
            }
        }
        //Number of bytes: 3276775
        //1179639
        //543586
    }
}
public class WriteClinent {
    public static void main(String[] args) throws IOException {
        SocketChannel sc = SocketChannel.open();
        sc.connect(new InetSocketAddress("localhost", 8080));
        ByteBuffer buffer = ByteBuffer.allocate(1024*1024);
        int count = 0;
        //3. Accept data
        while(true){
            count += sc.read(buffer);
            System.out.println(count);
            buffer.clear();
        }
    }
}



5. Summary

1. Bind Channel

It is also called registration event. Only the bound event selector cares

//Create a channel
ServerSocketChannel ssc = ServerSocketChannel.open();
//Set to non blocking
ssc.configureBlocking(false);
//selector
Selector selector = Selector.open();
//Register binding selector
ssc.register(selector, SelectionKey.OP_ACCEPT);
  • channel must work in non blocking mode
  • FileChannel does not have a non blocking mode, so it cannot be used with a selector
  • Bound event types can include
    1. connect: triggered when the client connection is successful
    2. accept: triggered when the server successfully accepts the connection
    3. Read: triggered when data can be read in. Data cannot be read in temporarily because of weak acceptance ability
    4. write: triggered when data can be written out. Data cannot be written out temporarily because of weak sending ability



2. Listen to Channel events

You can listen for events through the following three methods. The return value of the method represents how many channel s have events
Method 1. Block binding event sending:

int count = selector.select();

Method 2. Block until binding event occurs or timeout (unit: ms):

int count = selector.select(long timeout);

Method 3. No blocking, that is, whether there is an event or not, return immediately, and judge whether there is an event according to the return value:

int count = selector.selectNow();

When is select not blocked?

  • When the incident happened
    1. When a client initiates a connection request, an accept event will be triggered
    2. When the client sends data, the read event will be triggered when the client is normal or abnormally closed. In addition, if the sent data is larger than the buffer, multiple read events will be triggered
    3. If the channel is writable, the write event will be triggered. It can be written as long as it is not blocked.
    4. When nio bug occurs under linux
  • Call selector wakeup()
  • Call selector close()
  • interrupt of the thread where the selector is located

Topics: Java Netty network server