代码分析Java中的BIO与NIO

news2024/12/25 22:42:56

开发环境 

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();
            }

        }

    }
}

总结

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

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

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

相关文章

ffmpeg 4.4版本对MP4文件进行AES-CTR加密,和流式加密

对于ffmpeg的AES-CTR加密有两种方式&#xff0c;一个是普通的整个视频做加密&#xff0c;另一个是对视频做切片处理&#xff0c;然后进行加密。 一、对于普通的加密方式 直接使用下面的命令就行 ffmpeg -i animal.mp4 -vcodec copy -acodec copy -encryption_scheme cenc-aes…

VR漫游的“沉浸式”体验,你get到了吗?

信息化时代&#xff0c;几乎每隔一段时间都会出现新的词汇&#xff0c;而“沉浸式”一词仿佛自带流量一般&#xff0c;在很多场景中都有所使用&#xff0c;接下来我们就带大家一起感受下VR漫游所表现出来的“沉浸式”漫游体验。 VR漫游通过专业的全景相机采集高清的图片素材&am…

AWS-自定义ami的S3存取使用

需要提前配置好aws-cli哈 对应的区域 要统一 示例&#xff1a;即AWS-CLI 和 EC2、AMI、S3以上资源均要使用同已区域&#xff0c;以下拿新加坡举例 1.新建自定义AMI 2.查看ami状态 确认是可用状态&#xff0c;才能开始操作 3.aws-cli 开始存入s3 只能使用桶的根目录 开始上…

jdk1.7与jdk1.8的HashMap区别2-底层原理区别

jdk1.7与jdk1.8的HashMap区别1-基本结构与属性对比_ycsdn10的博客-CSDN博客 一、代码区别 1.代码数&#xff1a;JDK1.7与JDK1.8相差了一倍的代码 2.方法数&#xff1a;JDK1.7是40个&#xff0c;JDK1.8是51个&#xff08;只算基本方法&#xff09; 二、Hash差别 1.JDK1.7 st…

【Pytorch+torchvision】MNIST手写数字识别

深度学习入门项目&#xff0c;含代码详细解析 在本文中&#xff0c;我们将在PyTorch中构建一个简单的卷积神经网络&#xff0c;并使用MNIST数据集训练它识别手写数字。 MNIST包含70,000张手写数字图像: 60,000张用于培训&#xff0c;10,000张用于测试。图像是灰度&#xff08;即…

融云:从「对话框」跳进魔法世界,AIGC 带给社交的新范式

8 月 17 日&#xff08;周四&#xff09;&#xff0c;融云将带来直播课-《北极星如何协助开发者排查问题与预警风险&#xff1f;》欢迎点击上方报名~ AIGC 与社交结合的应用主要分两种&#xff0c;一是发乎于 AIGC&#xff0c;以大模型为基础提供虚拟伴侣等服务的 App&#xff…

7个月的测试经验,来面试居然开口要18K,我一问连5K都不值...

2021年8月份我入职了深圳某家创业公司&#xff0c;刚入职还是很兴奋的&#xff0c;到公司一看我傻了&#xff0c;公司除了我一个测试&#xff0c;公司的开发人员就只有3个前端2个后端还有2个UI&#xff0c;在粗略了解公司的业务后才发现是一个从零开始的项目&#xff0c;目前啥…

网络防御(7)

课堂实验 R1 [Huawei] int g0/0/0 [Huawei-GigabitEthernet0/0/0]ip add 100.1.12.2 24 protocolAug 1 2023 10:24:09-08:00 Huawei gOlIFNET/4/LINK STATE(1)[4]:The1ineIp on the interface GigabitEthernet0/0/0 has entered the Up state. [Huawei-GigabitEthernet0/0/…

lab7 proxylab

前情提要&#xff0c;如果看了书本&#xff0c;这个lab难度不高&#xff0c;但是如果不看书&#xff0c;难度还是挺高的&#xff0c;并且这个lab会用到cachelab中学到的东西&#xff0c;需要阅读 第十章&#xff1a;系统编程第十一章&#xff1a;网络编程第十二章&#xff1a;…

UE5.2 LyraDemo源码阅读笔记(四)

上一篇&#xff08;三&#xff09;讲到在模式玩法UI点击Elimination进入淘汰赛模式。 UI选择点击Elimination后&#xff0c;触发蓝图W_HostSessionScreen的HostSession节点&#xff0c;有&#xff1a; 调用这个方法切换关卡后&#xff0c;会调用到LyraGameMode.cpp的 ALyraGam…

双通道差分2:1/1:2USB31多路复用器/分离器ASW3410

ASW3410 是一个 2:1 或1:2 的数据开关&#xff0c;用于高速数据传输。 ASW3410数据开关支持高性能的各类高速数据 传输协议&#xff0c;如下: USB 3.1 SuperSpeed (Gen 2)10Gbps PCle (Gen 3) SATA 6Gbit/s 光纤通道HDMI 2.0 Display Port 1.2 特性 10GHz 典型带宽 2.5 GHz的…

【C++从0到王者】第十八站:手把手教你写一个简单的优先级队列

文章目录 一、优先级队列简介二、优先级队列的接口说明1.基本介绍及其使用2.构造函数3.求数组中第k个最大的元素 三、手撕优先级队列四、仿函数1.仿函数介绍2.优先级队列添加仿函数3.需要自己写仿函数的情形 五、优先级队列完整代码 一、优先级队列简介 优先级队列是一种容器适…

【网络安全】等保测评系列预热

【网络安全】等保测评系列预热 前言1. 什么是等级保护&#xff1f;2. 为什么要做等保&#xff1f;3. 路人甲疑问&#xff1f; 一、等保测试1. 渗透测试流程1.1 明确目标1.2 信息搜集1.3 漏洞探索1.4 漏洞验证1.5 信息分析1.6 获取所需1.7 信息整理1.8 形成报告 2. 等保概述2.1 …

HEIF—— 1、vs2017编译Nokia - heif源码

HEIF(高效图像文件格式) 一种图片有损压缩格式,它的后缀名通常为".heic"或".heif"。 HEIF 是由运动图像专家组 (MPEG) 标准化的视觉媒体容器格式,用于存储和共享图像和图像序列。它基于著名的 ISO 基本媒体文件格式 (ISOBMFF) 标准。HEIF读写器引擎…

NAT及其实验(eNSP,细致易懂)

目录 NAT产生背景 NAT概述NAT&#xff08;Network Address Translation&#xff09;&#xff0c;网络地址转换 NAT工作规则 标准NAT技术 NAPT[网络地址端口转换[Port-->传输层-端口编号]] Easy IP——最简单的PAT NAT Server 静态NAT实验 动态NAT实验 NAPT实验 N…

Ajax 笔记(一)

笔记目录 1. Ajax 入门1.1 Ajax 概念1.2 axios 使用1.2.1 URL1.2.2 URL 查询参数1.2.3 小案例-查询地区列表1.2.4 常用请求方法和数据提交1.2.5 错误处理 1.3 HTTP 协议1.3.1 请求报文1.3.2 响应报文 1.4 接口文档1.5 案例1.5.1 用户登录&#xff08;主要业务&#xff09;1.5.2…

用MiCoNE工具对16S序列数据进行共现网络分析

谷禾健康 微生物群通常由数百个物种组成的群落&#xff0c;这些物种之间存在复杂的相互作用。绘制微生物群落中不同物种之间的相互关系&#xff0c;对于理解和控制其结构和功能非常重要。 微生物群高通量测序的激增导致创建了数千个包含微生物丰度信息的数据集。这些丰度可以转…

umi黑科技:把静态文件打包进静态网页中:P

为了能够跨平台通用&#xff0c;我现在很多工具都需要用JS进行开发。 比如我之前研究了半天的JS版本的报表工具。 但是这其中有个问题我没办法解决&#xff0c;就是有一些设置信息或者是模板文件需要一起打包进静态的页面中。 今天解决了这个问题&#xff0c;记录一下方法。 1…

Android 13 Launcher——屏蔽长按非icon区域出现弹窗

目录 一.背景 二.屏蔽此功能 一.背景 长按Launcher非icon区域也是会有弹窗的&#xff0c;会显示小组件等信息&#xff0c;定制开发要求长按非icon区域不要弹窗&#xff0c;我们来实现此功能&#xff0c;先看下未修改前的长按非icon区域的效果 如上图可以看出长按功能显示出壁…

计网实验第三章:UDP

问题集1 问题一 问题参考Wireshark的报文内容字段的显示信息 在这个数据包中&#xff0c;确定每个UDP报头字段的长度(以字节为单位) 答&#xff1a;96 bytes 问题二 长度字段中的值是什么的长度?你可以参考课文 这个答案)。用捕获的UDP数据包验证您的声明。 答&#xff1…