Java I/O模式 (一)

news2024/9/22 17:25:16

第一章 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;
	}

	@Override
	public 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;
	}
	@Override
	public 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;
	}
	@Override
	public 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;
	}

	@Override
	public 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.coloradmin.cn/o/1921509.html

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

相关文章

调整网络安全策略以适应不断升级的威胁形势

关键网络安全统计数据和趋势 当今数字时代网络安全的重要性

【大语言模型应用形态 AI Agent 发展趋势深度分析 2024】

文末有福利&#xff01; 一、智能体&#xff08;AI Agent&#xff09; 1. 智能体正成为大模型重要研发方向 随着技术飞速发展&#xff0c;智能体&#xff08;AI Agent&#xff09;正成为一股革命性力量&#xff0c;正在重新定义人与数字系统互动的方式。AI Agent是一种高效、…

使用DeepWalk 和Word2Vec实现单词Embedding

0、准备“边”数据集Wiki_edgelist import pandas as pddf pd.read_csv(./data/wiki/Wiki_edgelist.txt, sep , headerNone, names["source", "target"]) df.head() 1、读入 起点-终点 边数据&#xff0c;构建图 # 1、读入 起点-终点 边数据&#xff0c…

如何评估独立站的外链质量?

要评估独立站的外链质量时&#xff0c;首先要看的不是别的&#xff0c;而是内容&#xff0c;跟你网站相关的文章内容才是最重要的&#xff0c;其他的一切其实都不重要。什么网站的DA&#xff0c;评级&#xff0c;网站的主要内容跟你的文章内容是否相关其实都不重要&#xff0c;…

git提交大文件服务500

错误如图 需保证git服务端能接收大文件 修改项目下.git文件中的config文件&#xff0c;加入 [http] postBuffer 524288000

逆变-TI视频课笔记

目录 1、全桥逆变 1.1、全桥逆变SPWM仿真 2、半桥逆变 2.1、本课小结 3、多重逆变&#xff08;间接的“交-直-交-直”变流&#xff09; 3.1、多电平逆变的目的 3.2、单逆变桥 3 电平控制时序 3.3、大功率设备的功率因数 3.4、本课小结 视频链接&#xff1a;文字…

怎样在 C 语言中实现堆排序?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01; &#x1f4d9;C 语言百万年薪修炼课程 【https://dwz.mosong.cc/cyyjc】通俗易懂&#xff0c;深入浅出&#xff0c;匠心打磨&#xff0c;死磕细节&#xff0c;6年迭代&…

小程序-设置环境变量

在实际开发中&#xff0c;不同的开发环境&#xff0c;调用的接口地址是不一样的 例如&#xff1a;开发环境需要调用开发版的接口地址&#xff0c;生产环境需要正式版的接口地址 这时候&#xff0c;我们就可以使用小程序提供了 wx.getAccountInfoSync() 接口&#xff0c;用来获取…

分享 | 一文简述模糊测试智能体技术实践

近日&#xff0c;华为2012实验室中央软件院旗下的欧拉多咖创新团队成功举办了【欧拉多咖 — 操作系统研讨会】。本次研讨会以“系统安全AI&#xff1f;”为主题&#xff0c;探讨了大模型技术如何推动基础软件迈向大规模算力时代&#xff0c;并详细讨论了在这一过程中系统软件所…

论文 | LEAST-TO-MOST PROMPTING ENABLES COMPLEXREASONING IN LARGE LANGUAGE MODELS

论文主题&#xff1a; 这篇论文提出了“从简单到复杂提示”&#xff08;Least-to-Most Prompting&#xff09;这一新的提示策略&#xff0c;旨在解决大语言模型在解决比提示示例更复杂的问题时表现不佳的难题。 核心思想&#xff1a; 将复杂问题分解成一系列更简单的子问题。按…

Verilog基础:操作数的位选(bit-select)和域选(part select)

相关阅读 Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html?spm1001.2014.3001.5482 位选 位选(bit-select)用于选择一个向量(vector)的某位&#xff0c;可以是线网大类(net)&#xff0c;也可以是变量大类(variable)中的reg、integer和time&#xf…

Redis 主从复制,哨兵与集群

目录 一.redis主从复制 1.redis 主从复制架构 2.主从复制特点 3.主从复制的基本原理 4.命令行配置 5.实现主从复制 6.删除主从复制 7.主从复制故障恢复 8.主从复制完整过程 9.主从同步优化配置 二.哨兵模式&#xff08;Sentinel&#xff09; 1.主要组件和概念 2.哨…

半小时获得一张ESG入门证书【详细中英文笔记一】

前些日子&#xff0c;有朋友转发了一则小红书的笔记给我&#xff0c; 标题是《半小时获CFI官方高颜值免费证书 ESG认证》。这对考证狂魔的我来说&#xff0c;必然不能错过啊&#xff0c;有免费的羊毛不薅白不薅。 ESG课程的 CFI 官方网址戳这里&#xff1a;CFI 于是信心满满的…

Electron运行报错:Error Cannot find module ‘node_moduleselectroncli.js‘

Electron运行报错&#xff1a;Error: Cannot find module ‘node_modules\electron\cli.js’ 顾名思义&#xff0c;命令行执行Electron .时候&#xff0c;会优先从项目目录查找对应依赖&#xff0c;如果是报错显示是找不到项目目录下的依赖&#xff0c;我们可以从安装在全局的…

轮转数组(时间复杂度不同的三种思路)

&#xff08;来源&#xff1a;LeetCode&#xff09; 题目 分析 其实一次轮转就是将最后一个数据放到最前面&#xff0c;其他数据整体向后移动一位。k为几就重复这个行为几次。 思路1 我们应该很快能想到最直接的一种思路。while(k--)……循环内完成两件事&#xff0c;保存最…

【C++】函数重载详解

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

Python面试全攻略:基础知识、特性、算法与实战解析

随着Python的普及&#xff0c;越来越多的人开始学习Python并尝试在面试中展示自己的技能。在这篇文章中&#xff0c;我们将探讨Python面试需要注意的问题以及一些经典的Python算法。 一、Python面试需要注意的问题 基础知识 在Python面试中&#xff0c;基础知识是非常重要的。…

开源浪潮:助力未来科技的飞速发展

文章目录 开源项目有哪些机遇与挑战&#xff1f;开源项目的发展趋势发展现状开源社区的活跃度 我是如何参与开源项目的经验分享选择开源项目贡献代码 开源项目的挑战开源项目面临的挑战 开源项目有哪些机遇与挑战&#xff1f; 随着全球经济和科技环境的快速变化&#xff0c;开源…

设计模式 - 最简单最有趣的方式讲述

别名《我替你看Head First设计模式》 本文以故事的形式带你从0了解设计模式&#xff0c;在其中你仅仅是一名刚入职的实习生&#xff0c;在项目中摸爬滚打。&#xff08;以没有一行真正代码的形式&#xff0c;让你无压力趣味学习&#xff09; 设计模式 策略模式观察者模式装饰者…

【简历】重庆某一本大学:JAVA简历指导,中厂通过率较低

注&#xff1a;为保证用户信息安全&#xff0c;姓名和学校等信息已经进行同层次变更&#xff0c;内容部分细节也进行了部分隐藏 简历说明 这是一份重庆某一本大学Java同学的简历。那么因为学校是一个一本的学校&#xff0c;就先要确定就业层次在中厂或者大厂&#xff0c;但是…