代码分析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(){@Overridepublic 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的处理能力, 即epollSelector 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.hqwc.cn/news/57765.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UE中低延时播放RTSP监控视频解决方案

第1章 方案简介 1.1 行业痛点 在各种智慧城市、智慧社区、智慧水利、智慧矿山等数字孪生项目中&#xff0c;经常使用通UE来开发三维可视化场景。在这些场景中通常都需要把现场的各种监控视频在UE的可视化场景中接入&#xff0c;主要包含海康威视、大华、宇视、华为等众多监控…

网络编程——数据包的组装和拆解

数据包的组装和拆解 一、数据包在各个层之间的传输 二、各个层的封包格式 1、链路层封包格式 -------------------------------------------------------------------------------------------------------------------------------------- | 目标MAC地址&#xff08;6字节&a…

WebView2对比CefSharp的超强优势

第一次使用了CefSharp组件&#xff0c;集成开发结束后&#xff0c;测试及使用过程中遇到了一些无法处理的bug及严重的性能问题。然后又测试对比了其他多种组件&#xff0c;具体情况可以阅读我的博客​ ​《.NET桌面程序集成Web网页开发的十种解决方案》​​。最终选用了微软新出…

Ubuntu 22.04安装和使用ROS1可行吗

可行。 测试结果 ROS1可以一直使用下去的&#xff0c;这一点不用担心。Ubuntu会一直维护的。 简要介绍 Debian发行版^_^ AI&#xff1a;在Ubuntu 22.04上安装ROS1是可行的&#xff0c;但需要注意ROS1对Ubuntu的支持只到20.04。因此&#xff0c;如果要在22.04上安装ROS1&am…

中间件多版本冲突的4种解决方案和我们的选择

背景 在小小的公司里面&#xff0c;挖呀挖呀挖。最近又挖到坑里去了。一个稳定运行多年的应用&#xff0c;需要在里面支持多个版本的中间件客户端&#xff1b;而多个版本的客户端在一个应用里运行时会有同名类冲突的矛盾。在经过询问chatGPT&#xff0c;百度&#xff0c;googl…

linux下.run安装脚本制作

1、安装文件(install.sh) PS: .run安装包内部执行脚本文件 2、资源文件(test.zip) PS: 待安装程序源文件 3、制作.run脚本(install.run) cat install.sh test.zip > install.run chmod ax install.run

【软件工程】3 ATM系统的设计

目录 3 ATM系统的设计 3.1体系结构设计 3.2 设计模式选择 3.3 补充、完善类图 3.4 数据库设计 3.4.1 类与表的映射关系 3.4.2 数据库设计规范 3.4.3 数据库表 3.5 界面设计 3.5.1 界面结构设计 3.5.2 界面设计 3.5.2.1 功能界面设计 3.5.2.2 交互界面 总博客&…

基于Python++PyQt5马尔科夫模型的智能AI即兴作曲—深度学习算法应用(含全部工程源码+测试数据)

目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境PC环境配置 模块实现1. 钢琴伴奏制作1&#xff09;和弦的实现2&#xff09;和弦级数转为当前调式音阶3&#xff09;根据预置节奏生成伴奏 2. 乐句生成1&#xff09;添加音符2&#xff09;旋律生成3&#xff09;节…

SpringBoot + Docker 实现一次构建到处运行~

一、容器化部署的好处 图片 Docker 作为一种新兴的虚拟化方式&#xff0c;它可以更高效的利用系统资源&#xff0c;不需要进行硬件虚拟以及运行完整操作系统等额外开销。 传统的虚拟机技术启动应用服务往往需要数分钟&#xff0c;而 Docker 容器应用&#xff0c;由于直接运行…

linuxARM裸机学习笔记(7)----RTC实时时钟实验

基础概念&#xff1a; I.MX6U 内部也有个RTC 模块&#xff0c;但是不叫作“ RTC ”&#xff0c;而是叫做“ SNVS ”。 SNVS 直译过来就是安全的非易性存储&#xff0c; SNVS 里面主要是一些低功耗的外设&#xff0c;包括一个 安全的实时计数器 (RTC) 、一个单调计数器 (mo…

socket 到底是个啥

我相信大家在面试过程中或多或少都会被问到这样一个问题&#xff1a;你能解释一下什么是 socket 吗 我记得我当初的回答很是浅显&#xff1a;socket 也叫套接字&#xff0c;用来负责不同主机程序之间的网络通信连接&#xff0c;socket 的表现方式由四元组&#xff08;ip地址&am…

利用线程池多线程并发实现TCP两端通信交互,并将服务端设为守护进程

文章目录 实现目标实现步骤封装日志类封装线程池封装线程封装锁封装线程池 TCP通信的接口和注意事项accept TCP封装任务客户端Client.hppClient.cc 服务端Server.hpp Server.cc实现效果 守护进程服务端守护进程化 实现目标 利用线程池多线程并发实现基于TCP通信的多个客户端与…