Java I/O模式 (一)

第一章 Java的I/O演进之路

1.1 I/O模型基本说明

1/0模型:就是用什么样的通道或者说是通信模式和架构进行数据的传输和接收,很大程度上决定了程序通信的性能,Java 共支持3种网络编程的/10 模型:BIO、NIO、AIO

实际通信需求下,要根据不同的业务场景和性能需求決定选择不同的1/0模型

1.2 I/O 模型

Java BIO

​ 同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就

需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销
请添加图片描述

Java NIO

​ 同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到有I/O请求就进行处理。

请添加图片描述

Java AIO

​ 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先处理完成,再通知服务器去启动线程进行处理,一般适用于连接数较多且连接时间长的应用

1.3 BIO、NIO、AIO适用场景分析

I/O模型适用场景
BIOBIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序简单易理解。
NIONIO 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务器间通讯等。编程比较复杂,JDK1.4 开始支持。
AIOAIO 方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用 OS 参与并发操作,编程比较复杂,JDK7 开始支持。

第二章 JAVA BIO 深入剖析

2.1 Java BIO 基本介绍

  • Java BIO 就是传统的Java IO 编程,其相关的类和接口在java.io
  • 同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户端连接服务器)

2.2 Java BIO 工作机制

请添加图片描述

2.2 Java BIO 样例

客户端
/*** 客户端 Client*/public class Client {public static void main(String[] args) throws IOException {// 1.创建一个socket对象Socket socket = new Socket("127.0.0.1",9999);// 2.从socket对象中获得字节输出流OutputStream outputStream = socket.getOutputStream();// 3.把字节输出流包装成一个打印流PrintStream printStream = new PrintStream(outputStream);printStream.print("hello World! 服务端");printStream.flush();}
}
服务端
/*** 目标: 客户端发消息,服务端接收消息*/public class Server {public static void main(String[] args) {try {System.out.println("=============原神 启动===============");// 1.定义一个 ServerSocket对象进行服务端的端口注册ServerSocket serverSocket = new ServerSocket(9999);// 2.监听客户端 Socket 链接请求Socket socket = serverSocket.accept();// 3.从socket管道中得到一个字节输入流对象InputStream inputStream = socket.getInputStream();// 4.把字节输入流包装成一个缓存字符输入流BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));String msg;if ((msg = bufferedReader.readLine()) != null){System.out.println("服务端接收到:"+msg);}// 把字节输入流包装成一个缓存字节输入流//BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);} catch (IOException e) {e.printStackTrace();}}
}

2.4 BIO模式下多发和多收消息【一对一】

/*** 客户端*/public class Client {public static void main(String[] args) throws IOException {// 1.创建一个socket对象Socket socket = new Socket("127.0.0.1",9999);// 2.从socket对象中获得字节输出流OutputStream outputStream = socket.getOutputStream();// 3.把字节输出流包装成一个打印流PrintStream printStream = new PrintStream(outputStream);Scanner scanner = new Scanner(System.in);while (true){System.out.println("请说");String msg = scanner.nextLine();printStream.println(msg);printStream.flush();}}
}
/*** 目标: 服务端反复接收消息,客户端反复发送消息*/public class Server {public static void main(String[] args) {try {System.out.println("=============原神 启动===============");// 1.定义一个 ServerSocket对象进行服务端的端口注册ServerSocket serverSocket = new ServerSocket(9999);// 2.监听客户端 Socket 链接请求Socket socket = serverSocket.accept();// 3.从socket管道中得到一个字节输入流对象InputStream inputStream = socket.getInputStream();// 4.把字节输入流包装成一个缓存字符输入流BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));String msg;while ((msg = bufferedReader.readLine()) != null){System.out.println("服务端接收到:"+msg);}// 把字节输入流包装成一个缓存字节输入流//byte[] bytes = new byte[1024];//BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);//while (bufferedInputStream.read(bytes)>0){//	System.out.println("客户端收到了"+bytes.toString());//}} catch (IOException e) {e.printStackTrace();}}
}

2.5 BIO模式接收多个客户端

​ 如果服务端需要处理很多个客户端的消息通信请求,此时我们就需要在服务端引入线程了,也就是说客户端每发起一个请求,服务端

就创建一个新的线程来处理这个客户端的请求,这样就实现了一个客户端一个线程的模型

/*** 目标: 服务端同时接收多个socket通信需求* 思路: 每收到一个socket请求后交给一个独立的线程来处理客户端的数据交互*/public class Server {public static void main(String[] args) {try {// 1.注册端口ServerSocket serverSocket = new ServerSocket(9999);// 2.定义一个死循环,负责不断的接收客户端的socket请求while (true){Socket socket = serverSocket.accept();// 3.创建一个独立线程处理这个客户端socket请求ServerThreadReader serverThreadReader = new ServerThreadReader(socket);serverThreadReader.start();}} catch (IOException e) {throw new RuntimeException(e);}}}class ServerThreadReader extends Thread{private Socket socket;ServerThreadReader(Socket socket){this.socket = socket;}@Overridepublic void run() {System.out.println("=============原神 启动===============");// 从 socket 对象中得到字节输入流InputStream inputStream = null;try {inputStream = socket.getInputStream();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));String msg;while ((msg=bufferedReader.readLine())!=null){String name = Thread.currentThread().getName();System.out.println(name+msg);}} catch (IOException e) {throw new RuntimeException(e);}}
}
小结
小结
1.每个Socket接收到,都会创建一个线程,线程的竞争、切换上下文影响性能;
2.每个线程都会占用栈空间和CPU资源;
3.并不是每个socket都进行I0操作,无意义的线程处理;
4.客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。

2.6 伪异步 I/O 编程

在上述案例中:客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。

接下来我们采用一个伪异步1/0的通信框架,采用线程池和任务队列实现,当客户端接入时,将客户端的Socket封装成一个Task(该任务实现java.lang.Runnable线程任务接口)交给后端的线程池中进行处理。JDK的线程池维护一个消息队列和N个活跃的线程,对消息队列中Socket任务进行处理,由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。

请添加图片描述

服务端代码
/*** 目标: 开发实现伪异步通信架构*/
public class Server {public static void main(String[] args) {try {// 1.注册端口ServerSocket serverSocket = new ServerSocket(9999);// 2.初始化一个线程对象HandlerSocketServerPool handlerSocketServerPool = new HandlerSocketServerPool(3, 10);// 3.定义一个循环介绍客户端socket连接请求while (true){Socket socket = serverSocket.accept();//4.把socket对象封装成一个任务对象ServerRunnableTarget serverRunnableTarget = new ServerRunnableTarget(socket);// 4.把任务对象交给一个线程池进行处理handlerSocketServerPool.excute(serverRunnableTarget);}}catch(Exception e){e.printStackTrace();}}
}/*------------------------------------------类分界线---------------------------------------------------*/public class ServerRunnableTarget implements Runnable{private Socket socket;ServerRunnableTarget(Socket socket){this.socket = socket;}@Overridepublic void run() {System.out.println("=============原神 启动===============");// 从 socket 对象中得到字节输入流InputStream inputStream = null;try {inputStream = socket.getInputStream();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));String msg;while ((msg=bufferedReader.readLine())!=null){String name = Thread.currentThread().getName();System.out.println(name+msg);}} catch (IOException e) {throw new RuntimeException(e);}}
}/*------------------------------------------类分界线---------------------------------------------------*/public class HandlerSocketServerPool {// 1.创建一个线程池的成员变量,用来存储一个线程池对象private ExecutorService executorService;/*** 2.创建这个类的对象的时候需要初始化线程池对象* @param maxThreadNum* @param queueSize*/HandlerSocketServerPool(int maxThreadNum, int queueSize){executorService = new ThreadPoolExecutor(2,maxThreadNum,20,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(queueSize));}/*** 3.提供一个方法来提交任务给线程池的任务队列来暂存,等着线程来处理*/public void excute(Runnable target){executorService.execute(target);}
}
小结:
  • 伪异步io采用了线程池实现,因此避免了为每个请求创建一个独立线程造成线程资源耗尽的问题,但由于底层依然是采用的同步阻塞模型,因此无法从根本上解决问题。
  • 如果单个消息处理的缓慢,或者服务器线程池中的全部线程都被阻塞,那么后续socket的io消息都将在队列中排队。新的Socket请求将被拒绝,客户端会发生大量连接超时。

2.7 基于 BIO 形式下的文件上传

/*** 目标:服务端开发,实现可以接收服务端任意类型文件,并保存到服务端磁盘*/
public class Server {public static void main(String[] args) {try {ServerSocket serverSocket = new ServerSocket(8888);while (true){Socket socket = serverSocket.accept();// 交给一个独立的线程来处理ServerReaderThread serverReaderThread = new ServerReaderThread(socket);serverReaderThread.start();}}catch (Exception e){e.printStackTrace();}}
}/*------------------------------------------类分界线---------------------------------------------------*/public class ServerReaderThread extends Thread{private Socket socket;public ServerReaderThread(Socket socket){this.socket = socket;}@Overridepublic void run() {try {// 1.得到数据输入流得到客户端发送过来的数据InputStream inputStream = socket.getInputStream();DataInputStream dataInputStream = new DataInputStream(inputStream);// 2.读取客户端发送过来的文件类型String suffix = dataInputStream.readUTF();String name = dataInputStream.readUTF();byte[] buffer = new byte[1024];System.out.println("已接收到后缀"+suffix);// 3.定义一个字节输出管道File file = new File( "./"+name + suffix);file.createNewFile();FileOutputStream fileOutputStream = new FileOutputStream(file);int len;while ((len=dataInputStream.read(buffer))>0){fileOutputStream.write(buffer,0,len);}dataInputStream.close();fileOutputStream.close();}catch (Exception e){e.printStackTrace();}}
}/*------------------------------------------类分界线--------------------------------------------------*//*** 目标: 实现客户端上传任意类型的文件数据给服务端存储起来*/
public class Client {public static void main(String[] args) {try{// 1.请求与服务端的socket连接Socket socket = new Socket("127.0.0.1", 8888);OutputStream outputStream = socket.getOutputStream();File file = new File("/Users/zhangliuxiao/Downloads/Sentinel.mp4");String filename = file.getName();String name = filename.substring(0,filename.lastIndexOf('.'));System.out.println(name);String extension = filename.substring(filename.lastIndexOf('.'));System.out.println(extension);// 2.把字节输出流包装成一个数据输出流DataOutputStream dataOutputStream = new DataOutputStream(outputStream);// 3.先发送上传文件的后缀给服务器dataOutputStream.writeUTF(extension);dataOutputStream.writeUTF(name);// 4.把文件数据发送给服务端进行接收FileInputStream fileInputStream = new FileInputStream(file);byte[] buffer = new byte[1024];int len;while ((len=fileInputStream.read(buffer))>0){dataOutputStream.write(buffer,0,len);}dataOutputStream.flush();socket.shutdownOutput();outputStream.close();fileInputStream.close();dataOutputStream.close();}catch (Exception e){e.printStackTrace();}}
}

2.8 Java BIO 模式下的端口转发思想

请添加图片描述

/*** 目标:BIO模式下的端口转发思想-服务端实现**  服务端实现的需求:*  	1.注册端口*  	2.接受客户端的socket连接,交给一个独立的线程来处理*  	3.把当前连接的客户端socket存入到一个在线socket集合中来保存*  	4.接收客户端的消息,然后推送给当前所有在线的socket接收*/
public class Server {public static List<Socket> allSocketOnline = new ArrayList<>();public static void main(String[] args) {try {ServerSocket serverSocket = new ServerSocket(9999);while (true){Socket socket = serverSocket.accept();// 把登录的客户端中的socket存入到一个在线集合中去allSocketOnline.add(socket);// 为当前成功登录的socket分配一个独立的线程来与之通信ServerReader serverReader = new ServerReader(socket);serverReader.start();}} catch (IOException e) {throw new RuntimeException(e);}}
}/*------------------------------------------类分界线---------------------------------------------------*/public class ServerReader extends Thread{private Socket socket;public ServerReader(Socket socket) {this.socket = socket;}@Overridepublic void run() {try {InputStream inputStream = socket.getInputStream();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));String msg;while ((msg = bufferedReader.readLine()) != null){sendMsgToAllClient(msg);}} catch (IOException e) {System.out.println("当前有人下线了");throw new RuntimeException(e);}}/*** 把当前客户端发来的消息推送给所有在线的socket* @param msg*/private void sendMsgToAllClient(String msg) throws IOException {for (Socket socket : Server.allSocketOnline) {OutputStream outputStream = socket.getOutputStream();PrintStream printStream = new PrintStream(outputStream);printStream.println(msg);printStream.flush();}}
}

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

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

相关文章

Centos忘记密码,重置root密码

Centos忘记密码&#xff0c;重置root密码 操作环境&#xff1a;Centos7.6 1、选择包含rescue的选项&#xff0c;按e进入编辑模式 首先&#xff0c;我们需要重启系统&#xff0c;进入开机引导菜单界面。在这里&#xff0c;我们可以看到系统的内核版本和启动参数等信息。我们需…

【JavaEE精炼宝库】文件操作(2)——文件内容读写 | IO流

文章目录 一、输入流1.1 InputStream 概述&#xff1a;1.2 read 方法详解&#xff1a;1.3 close 方法&#xff1a;1.4 利用 Scanner 进行读操作&#xff1a;1.5 Reader&#xff1a; 二、输出流2.1 OutputStream 概述&#xff1a;2.2 write 方法详解&#xff1a;2.3 利用 PrintW…

学习测试8-数据库mysql操作

下载配置mysql 网络博客 使用 在Linux里 1 service mysql start 启动服务 2 在Navicatt 中连接Linux服务器 3 第一步 将所有文件上传到/opt目录下 第二步 chmod 777 deploy-mysql.sh 第三步 ./deploy-mysql.sh4 service mysql status 查看状态是否安装成功 5 重启mys…

Typescript 模块小知识-global scope

问题表现 在编写ts代码的时候遇到一个问题, 表现为, 如果在某个ts工程中, 如果多个文件里面没有任何导出export或者是export default, 那么这些文件如果有const或者是let定义相同的声明都会报错如下 无法重新声明块范围变量 a/a.ts 和 index.ts 和 index2.ts 都没有进行expor…

基于Redisson 实现 Redis 分布式锁

代码示例&#xff1a; GetMapping("/testJmeter")public void testJmeter() {synchronized (this){int stock Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"))if (stock > 0) {int realStock stock - 1;stringRedisTemplate.opsFo…

盘点:流媒体视频流协议与EasyCVR视频监控技术的深度融合

随着科技的不断进步&#xff0c;视频监控技术已经渗透到了社会的各个角落&#xff0c;从公共安全到企业运营&#xff0c;再到家庭安全&#xff0c;其应用范围日益广泛。而视频流格式作为视频监控技术的核心要素之一&#xff0c;其选择和应用对于确保视频传输的流畅性、清晰度和…

Python爬虫教程第3篇-解决使用reqeusts遇到的ProxyError异常

起因 问题出现在windows电脑上&#xff0c;我用mac执行程序的时候并不会报错&#xff0c;但是如果在windows上的时候&#xff0c;大部分windows电脑会报错&#xff0c;而有些版本低的windows电脑又不会报错。 异常栈信息 HTTPSConnectionPool, Cannot connect to proxy, no …

基于SpringBoot的校园疫情防控系统

你好&#xff0c;我是专注于计算机科学与技术的研究者。如果你对我的工作感兴趣或有任何问题&#xff0c;欢迎随时联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot框架&#xff0c;B/S架构 工具&#xff1a;Eclipse&#xff0c;Mav…

2024中国互联网大会 | 中国电信携手产业链伙伴推动低空经济行业创新与发展

低空经济是指以民用有人驾驶和无人驾驶航空器在低空空域内的各类飞行活动为牵引&#xff0c;辐射带动相关领域融合发展的综合经济形态。2024年全国两会&#xff0c;“低空经济”首次写入国务院政府工作报告。 赛迪顾问在《中国低空经济发展研究报告&#xff08;2024&#xff09…

python作业二

# 二进制转化为十进制 num input("num:")def binaryToDecimal(binaryString):he 0length len(binaryString)for i in range(length):he int(binaryString[i]) * 2 ** (length - i - 1)return heprint(binaryToDecimal(num))代码运行如下&#xff1a; import math…

特种设备管理为什么这么难?为何它是安全生产的重中之重?

随着工业化进程的加速和科技水平的不断提升&#xff0c;特种设备作为工业生产、公共服务和基础设施建设中的关键要素&#xff0c;其应用范围日益广泛&#xff0c;从大型压力容器、锅炉、电梯、叉车到压力管道、客运索道等&#xff0c;无一不渗透于我们日常生活的方方面面。然而…

【qt】QTcpSocket相关的信号

QTcpSocket可以在这里找到相关的信号 进行信号槽的关联 connect():这个信号在connectToHost()被调用并且连接已经成功建立之后发出 disconnected():该信号在套接字断开连接时发出 stateChanged(QAbstractSocket::SocketState socketState):每当QAbstractSocket的状态发生变化…

【若依前后端分离】通过输入用户编号自动带出部门名称(部门树)

一、部门树 使用 <treeselect v-model"form.deptId" :options"deptOptions" :show-count"true" placeholder"请选择归属部门"/> <el-col :span"12"><el-form-item label"归属部门" prop"dept…

音视频开发—使用FFmpeg从纯H264码流中提取图片 C语言实现

文章目录 1.H264码流文件解码流程关键流程详细解码流程详细步骤解析 2.JPEG编码流程详细编码流程详细步骤解析 3.完整示例代码4.效果展示 从纯H.264码流中提取图片的过程包括解码和JPEG编码两个主要步骤&#xff0c;以下是详细阐述 1.H264码流文件解码流程 关键流程 查找编解…

敏捷开发笔记(第10章节)--Liskov原则(LSP)

目录 1&#xff1a;PDF上传链接 10.1 Liskov替换原则&#xff08;LSP&#xff09; 10.2 一个违反LSP的简单例子 10.6 启发式规则和习惯用法 10.7 结论 1&#xff1a;PDF上传链接 【免费】敏捷软件开发(原则模式与实践)资源-CSDN文库 OCP背后的主要机制是抽象(abstraction…

group 与查询字段

需求 每周周一&#xff0c;统计菜单在过去一周&#xff0c;点击次数&#xff0c;和点击人数&#xff08;同一个人访问多次按一次计算&#xff09; 表及数据 日志表 CREATE TABLE t_data_log ( id varchar(50) NOT NULL COMMENT 主键id, operation_object varchar(500) DE…

【D3.js in Action 3 精译】1.3 D3 视角下的数据可视化最佳实践(下)

当前内容所在位置 第一部分 D3.js 基础知识 第一章 D3.js 简介 ✔️ 1.1 何为 D3.js&#xff1f;1.2 D3 生态系统——入门须知 1.2.1 HTML 与 DOM1.2.2 SVG - 可缩放矢量图形1.2.3 Canvas 与 WebGL1.2.4 CSS1.2.5 JavaScript1.2.6 Node 与 JavaScript 框架1.2.7 Observable 记事…

我的世界1.21多种服务端开服教程,原版/Forge/Fabric/Paper/Mohist...,Minecraft开服教程

Minecraft&#xff08;MC&#xff09;1.21版多种服务端开服教程&#xff0c;我的世界1.21服务器搭建教程&#xff0c;MC原版/Forge/Fabric/Paper/Mohist服务端搭建教程&#xff0c;我的世界MOD/插件服开服教程。 本教程使用 Linux系统MCSManager 面板来搭建Minecraft服务器。 …

人工智能行业应用-垃圾识别一

垃圾识别应用主要体现在AI图像垃圾识别技术上&#xff0c;这是一种基于人工智能和计算机视觉技术的图像处理技术&#xff0c;广泛应用于各个领域以提高垃圾处理的效率和准确性。 1、垃圾识别效果图 2 垃圾识别任务分析 综合利用Python语言、Qt开发模块&#xff0c;OpenCV开发模…

数据结构(Java):单链表面试OJ题

1、题一&#xff1a;获取链表倒数第k个节点 . - 力扣&#xff08;LeetCode&#xff09; 1.1 思路解析 此题我们使用双指针法求解。 首先&#xff0c;我们要知道&#xff0c;倒数的第k个节点&#xff0c;距离倒数第一个节点还需要移动k-1次。 1.那么我们可以定义出两个指针&a…