BIO和NIO的区别与实现

news2024/10/6 2:26:08

目录

目标

概述

实战

单线程版本的BIO

多线程版本的BIO

单线程NIO(简易版)

单线程NIO(多路复用版)

客户端发送数据的方法


目标

  • 了解BIO和NIO的区别和应用场景。
  • 分析BIO和NIO的线程模型,利用Socket实现BIO和NIO的核心功能。

概述

BIO(Blocking I/O)
        同步阻塞式IO。JDK1.4以前的IO模型。当客户端连接到服务端以后,服务端可以用单线程处理客户端连接,也可以用线程池处理客户端连接,但是它们都是一个线程以同步阻塞的方式处理一个客户端连接。
缺点:

  • 以同步阻塞的方式处理连接意味着一个线程需要处理一个客户端的连接事件和读写事件,且连接事件和读写事件都是阻塞的,即客户端与服务端建立连接后其他客户端的连接无法被处理(同一个线程下)。即使客户端与服务端建立连接后,客户端迟迟没有读写操作,这个线程也会继续阻塞等待。

优点:

  • 编程简单。
  • 适用于客户端较少的传统项目。

NIO(New I/O或Non Blocking I/O)
        同步非阻塞式IO。JDK1.4(含)以后的IO模型。
        普通的NIO只是将连接事件和读写事件设置为非阻塞,即将连接好的客户端放到一个集合里面,通过循环遍历所有客户端连接的方式处理客户端的请求。
        NIO配合多路复用器(Selector)以后,客户端的连接都会注册到Selector上,客户端的连接操作和读写操作会通过反应堆模式(Reactor)触发连接事件和读写事件。这些有连接操作和读写操作的客户端都会存在于一个集合中,通过循环遍历这个集合来针对性地处理客户端请求。
缺点:

  • 编程复杂。

优点:

  • 通过非阻塞的模式处理客户端的连接操作、读写操作和针对性地循环处理客户端请求,极大地提高了服务端的并发量。
  • 减少了不必要的线程建立。

实战

单线程版本的BIO

简介

一次只能处理一个连接,每个客户端都要发送消息才能轮到下一个客户端操作。

    /**
     * 单线程版本的BIO
     * @throws IOException
     */
    public void oneThreadBio() throws IOException {
        ServerSocket serverSocket = new ServerSocket(8099);
        for (; ; ) {
            log.info("这里会阻塞,等待客户端连接……");
            Socket socketClient = serverSocket.accept();
            log.info("客户端连接成功:{}", socketClient.getRemoteSocketAddress());
            byte[] bytes = new byte[1024];
            //这里会阻塞,等待客户端发送消息。
            //将客户端发过来的数据放入bytes
            int read = socketClient.getInputStream().read(bytes);
            if (read != -1) {
                String msg = new String(bytes, 0, read);
                log.info("收到消息:{}", msg);
            }
        }
    }

多线程版本的BIO

简介

一次处理多个连接,但是无限制地创建线程、客户端长时间不发送数据导致线程无法被销毁,会导致服务器崩溃。

    /**
     * 多线程版本的BIO
     * @throws IOException
     */
    public void multiThreadBio() throws IOException {
        ServerSocket serverSocket = new ServerSocket(8099);
        for (; ; ) {
            log.info("这里会阻塞,等待客户端连接……");
            Socket socketClient = serverSocket.accept();
            log.info("客户端连接成功:{}", socketClient.getRemoteSocketAddress());
            new Thread(() -> {
                byte[] bytes = new byte[1024];
                //这里会阻塞,等待客户端发送消息。
                //将客户端发过来的数据放入bytes
                int read = 0;
                try {
                    read = socketClient.getInputStream().read(bytes);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                if (read != -1) {
                    String msg = new String(bytes, 0, read);
                    log.info("收到消息:{}", msg);
                }
            }).start();
        }
    }

单线程NIO(简易版)

简介

将连接放入到集合中,通过循环集合的方式接收数据。监听不阻塞,读入数据也不阻塞。当客户端连接过多时,循环的次数也会很多,尤其是客户端没关闭,则客户但会一直存在于集合中,做了很多无用的循环。

    List<SocketChannel> socketChannelList = new ArrayList();
    /**
     * 单线程NIO(简易版)
     * @throws IOException
     */
    public void simpleNio() throws IOException {
        //创建socket服务端
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //绑定端口
        serverSocketChannel.bind(new InetSocketAddress(8099));
        //false=非阻塞;true=阻塞。
        serverSocketChannel.configureBlocking(false);
        for (; ; ) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            if (socketChannel != null) {
                log.info("连接成功。{}", socketChannel.getRemoteAddress());
                //false=非阻塞;true=阻塞。
                socketChannel.configureBlocking(false);
                socketChannelList.add(socketChannel);
            }
            if (CollectionUtils.isEmpty(socketChannelList)) {
                continue;
            }
            Iterator<SocketChannel> iterator = socketChannelList.iterator();
            while (iterator.hasNext()) {
                ByteBuffer bb = ByteBuffer.allocate(16);
                SocketChannel channel = iterator.next();
                //把数据读入到ByteBuffer中
                int read = channel.read(bb);
                if (read > 0) {
                    //切换到读模式
                    bb.flip();
                    log.info("收到消息:{}", StandardCharsets.UTF_8.decode(bb));
                    bb.clear();
                } else if (read == -1) {
                    iterator.remove();
                    log.info("客户端退出。{}", channel.getRemoteAddress());
                }
            }
        }
    }

单线程NIO(多路复用版)

简介

在上一个NIO案例上加入了多路复用器,即将Channel注册到Selector上。客户端的连接操作和读写操作,会通过反应堆模式(Reactor)触发连接事件和读写事件(如果Channel注册了连接事件、读事件、写事件)。服务端会针对性地遍历客户端操作,减少了不必要的空循环,使得一个线程也能处理多个客户端连接。

    /**
     * 单线程NIO(多路复用版)
     * @throws IOException
     */
    public void selectorNio() throws IOException {
        //创建socket服务端
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //绑定端口
        serverSocketChannel.bind(new InetSocketAddress(8099));
        //false=非阻塞;true=阻塞。
        serverSocketChannel.configureBlocking(false);
        //这是JDK提供的选择器(用于选择事件)
        Selector selector = Selector.open();
        //将服务端的serverSocketChannel注册到Selector上,关注的事件:连接事件。
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        for (; ; ) {
            //这里会阻塞,一旦客户端和服务器有了数据传递,则向下运行。
            selector.select();
            //
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> selectionKeyiterator = selectionKeys.iterator();
            while (selectionKeyiterator.hasNext()) {
                SelectionKey selectionKey = selectionKeyiterator.next();
                if (selectionKey.isAcceptable()) {//发生了连接事件
                    ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel socketChannel = ssc.accept();
                    //false=非阻塞;true=阻塞。
                    socketChannel.configureBlocking(false);
                    //读事件(如果服务端还想要向客户端发消息,可以再多注册一个写事件。)
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    log.info("连接建立成功:{}", socketChannel.getRemoteAddress());
                } else if (selectionKey.isReadable()) {//发生了读事件
                    ByteBuffer bb = ByteBuffer.allocate(16);
                    SocketChannel socketChannel= (SocketChannel) selectionKey.channel();
                    int read = socketChannel.read(bb);
                    if (read > 0) {
                        //切换到读模式
                        bb.flip();
                        log.info("收到消息:{}", StandardCharsets.UTF_8.decode(bb));
                        bb.clear();
                    }else if(read ==-1){
                        socketChannel.close();
                        log.info("客户端连接断开。");
                    }
                }
                selectionKeyiterator.remove();
            }
        }
    }

客户端发送数据的方法

简介

大家可以写客户端代码与服务端交互。这里我通过cmd.exe窗口模拟客户端向服务端发送数据。一个黑窗口就是一个客户端,启动2个黑窗口向服务端发送数据可以明显看出BIO和NIO的区别。

第一步

打开cmd.exe窗口,根据服务端绑定的端口,使用telnet向服务端发起连接。

 第二步 

按Ctrl+]组合键,输入help,查看各种命令。

 第三步

向服务端发送数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/426089.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pytorch 笔记

1.python文件、python控制台Terminal、jupyter代码执行比较 2.Dataset和Dataloader dataset存储数据集&#xff0c;dataloader从数据集中批量加载数据&#xff0c;如 把 dataset 放入 DataLoaderloader Data.DataLoader(datasettorch_dataset, # torch TensorDataset forma…

MIPI D-PHYv2.5笔记(17) -- Global Operation Flow Diagram、数据速率依赖参数

声明&#xff1a;作者是做嵌入式软件开发的&#xff0c;并非专业的硬件设计人员&#xff0c;笔记内容根据自己的经验和对协议的理解输出&#xff0c;肯定存在有些理解和翻译不到位的地方&#xff0c;有疑问请参考原始规范看 Global Operation Flow Diagram 图1 Data Lane Mod…

【Linux】线程概念详析

我们已经了解了Linux操作系统进程部分相关知识&#xff1a; 博主有关Linux进程相关介绍的文章&#xff1a; &#x1f4a5;[Linux] 系统进程相关概念、系统调用、Linux进程详析、进程查看、fork()初识 &#x1f4a5;[Linux] 进程状态相关概念、Linux实际进程状态、进程优先级 …

【超算/先进计算学习】日报1

目录今日已完成任务列表遇到的问题及解决方案任务完成详细笔记Darknet框架优化介绍darknet介绍YOLO高性能计算与超级计算机简介算力超级计算机概念与体系结构并行编程技术Linux常用操作命令Linux操作系统与指令使用机器信息查询文件、目录和权限文件内容查看环境变量使用对自己…

15.枚举

枚举 枚举是Java1.5引入的新特性&#xff0c;通过关键字enum来定义枚举类。枚举类是一种特殊类&#xff0c;它和普通类一样可以使用构造方法、定义成员变量和方法&#xff0c;也能实现接口,但枚举类不能继承其他类。枚举是一个被命名的整型常数的集合&#xff0c;用于声明一组…

Linux系统下安装zookeeper教程

将下载好的zookeeper上传到linux服务器上&#xff0c;这里使用的是FileFilla 解压&#xff1a;[rootcentos7964 software]# tar -zxvf apache-zookeeper-3.7.1-bin.tar.gz 进入刚刚解压后的文件夹&#xff0c;创建一个名字为data的文件夹&#xff0c;如下&#xff1a; [rootc…

分库分表--shardingjdbc

文章目录前言一、shardingjdbc简介作用二、如何使用1.我有个表现在体量太大了,我想做分库分表2.开始改造1 引入shardingjdbc2 更改yml文件3 测试看效果3.旧数据迁移4.其他分库类型5 部分配置说明总结前言 当项目开始的时候,没有想到后续的分库分表的话,其实对于后续的分库分表…

MYSQL学习 - DDL数据库操作

前言 从今天开始, 健哥就带各位小伙伴学习数据库技术。数据库技术是Java开发中必不可少的一部分知识内容。也是非常重要的技术。本系列教程由浅入深, 全面讲解数据库体系。 非常适合零基础的小伙伴来学习。 ------------------------------前戏已做完&#xff0c;精彩即开始---…

【数据结构】树和二叉树——堆

目录 &#x1f349;一.树的概念及结构&#x1f349; 1.树的概念 2.树的相关术语 3.树的表示 4.树在实际中的应用 &#x1f34a;二.二叉树的概念和结构&#x1f34a; 1.二叉树的概念 2.特殊的二叉树 2.1.满二叉树 2..2.完全二叉树 3.二叉树的性质 4.二叉树的存储结构 …

限流算法(计数器、滑动时间窗口、漏斗、令牌)原理以及代码实现

文章目录前言1、计数器&#xff08;固定时间窗口&#xff09;算法原理代码实现存在的问题2、滑动时间窗口算法原理代码实现存在的问题3、漏桶算法原理代码实现存在的问题4、令牌桶算法原理代码实现最后本文会对这4个限流算法进行详细说明&#xff0c;并输出实现限流算法的代码示…

一文了解Java ArrayList (源码逐行解析)

介绍 ArrayList 是最常用的 List 实现类&#xff0c;内部是通过数组实现的&#xff0c;它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔&#xff0c;当数组大小不满足时需要增加存储能力&#xff0c;就要将已经有数组的数据复制到新的存储空间中。当从 Arr…

c++积累5-lock_guard使用

1、std:mutex 在了解lock_guard之前&#xff0c;需要先学习下std:mutex,因为lock_guard内部就是使用的std:mutex std:mutex&#xff1a;是一个用于保护共享数据不会同时被多个线程访问的类&#xff0c;它叫做互斥量。我们可以把它看作一把锁&#xff0c;基本使用如下&#xff…

【致敬未来的攻城狮计划】— 连续打卡第四天:e2 studio 使用教程

系列文章目录 1.连续打卡第一天&#xff1a;提前对CPK_RA2E1是瑞萨RA系列开发板的初体验&#xff0c;了解一下 2.开发环境的选择和调试&#xff08;从零开始&#xff0c;加油&#xff09; 3.欲速则不达&#xff0c;今天是对RA2E1 基础知识的补充学习。 文章目录 系列文章目录 文…

Flutter成不了“顶流明星”的7大理由

Flutter是一款由Google推出的跨平台移动应用开发框架&#xff0c;近年来备受关注。尽管Flutter在某些方面表现出色&#xff0c;但仍然有一些人对它的发展前景表示怀疑。近期一些文章针对Flutter的发展提出了不少质疑和批评&#xff0c;称其难以成为移动应用开发的“顶流明星”&…

【计算机网络—— 安装packet tracer的教程,管理MAC地址和配置路由器的实验步骤以及心得体会】

文章目录安装packet tracer的教程管理MAC地址实验目的 &#xff1a;管理MAC地址实验内容及过程记录&#xff08;一&#xff09;运行Cisco Packet Tracer软件&#xff08;二&#xff09;选择PC终端&#xff08;三&#xff09;运行终端&#xff08;四&#xff09;按表内容对交换机…

Spring Cloud Nacos使用总结

目录 安装Nacos服务器 服务发现与消费 服务发现与消费-添加依赖 服务发现-配置文件 服务发现-注解 服务发现-Controller 服务消费-配置文件 服务消费-注解与Ribbon消费代码 服务消费-运行 配置管理 配置管理-添加依赖 配置管理-配置文件 配置管理-注解 配置管理-…

Raft一致性算法(精简和扩展)

raft一致性算法 文章目录raft一致性算法一、raft简介1.1 raft涉及到的名词1.2 Rpc请求1.3 复制状态机二、raft⼀致性算法2.0 摘要2.0.1 所有服务器需遵守的规则2.0.2 跟随者2.0.3 候选⼈2.0.4 领导人2.0.5 状态2.0.6 特性2.1 raft基础2.2 leader选举2.2.1 集群启动时选举2.2.2 …

微电网两阶段鲁棒优化经济调度方法(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

清华大学ChatGLM-6B部署运行

一、模型介绍 开源项目&#xff1a; GitHub - THUDM/ChatGLM-6B: ChatGLM-6B&#xff1a;开源双语对话语言模型 | An Open Bilingual Dialogue Language Model ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM) 架构&…

PLC高精度定时器(T法测速功能块)

S7-200 SMART PLC时间间隔指令BGN_ITIME,和CAL_ITIME采用的是系统自带的1ms高精度定时器,PLC里只能调用一次。T法测速和M法测速应用时,都需要高精度时序定时器的支持(当然你也可以采用定时中断的方式获取高精度时序),时间间隔指令请参看下面的博客文章: SMART PLC时间间…