IO学习笔记5

2.2 NIO

由于上面BIO的弊端,以及为了解决C10K的问题,出现了NIO模型(NonBlockingIO)。java中的nio指new io,而linux中的nio指NonblockingIO。

NIO是同步非阻塞模型。

代码如下:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;

/**
 * @author shuai.zhao@going-link.com
 * @date 2021/6/2
 */
public class SocketNIO {
    public static void main(String[] args) throws IOException {

        ArrayList<SocketChannel> clients = new ArrayList<>();

        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(9000));

        ssc.configureBlocking(false);

        while (true) {
          // 如果没有客户端连接进来则返回null
            SocketChannel newClient = ssc.accept();
            if (newClient != null) {
                System.out.println("client " + newClient.getRemoteAddress() + " is in");
                newClient.configureBlocking(false);
                clients.add(newClient);
            }

            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
            for (SocketChannel client : clients) {
                int read = client.read(byteBuffer);
                if (read > 0) {
                    byteBuffer.flip();
                    byte[] bytes = new byte[byteBuffer.limit()];
                    byteBuffer.get(bytes);
                    System.out.println("client:" + client.socket().getPort() + " write: " + new String(bytes));
                    byteBuffer.clear();
                }
            }
        }
    }
}

用一个线程去处理10K个客户端连接。

再次使用strace去追踪启动服务端,查看主线程系统调用:

........
accept(4, 0x7f2fbc0d21a0, [28])         = -1 EAGAIN (资源暂时不可用)
accept(4, 0x7f2fbc0d4d50, [28])         = -1 EAGAIN (资源暂时不可用)
accept(4, 0x7f2fbc72f070, [28])         = -1 EAGAIN (资源暂时不可用)
accept(4, 0x7f2fbc0d19e0, [28])         = -1 EAGAIN (资源暂时不可用)
accept(4, 0x7f2fbc0d21a0, [28])         = -1 EAGAIN (资源暂时不可用)
accept(4, 0x7f2fbc0d4d50, [28])         = -1 EAGAIN (资源暂时不可用)
accept(4, 0x7f2fbc72f070, [28])         = -1 EAGAIN (资源暂时不可用)
accept(4, 0x7f2fbc0d19e0, [28])         = -1 EAGAIN (资源暂时不可用)

可以看到服务端是不再阻塞的,每次循环accept()方法如果没有接收到客户端连接,就返回-1,对应java里就返回null,然后继续下一次循环。使用nc连接服务端,发送数据,然后可以在主线程的系统调用文件out.13101中找到发送的数据,而且后续调用为:

accept(4, 0x7f2fbc0d19e0, [28])         = -1 EAGAIN (资源暂时不可用)
read(5, 0x7f2fbc0ff6a0, 1024)           = -1 EAGAIN (资源暂时不可用)
accept(4, 0x7f2fbcea0cb0, [28])         = -1 EAGAIN (资源暂时不可用)
read(5, 0x7f2fbc0ffab0, 1024)           = -1 EAGAIN (资源暂时不可用)
accept(4, 0x7f2fbc0d4d50, [28])         = -1 EAGAIN (资源暂时不可用)
read(5, 0x7f2fbc0ffec0, 1024)           = -1 EAGAIN (资源暂时不可用)
accept(4, 0x7f2fbc0d21a0, [28])         = -1 EAGAIN (资源暂时不可用)
read(5, 0x7f2fbc1002d0, 1024)           = -1 EAGAIN (资源暂时不可用)
accept(4, 0x7f2fbc0d19e0, [28])         = -1 EAGAIN (资源暂时不可用)
read(5, 0x7f2fbc1006e0, 1024)           = -1 EAGAIN (资源暂时不可用)

同一个线程同时accept和从客户端read数据,没有时都返回null。

使用C10K客户顿端调用,服务端控制台输出结果如下:

// 服务端
client /127.0.0.1:20210 is in
client /127.0.0.1:20211 is in
client /127.0.0.1:20212 is in
Exception in thread "main" java.io.IOException: Too many open files
	at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
	at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:424)
// 客户端
connection time consuming:52739
clients = 10214

(ps: 因为我是本地调用,所以可用fd不够,可以在linux下通过cat /proc/sys/fs/file-max命令查看当前用户可打开的fd数量,使用root用户时这个数量非常大,可以正常连接完这10w个连接数的。但是NIO仍无法避免的问题一个问题就是 fd文件不够,因为在生产环境不可能使用root用户来启动服务)

虽然本地无法启动建立完所有的连接,但是使用在linux上是可以建立的。我们使用c10K问题主要是为了看IO效率,至于fd不够的问题,通过扩容来解决(一台不够就再加一台),但是在使用NIO的时候,瓶颈并不在连接数上,因为只从fd考虑,NIO单台服务器就可以支持十万。从效率上看,我本地建立10214个连接耗时 52739毫秒。这里记一下下面我们会有更高效的方法

原文地址:https://www.cnblogs.com/Zs-book1/p/14889521.html