BIO和NIO的区别与实现

13 篇文章 4 订阅
订阅专栏
3 篇文章 0 订阅
订阅专栏

目录

目标

概述

实战

单线程版本的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,查看各种命令。

 第三步

向服务端发送数据。

博客
Docker数据卷使用手册
08-25 783
我们在很多网上教程上可以看到很多老师们往往将数据卷划分为三个种类:匿名卷、命名卷、绑定挂载。也有老师将数据卷分为四个种类:匿名卷、命名卷、绑定挂载、临时挂载。但是我去官方网站看了相关文档,文档中十分明确地将绑定挂载(Bind mounts)、数据卷(Volumes)、临时挂载(tmpfs mounts)规定为三个类别。大家学习绑定挂载和临时挂载时,通过docker inspect命令查看容器详情时也会发现Mounts中的Type值为bind。而数据卷的Type值为volume。但是对于这种概念性的东西每个人
博客
Docker绑定挂载使用手册
08-25 939
掌握绑定挂载、临时挂载的常用命令。理解绑定挂载、数据卷、临时挂载之间的区别。
博客
Docker基础命令
08-11 883
Docker是一个用于开发、发布和运行应用程序的开放平台。Docker可让您将应用程序与基础架构分离,以便快速交付软件。借助Docker,您可以像管理应用程序一样管理基础架构。通过利用Docker 的发布、测试和部署代码方法,您可以显著减少编写代码和在生产中运行代码之间的延迟。
博客
FastDFS安装与测试
01-06 1254
在Linux服务器上搭建单机版FastDFS系统。(考虑到Linux服务器访问GitHub受限,这里我将依赖包下载到本地,再将本地的依赖包上传到Linux服务器上。所以我的操作与Wiki上的操作略有不同,但本质一样)。在Linux服务器上,使用命令行实现文件上传、下载、删除等功能。熟悉FastDFS系统架构。
博客
Python字典类型
11-28 1213
掌握字典类型的使用方法,包括:创建、循环、常用方法等操作。
博客
Python集合类型
11-26 1066
掌握set和frozenset两种集合的使用方法,包括:创建、交集、并集、差集等操作。
博客
Python序列类型
11-24 907
掌握Python序列类型(list、tuple、range)的常用方法(创建、循环、查询、删除、切片等)。
博客
Python字符串类型
11-17 291
掌握字符串类型的使用方法。本文参考官方文档列举了各种字符串方法的使用方法,如有错误烦请指正。
博客
Python数字类型
10-22 319
掌握Python两种数据类型的使用方法。数字类型有三种,分别是:整数(int)浮点数(float)复数(complex)另外,布尔值类型(bool)是整数类型的子类型。
博客
用Netty搭建文件上传系统
05-23 1795
客户端向服务端发送一个文件(可以是视频、音频、文本、表格等格式的文件),服务端接收文件并保存到指定的目录下。服务端保存好文件之后向客户端发送回应:xxx文件收到了。
博客
适配器模式知多少
05-15 488
熟悉适配器设计模式,了解适配器设计模式的使用场景、具体实现。
博客
装饰者设计模式知多少
05-13 541
熟悉装饰者设计模式,了解装饰者设计模式的使用场景、具体实现。
博客
策略设计模式知多少
04-26 838
熟悉策略设计模式,了解策略设计模式的使用场景、具体实现。
博客
观察者设计模式知多少
04-26 850
熟悉观察者设计模式,了解观察者设计模式的使用场景、具体实现(包括:推设计模式、拉设计模式、被动观察者设计模式)。
博客
责任链设计模式知多少
04-25 963
熟悉责任链设计模式,了解责任链设计模式的使用场景、具体实现,单链责任链设计模式和双链责任链设计模式的区别。
博客
用Netty做一个简单的聊天室程序
04-16 1151
一个服务端支持多个客户端同时连接,服务端关注客户端的在线,离线情况,客户端关注其他客户端的离线情况、在线情况,发送的消息。
博客
JAVA反射机制知多少
03-31 1126
了解反射机制的概念,通过反射的方式动态地获取类的名称、成员变量、构造方法、成员变量,注解信息。
博客
Netty黏包半包解决方案
03-24 1822
了解黏包半包发生的原因,了解各个解决方案适用的场景。
博客
Netty之ByteBuf应用详解
03-16 2678
掌握ByteBuf的常用方法。了解池化的ByteBuf和非池化的ByteBuf的区别。了解直接内存的ByteBuf和堆内存的ByteBuf的区别。掌握对ByteBuf的内存释放方法。
博客
Netty之ChannelHandler初解
03-07 1020
掌握ChannelHandler基本使用方法。熟悉入栈ChannelHandler和出栈ChannelHandler的执行顺序。分析ChannelHandlerContext和NioSocketChannel写入数据时有什么不同。了解Pipeline添加多个ChannelHandler有什么意义。
写文章

热门文章

  • 怎么求铅直渐近线 76601
  • 证明当x趋向于0时1-cosx的等价无穷小是(x^2/2) 62339
  • 史上最全的点线面距离公式与推导过程(图文介绍) 59844
  • 平面方程的几种形式及推导过程(总结) 59769
  • Windows版JMeter下载安装 58746

分类专栏

  • Docker 3篇
  • FastDFS 1篇
  • Python 4篇
  • 设计模式 5篇
  • Kibana 2篇
  • Elasticsearch 16篇
  • Netty 13篇
  • Kafka 8篇
  • JDK 5篇
  • I/O 3篇
  • MySQL 21篇
  • JVM 1篇
  • XXL-JOB 1篇
  • Oracle 1篇
  • Lua 1篇
  • ZooKeeper 5篇
  • RocketMQ
  • 空气质量指数
  • 并发编程 3篇
  • Nginx 1篇
  • RabbitMQ 2篇
  • MyBatis 1篇
  • Postman 2篇
  • MongoDB 1篇
  • Redis 7篇
  • Spring 2篇
  • 其他 6篇
  • 算法解析 2篇
  • 数据结构 4篇
  • 多线程 2篇
  • 支付 1篇
  • 消息队列 5篇
  • 高等数学 42篇
  • English 20篇
  • 易语言基础 2篇

最新评论

  • Docker数据卷使用手册

    CSDN-Ada助手: 云原生入门 技能树或许可以帮到你:https://edu.csdn.net/skill/cloud_native?utm_source=AI_act_cloud_native

  • Docker数据卷使用手册

    CSDN-Ada助手: 恭喜你这篇博客进入【CSDN每天值得看】榜单,全部的排名请看 https://bbs.csdn.net/topics/619275100。

  • FastDFS安装与测试

    CSDN-Ada助手: 恭喜你这篇博客进入【CSDN每天值得看】榜单,全部的排名请看 https://bbs.csdn.net/topics/617867581。

  • 史上最全的点线面距离公式与推导过程(图文介绍)

    uintuu: hero

  • 用Netty搭建文件上传系统

    我的身前一尺是我的世界: private static final String UPLOAD_DIR = "C:\\Users\\20203\\Desktop\\test2\\"

最新文章

  • Docker数据卷使用手册
  • Docker绑定挂载使用手册
  • Docker基础命令
2024年4篇
2023年23篇
2022年32篇
2021年29篇
2020年90篇
2019年39篇
2018年6篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

玻璃钢生产厂家玻璃钢瓜果雕塑哪里有卖的广州人物玻璃钢雕塑供应厂家玻璃钢雕塑用什么牌子好昆山商场活动美陈佛山会发光的玻璃钢雕塑玻璃钢雕塑工程产品介绍玻璃钢铸铜人物雕塑加工湛江玻璃钢抽象雕塑文山市玻璃钢雕塑佛像玻璃钢雕塑工业朔州玻璃钢雕塑提供玻璃钢动物雕塑三水玻璃钢人物雕塑免费咨询吉林省玻璃钢雕塑定制周口价值观玻璃钢仿铜雕塑上海周年庆典商场美陈批发价广东玻璃钢花盆售价百货商场美陈哪家好河北曲阳玻璃钢雕塑定制工厂温州室内商场美陈福建玻璃钢雕塑厂商水果玻璃钢雕塑批发弥勒市玻璃钢雕塑设计批发萍乡特色玻璃钢雕塑优势环保玻璃钢雕塑批发普洱市玻璃钢雕塑厂家建德商场美陈宝鸡大型玻璃钢雕塑厂家河南玻璃钢小羊动物雕塑定制商场功能性美陈香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化