代码分析Java中的BIO与NIO

开发环境 

OS:Win10(需要开启telnet服务,或使用第三方远程工具)

Java版本:8

BIO

概念

BIO(Block IO),即同步阻塞IO,特点为当客户端发起请求后,在服务端未处理完该请求之前,客户端将一直等待服务端的响应。而服务端在此时也专注于该请求的处理,无法处理其它客户端的请求。

示例

package com.mlyzr.bio;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;

/**
 * @author 勿忘初心
 * @since 2023-08-07-23:59
 * 同步阻塞IO示例
 */
public class BioServer {

    public static void main(String[] args) throws IOException {
        // 监听本地8096端口
        ServerSocket serverSocket = new ServerSocket(8096);
        while(true){
            System.out.println("等待客户端连接...");
            // 接收客户端请求,阻塞
            Socket clientSocket = serverSocket.accept();
            System.out.println("监听到客户端连接");
            // 使用NIO处理请求信息
            handler(clientSocket);
        }
    }

    public static void handler(Socket clientSocket) throws IOException {
        byte[] bytes = new byte[1024];
        System.out.println("准备读取");
        int read = clientSocket.getInputStream().read(bytes);
        System.out.println("读取完毕");
        if(read != -1){
            System.out.println("接收到来自客户端的数据 "+new String(bytes,0,read,StandardCharsets.UTF_8));
        }
    }
}

在IDEA运行上述代码后,将在控制台看如下输出

控制台输出

此时先打开第一个客户端,这里使用telnet连接,具体操作为使用快捷键 Win+R 打开命令行,输入cmd,输入telnet localhost 8096(代码中绑定的端口)后回车,按下 ctrl+] (ctrl+ 右括号)即可进入telnet的交互模式,可向服务端发送信息。(telnet命令报错请使用自行搜索开启telnet服务,或使用其它工具如mobxterm等)

注意:cmd工具的telnet模式下无法发送中文字符,会出现乱码现象,如需发送中文字符请使用MobaXterm等第三方工具进行测试。

新建客户端

 客户端

当客户端成功连接后,服务端控制台输出将由等待状态变为读取状态,等待当前客户端发送消息。

此时再新建一个客户端连接,服务端控制台输出依旧不会发生任何改变, 因为第一个客户端的资源处理还没有结束。

在第一个客户端中使用send命令向服务端发送消息(不熟悉telnet命令的话可以输入help查看)可以看到服务端已经接收到发送的 client1 字符串后输出,并且之前的第二个客户端的连接目前可以被处理,同样进入准备读取的状态。

使用第二个客户端发送消息,此时服务端正常输出。

优化

不难发现上述操作中存在的问题,服务端一次只能处理一个客户端的请求,其余的客户端只能等待,当某一个客户端请求的资源处理耗时较长时,对于其它的客户端使用是非常糟糕的,这里可以通过使用多线程方式来进行优化。
package com.mlyzr.bio;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * @author 勿忘初心
 * @since 2023-08-07-23:59
 * 同步阻塞IO示例
 */
public class BioServer {

    public static void main(String[] args) throws IOException {
        // 监听本地8096端口
        ServerSocket serverSocket = new ServerSocket(8096);
        while(true){
            System.out.println("等待客户端连接...");
            // 接收客户端请求,阻塞
            Socket clientSocket = serverSocket.accept();
            System.out.println("监听到客户端连接");

            // 使用多线程来解决系统处理资源的能力
            new Thread(new Runnable(){
                @Override
                public void run() {
                    try {
                        // NIO处理请求资源
                        handler(clientSocket);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
            ).start();
        }


    }

    public static void handler(Socket clientSocket) throws IOException {
        byte[] bytes = new byte[1024];
        System.out.println("准备读取");
        int read = clientSocket.getInputStream().read(bytes);
        System.out.println("读取完毕");
        if(read != -1){
            System.out.println("接收到来自客户端的数据 "+new String(bytes,0,read,StandardCharsets.UTF_8));
        }
    }
}

 使用多线程后,可以看到服务端能够同时监听多个客户端的请求。

总结

使用改进后的代码通过测试可以发现服务端具备了同时处理多个客户端的能力,但同时仍然存在一些问题:

1.当请求非常大时,服务端将因为线程太多无法处理而导致崩溃。

2.即使使用了线程池,当线程池占满后服务将于单线程处理无异。

3.客户端只连接不发送数据,将一直占用服务端资源造成资源浪费。

NIO

概念

同步非阻塞IO,特点为当客户端发起请求后,在此期间客户端可以做其它的操作,但需要主动轮询服务端的处理结果,而且服务端也无需专注于当前请求的处理,也可以处理其他请求。

示例

package com.mlyzr.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.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @author 勿忘初心
 * @since 2023-08-08-15:28
 * 同步非阻塞IO示例代码
 */
public class NioServer {

    public static void main(String[] args) throws IOException {
        // 保存客户端的Channel集合
        List<SocketChannel> channelList = new ArrayList<>();
        // 创建NIO的ServerSocketChannel,与BIO的ServerSocket类似
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.socket().bind(new InetSocketAddress(8096));
        // 设置channel为非阻塞
        serverChannel.configureBlocking(false);
        System.out.println("等待客户端连接");

        while(true){
            // 非阻塞模式的accept方法不会阻塞
            SocketChannel socketChannel = serverChannel.accept();
            // 客户端是否连接
            if(socketChannel != null){
                System.out.println("监听到客户端连接");
                socketChannel.configureBlocking(false);
                // 将客户端连接保存到list中
                channelList.add(socketChannel);
            }

            Iterator<SocketChannel> iterator = channelList.iterator();
            while(iterator.hasNext()){
                SocketChannel sc = iterator.next();
                ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                int read = sc.read(byteBuffer);
                // 若存在数据,则将数据打印出来
                if(read > 0){
                    System.out.println("接收到客户端数据"+new String(byteBuffer.array(),0,read,StandardCharsets.UTF_8));
                }
                // 无数据则说明客户端已断开
                if(read == -1){
                    iterator.remove();
                    System.out.println("客户端已断开连接");
                }
            }
        }

    }
}

客户端

 运行实例代码后,可以同时打开多个客户端,客户端连接将被加入集合遍历处理,无需等待,服务端此时可以处理多个请求,此时服务端使用的是单线程。

优化

虽然上述代码解决了BIO中遗留的阻塞问题,但多个请求连接而不发送数据占用资源的情况仍然存在,如当请求数据量巨大时,需要通过遍历集合的方式寻找存在需要处理数据的客户端时,时间的损耗仍然非常大。因此使用多路复用的方式来解决无效遍历的问题。

package com.mlyzr.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author 勿忘初心
 * @since 2023-08-08-15:28
 * 同步非阻塞IO示例代码
 */
public class NioServer {

    public static void main(String[] args) throws IOException {
        // 创建NIO的ServerSocketChannel,与BIO的ServerSocket类似
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.socket().bind(new InetSocketAddress(8096));
        // 设置channel为非阻塞
        serverChannel.configureBlocking(false);
        // 引入多路复用,提高对Channel的处理能力, 即epoll
        Selector selector = Selector.open();
        // 将ServerSocketChannel注册到selector上,即服务端接收到连接请求时,将连接事件进行注册
        SelectionKey selectionKey = serverChannel.register(selector,SelectionKey.OP_ACCEPT);
        System.out.println("等待客户端连接");

        while(true){
            // 判断是否有注册的事件,无事件将阻塞
            selector.select();

            // 获取所有已经注册的事件实例
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();

            while(keyIterator.hasNext()){
                SelectionKey selectKey = keyIterator.next();
                // 若为连接事件,则获取该实例
                if(selectKey.isAcceptable()){
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectKey.channel();
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    // 设置为非阻塞
                    socketChannel.configureBlocking(false);
                    // 注册读事件,若需要发送消息给客户端可以注册写事件
                    SelectionKey key = socketChannel.register(selector,SelectionKey.OP_READ);
                    System.out.println("监听到客户端连接");
                }
                if (selectKey.isReadable()){
                    SocketChannel socketChannel = (SocketChannel) selectKey.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                    int read = socketChannel.read(byteBuffer);
                    // 若存在数据,则将数据打印出来
                    if(read > 0){
                        System.out.println("接收到客户端数据"+new String(byteBuffer.array(),0,read,StandardCharsets.UTF_8));
                    }
                    // 无数据则说明客户端已断开
                    if(read == -1){
                        System.out.println("客户端已断开连接");
                        socketChannel.close();
                    }
                }
                // 从事件集合中删除本次处理的key,防止重复处理
                keyIterator.remove();
            }

        }

    }
}

总结

当使用多路复用后,会监听到客户端的连接事件,并为当前的连接注册读事件,当客户端发送数据时,会被再次监听,此时会进入读操作的事件处理中,将打印接收到的数据,同时每一次的事件使用后将会被移除,防止事件重复,从而解决了无效遍历的问题。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CCLINK IE转MODBUS-TCP网关modbus tcp协议详解

你是否曾经遇到过需要同时处理CCLINK IE FIELD BASIC和MODBUS两种数据协议的情况&#xff1f;捷米的JM-CCLKIE-TCP网关可以帮助你解决这个问题。 捷米JM-CCLKIE-TCP网关可以分别从CCLINK IE FIELD BASIC一侧和MODBUS一侧读写数据&#xff0c;然后将数据存入各自的缓冲区。接着…

MySQL高阶知识点(一)一条SQL【更新】语句是如何执行的

一条SQL【更新】语句是如何执行的 首先&#xff0c;可以确定的说&#xff0c;【查询】语句的那一套流程&#xff0c;【更新】语句也是同样会走一遍&#xff0c;与查询流程不一样的是&#xff0c; 更新语句涉及到【事务】&#xff0c;就必须保证事务的四大特性&#xff1a;ACID&…

k8s 滚动更新控制(一)

在传统的应用升级时&#xff0c;通常采用的方式是先停止服务&#xff0c;然后升级部署&#xff0c;最后将新应用启动。这个过程面临一个问题&#xff0c;就是在某段时间内&#xff0c;服务是不可用的&#xff0c;对于用户来说是非常不友好的。而kubernetes滚动更新&#xff0c;…

jacoco功能测试-代码覆盖率

1、下载 jacoco 官网地址&#xff1a;EclEmma - JaCoCo Java Code Coverage Library 2、拷贝 jar 包 下载好后&#xff0c;找到这两个文件&#xff0c;然后找到被测项目 3、启动 jacocoagent&#xff0c;监控被测项目 java -javaagent:jacocoagent.jarincludes*,outputtcp…

Filament for Android 编译搭建(基于Ubuntu20.04系统)

一、Filament 源代码下载 github下载地址&#xff1a; 2、安装clang 我是直接安装clang-10 Ubuntu 20.04 &#xff0c;sudo apt install clang 命令默认就是clang-10 $sudo apt-get install clang-10 # 安装 AST.h 等头文件 $sudo apt-get install libclang-10-dev $sudo …

【Linux操作系统】文件描述符fd

&#x1f525;&#x1f525; 欢迎来到小林的博客&#xff01;&#xff01;       &#x1f6f0;️博客主页&#xff1a;✈️林 子       &#x1f6f0;️博客专栏&#xff1a;✈️ Linux之路       &#x1f6f0;️社区 :✈️ 进步学堂       &#x1…

使用logback异步打印日志

文章目录 一、介绍二、运行环境三、演示项目1. 接口2. 日志配置文件3. 效果演示4. 异步输出验证 四、异步输出原理五、其他参数配置六、源码分析1. 同步输出2. 异步输出 七、总结 一、介绍 对于每一个开发人员来说&#xff0c;在业务代码中添加日志是至关重要的&#xff0c;尤…

docker基本使用方法

docker使用 1. Docker 介绍 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。Docker 使您能够将应用程序与基础架构分开&#xff0c;从而可以快速交付软件。通过利用 …

Oracle 开发篇+Java通过DRCP访问Oracle数据库

标签&#xff1a;DRCP、Database Resident Connection Pooling、数据库驻留连接池释义&#xff1a;DRCP&#xff08;全称Database Resident Connection Pooling&#xff09;数据库驻留连接池&#xff08;Oracle自己的数据库连接池技术&#xff09; ★ Oracle开启并配置DRCP sq…

神码ai伪原创文章生成器软件【php源码】

大家好&#xff0c;本文将围绕python二级用哪个版本的软件展开说明&#xff0c;二级python 值不值得考是一个很多人都想弄明白的事情&#xff0c;想搞清楚python二级用什么软件需要先了解以下几个事情。 火车头采集ai伪原创插件截图&#xff1a; 问题一&#xff1a;安装python…

java+springboot+mysql智能社区管理系统

项目介绍&#xff1a; 使用javaspringbootmysql开发的社区住户综合管理系统&#xff0c;系统包含超级管理员、管理员、住户角色&#xff0c;功能如下&#xff1a; 超级管理员&#xff1a;管理员管理&#xff1b;住户管理&#xff1b;房屋管理&#xff08;楼栋、房屋&#xff…

代码详解——Transformer

文章目录 整体架构Modules.pyScaledDotProductAttention SubLayers.pyMultiHeadAttentionPositionwiseFeedForward Layers.pyEncoderLayerDecoderLayer Models.pyget_pad_maskget_subsequent_maskPositionalEncodingEncoderDecoderTransformer 整体架构 源码地址&#xff08;py…

电商3D产品渲染简明教程

3D 渲染让动作电影看起来更酷&#xff0c;让建筑设计变得栩栩如生&#xff0c;现在还可以帮助营销人员推广他们的产品。 从最新的《阿凡达》电影到 Spotify 的上一次营销活动&#xff0c;3D 的应用让一切变得更加美好。 在营销领域&#xff0c;3D 产品渲染可帮助品牌创建产品的…

Docker+rancher部署SkyWalking8.5并应用在springboot服务中

1.Skywalking介绍 Skywalking是一个国产的开源框架&#xff0c;2015年有吴晟个人开源&#xff0c;2017年加入Apache孵化器&#xff0c;国人开源的产品&#xff0c;主要开发人员来自于华为&#xff0c;2019年4月17日Apache董事会批准SkyWalking成为顶级项目&#xff0c;支持Jav…

Kafka消息队列学习(一)

文章目录 概述核心概念生产者示例同步 / 异步发送消息生产者参数配置ack-确认机制retries - 重试次数compression_type - 消息压缩类型 分区机制分区策略 消费者消息有序性提交和偏移量偏移量提交方式手动提交 高可用设计 SpringBoot集成Kafka基本使用传递对象消息 概述 核心概…

2023企业微信0day漏洞复现以及处理意见

2023企业微信0day漏洞复现以及处理意见 一、 漏洞概述二、 影响版本三、 漏洞复现小龙POC检测脚本: 四、 整改意见 免责声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#x…

Arduino+esp32学习笔记

学习目标&#xff1a; 使用Arduino配置好蓝牙或者wifi模块 学习使用python配置好蓝牙或者wifi模块 学习内容&#xff08;笔记&#xff09;&#xff1a; 一、 Arduino语法基础 Arduino语法是基于C的语法,C又是c基础上增加了面向对象思想等进阶语言。那就只记录没见过的。 单多…

Mysql in 查询的奇怪方向

Mysql in 查询的奇怪方向 关于表字段存储的数据为 num1,num2,num3时, 还要通过多个num1,num2入参针对该字段进行查询 建表语句 CREATE TABLE test (test_ids varchar(100) DEFAULT NULL COMMENT 保存ids 以逗号分隔 ) ENGINEInnoDB;数据项 查询语句 SELECT test_ids FROM t…

Android之版本号、版本别名、API等级对应关系(全)(一百六十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

数据结构:选择排序

简单选择排序 选择排序是一种简单直观的排序算法。首先在未排序序列中找到最大&#xff08;最小&#xff09;的元素&#xff0c;存放到排序学列的其实位置&#xff0c;然后在剩余的未排序的元素中寻找最小&#xff08;最大&#xff09;元素&#xff0c;存放在已排序序列的后面…