NIO深入

  • 2022-08-11
  • 浏览 (500)

NIO深入

BIO

概念

在提到NIO之前,我们说先看看BIO,也就是Blocking IO,阻塞IO,我们首先实现一个最基本的网络通信

/**
 * QQ客户端
 *
 * @author: 陌溪
 * @create: 2020-03-28-11:09
 */
public class QQClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8080);
        socket.getOutputStream().write("我来发送".getBytes());
    }
}

/**
 * QQ客户端
 *
 * @author: 陌溪
 * @create: 2020-03-28-11:09
 */
public class QQServer {
    static byte[] bytes = new byte[1024];

    public static void main(String[] args) throws IOException {
        while(true) {
            ServerSocket serverSocket = new ServerSocket();

            // 绑定IP地址
            serverSocket.bind(new InetSocketAddress(8080));

            System.out.println("服务器等待连接....");

            // 阻塞
            Socket socket = serverSocket.accept();

            System.out.println("服务器连接....");

            // 阻塞
            System.out.println("等待发送客户端数据");
            socket.getInputStream().read(bytes);

            System.out.println("数据接收成功:" + new String(bytes));
        }
    }
}

首先运行Server端,然后在运行Client端

服务器等待连接....
服务器连接....
等待发送客户端数据
数据接收成功:我来发送 

通过运行结果我们能发现,服务端线程会进行两次阻塞,首先第一次就是在等待连接的时候会阻塞,然后是等待数据的时候又会阻塞。

因此在服务器端,不活跃的线程,比较多,所以我们考虑单线程

BIO怎么改成非阻塞

我们从上面的阻塞开始说起

while(true) {
ServerSocket serverSocket = new ServerSocket();

    // 绑定IP地址
    serverSocket.bind(new InetSocketAddress(8080));

    // 阻塞
    Socket socket = serverSocket.accept();

    // 阻塞
    socket.getInputStream().read(bytes);

}

假设我们现在访问淘宝网页,就相当于我们本机与淘宝的服务器建立了连接,然后淘宝服务器就会等待我的请求,但是假设我只是打开了网页,什么事情都不做,如果我的线程被一直阻塞的话,那就不能为服务进行接收了,这样就会卡在这里,但是我也不能为了这个不活跃的用户单独开启线程,因为他非常消耗我们的CPU资源,这样是会,非阻塞IO的用处就来了

在提到NIO之前,我们在将刚刚的QQServer改成非阻塞版本的伪代码,也就是我们通过一个关键字,设置他不阻塞,但是因为BIO里面没有这个方法,因此他就会一直阻塞着,所以把它称为阻塞的

// 设置非阻塞
serverSocket.setConfig();

伪代码如下:

/**
 * 单线程版服务器,NIO的伪代码
 *
 * @author: 陌溪
 * @create: 2020-03-28-12:04
 */
public class OneThreadServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress(8080));

        // 套接字列表,用于存储
        List<Socket> socketList = new ArrayList<>();
        // 缓冲区
        byte[] bytes = new byte[1024];

        // 设置非阻塞
//        serverSocket.setConfig();

        while(true) {
            // 获取连接
            Socket socket = serverSocket.accept();

            // 如果没人连接
            if(socket == null) {
                System.out.println("没人连接");

                // 遍历循环socketList,套接字list
                for(Socket item : socketList) {
                    int read = socket.getInputStream().read(bytes);
                    // 表示有人发送东西
                    if(read != 0) {
                        // 打印出内容
                        System.out.println(new String(bytes));
                    }
                }
            } else {
                // 如果有人连接,把套接字放入到列表中
                socketList.add(socket);

                // 设置非阻塞
//                serverSocket.setConfig();
                // 遍历循环socketList,套接字list

                // 遍历循环socketList,套接字list
                for(Socket item : socketList) {
                    int read = socket.getInputStream().read(bytes);
                    // 表示有人发送东西
                    if(read != 0) {
                        // 打印出内容
                        System.out.println(new String(bytes));
                    }
                }

            }
        }
    }
}

上述也提到了,因为BIO里面不提供不阻塞的方法,因此无法将其改成非阻塞的

NIO

但是在NIO里面,就提供了让其不阻塞的方法

在之前我们需要创建通信,BIO的方法如下所示:

ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(8080));

在NIO里面提出了通道的概念,其实代码和上面类似,只不过上面是创建了一个Socker连接,而下面是创建了一个通道

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8080));

我们创建的通道,默认是阻塞的,但是我们可以通过下面的方式,将其设置成非阻塞的

// 设置非阻塞
serverSocketChannel.configureBlocking(false);

下面我们进入while(true)的方法里面,因为原来是通过Socket获取到一个连接

// 获取连接
Socket socket = serverSocket.accept();

但是我们都知道,上述的连接是阻塞的,也就是说如果没有连接过来,它会一直阻塞的,因此Java提出了一个新的类,SockerChannel,它里面 提供了非阻塞的方法

// 设置非阻塞
serverSocketChannel.configureBlocking(false);

完整代码:

/**
 * NIO版QQ服务器
 *
 * @author: 陌溪
 * @create: 2020-03-28-12:16
 */
public class QQServerByNIO {
    public static void main(String[] args) throws IOException {
        // 创建一个通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8080));

        // 定义list用于存储SocketChannel,也就是非阻塞的连接
        List<SocketChannel> socketChannelList = new ArrayList<>();
        byte [] bytes = new byte[1024];
        // 缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        // 设置非阻塞
        serverSocketChannel.configureBlocking(false);

        while(true) {
            SocketChannel socketChannel = serverSocketChannel.accept();

            // 但无人连接的时候
            if(socketChannel == null) {

                // 睡眠一秒
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("无人连接");
                for(SocketChannel item: socketChannelList) {
                    int len = item.read(byteBuffer);
                    if(len > 0) {
                        // 切换成读模式
                        byteBuffer.flip();
                        // 打印出结果
                        System.out.println("读取到的数据" + new String(byteBuffer.array(), 0, len));
                    }
                    byteBuffer.clear();
                }

            } else {
                // 但有人连接的时候

                // 设置成非阻塞
                socketChannel.configureBlocking(false);

                // 将该通道存入到List中
                socketChannelList.add(socketChannel);

                for(SocketChannel item: socketChannelList) {
                    int len = item.read(byteBuffer);
                    if(len > 0) {
                        // 切换成读模式
                        byteBuffer.flip();
                        // 打印出结果
                        System.out.println("读取到的数据" + new String(byteBuffer.array(), 0, len));
                    }
                    byteBuffer.clear();
                }
            }
        }
    }
}

你可能感兴趣的文章

Vue如何使用G2绘制图片

Docker Compose入门学习

DockerDesktop入门简介

Docker图形化工具Portainer介绍与安装

1.Docker

Docker操作系统之Alpine

如何将镜像推送到阿里云容器镜像服务

对象存储MinIO入门介绍

ElasticSearch安装与介绍

Beats入门简介

0  赞