java network programming-nio learning: blocking and non-blocking

1. Blocking

  • In blocking mode, related methods will cause the thread to pause.

    • ServerSocketChannel.accept will cause the thread to pause when no connection is established

    • SocketChannel.read will cause the thread to pause when there is no data to read

    • The performance of blocking is actually that the thread is paused. The CPU will not be occupied during the pause, but the thread is equivalent to being idle.

  • Under single thread, blocking methods affect each other and can hardly work properly, requiring multi-thread support.

  • But under multi-threading, there are new problems, which are reflected in the following aspects:

    • A 32-bit JVM has a thread of 320k, and a 64-bit JVM has a thread of 1024k. If there are too many connections, it will inevitably lead to OOM, and too many threads will lead to performance degradation due to frequent context switching.

    • Thread pool technology can be used to reduce the number of threads and thread context switching, but it only treats the symptoms but not the root cause. If there are many connections established but inactive for a long time, all threads in the thread pool will be blocked, so it is not suitable for long connections, only short connections.

Server-side example code:

// 使用 nio 来理解阻塞模式, 单线程
// 0. ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(16);
// 1. 创建了服务器
ServerSocketChannel ssc = ServerSocketChannel.open();

// 2. 绑定监听端口
ssc.bind(new InetSocketAddress(8080));

// 3. 连接集合
List<SocketChannel> channels = new ArrayList<>();
while (true) {
    // 4. accept 建立与客户端连接, SocketChannel 用来与客户端之间通信
    log.debug("connecting...");
    SocketChannel sc = ssc.accept(); // 阻塞方法,线程停止运行
    log.debug("connected... {}", sc);
    channels.add(sc);
    for (SocketChannel channel : channels) {
        // 5. 接收客户端发送的数据
        log.debug("before read... {}", channel);
        channel.read(buffer); // 阻塞方法,线程停止运行
        buffer.flip();
        debugRead(buffer);
        buffer.clear();
        log.debug("after read...{}", channel);
    }
}

client:

SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("localhost", 8080));
System.out.println("waiting...");

Two, non-blocking

  • In non-blocking mode, the relevant methods will not cause the thread to pause.

    • ServerSocketChannel.accept will return null when no connection is established and continue running.

    • SocketChannel.read will return 0 when there is no data to read, but the thread does not need to be blocked and can execute other SocketChannel reads or execute ServerSocketChannel.accept

    • When writing data, the thread just waits for the data to be written to the Channel. There is no need to wait for the Channel to send the data through the network.

  • But in non-blocking mode, even if there is no connection established and no readable data, the thread is still running continuously, wasting the CPU.

  • During the data copying process, the thread is actually blocked (an improvement of AIO)

Server-side demo code, client code remains unchanged

// 使用 nio 来理解非阻塞模式, 单线程
// 0. ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(16);
// 1. 创建了服务器
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false); // 非阻塞模式
// 2. 绑定监听端口
ssc.bind(new InetSocketAddress(8080));
// 3. 连接集合
List<SocketChannel> channels = new ArrayList<>();
while (true) {
    // 4. accept 建立与客户端连接, SocketChannel 用来与客户端之间通信
    SocketChannel sc = ssc.accept(); // 非阻塞,线程还会继续运行,如果没有连接建立,但sc是null
    if (sc != null) {
        log.debug("connected... {}", sc);
        sc.configureBlocking(false); // 非阻塞模式
        channels.add(sc);
    }
    for (SocketChannel channel : channels) {
        // 5. 接收客户端发送的数据
        int read = channel.read(buffer);// 非阻塞,线程仍然会继续运行,如果没有读到数据,read 返回 0
        if (read > 0) {
            buffer.flip();
            debugRead(buffer);
            buffer.clear();
            log.debug("after read...{}", channel);
        }
    }
}

3. Multiplexing

A single thread can cooperate with the Selector to monitor multiple Channel read and write events, which is called multiplexing.

  • Multiplexing is only for network IO and ordinary file IO. Multiplexing cannot be used.

  • If the non-blocking mode of Selector is not used, the thread will do useless work most of the time, but Selector can ensure

    • Only connect when there is a connectable event

    • Read only when there are readable events

    • Write only when there are writable events

      • Due to network transmission capabilities, the Channel may not always be writable. Once the Channel is writable, the Selector's writable event will be triggered.

benefit

  • A thread can monitor multiple channel events by using a selector, and the thread will process the event only when it occurs. Avoid wasted work in non-blocking mode

  • Let this thread be fully utilized

  • Saves the number of threads

  • Reduced thread context switching

 

create

Selector selector = Selector.open();

Binding Channel events

Also called a registration event, only the bound event selector will care.

channel.configureBlocking(false);
SelectionKey key = channel.register(selector, 绑定事件);
  • channel must work in non-blocking mode

  • FileChannel does not have non-blocking mode, so it cannot be used with selector.

  • The bound event types can be

    • connect - triggered when the client connects successfully

    • accept - triggered when the server successfully accepts the connection

    • read - Triggered when the data can be read in. Sometimes the data cannot be read in due to weak receiving ability.

    • write - Triggered when the data can be written out. Sometimes the data cannot be written out temporarily due to weak sending ability.

Listening to Channel events

You can use the following three methods to monitor whether an event occurs. The return value of the method represents how many channels the event has occurred.

Method 1, block until the binding event occurs (commonly used)

int count = selector.select();

Method 2, block until the binding event occurs or times out (time unit is ms)

int count = selector.select(long timeout);

Method 3 will not block, that is, whether there is an event or not, it will return immediately and check whether there is an event based on the return value.

int count = selector.selectNow();

When does select not block

  • when the event happened

    • When the client initiates a connection request, the accept event will be triggered.

    • When the client sends data, the read event will be triggered when the client is closed normally or abnormally. In addition, if the data sent is larger than the buffer buffer, multiple read events will be triggered.

    • The channel is writable and will trigger the write event.

    • When nio bug occurs under linux

  • call selector. wakeup()

  • Call selector.close()

  • The thread where the selector is located interrupts

Guess you like

Origin blog.csdn.net/puzi0315/article/details/129119965