BIONIOAIO
通信技术整体解决的问题
1.局域网内的通信要求
2.多系统间的底层消息传递机制
3.高并发下,大数据量的通信场景需要
4.游戏行业。无论是手游服务端、还是大型网络游戏,java的应用越来越广
IO模型基本说明
就是用什么样的通道或者说是通信模式和架构进行数据的传输和接收,很大程度上决定了程序通信性能,Java共支持三种网络编程的IO模型:BIO、NIO、AIO
实际通信需求下,要根据不同的业务场景和性能要求决定选择不同的IO模型
IO模型
Java BIO
同步并阻塞(传统阻塞型),服务器实现模式为一个连接 一个线程,即客户端有连接请求时服务器端就需要启动 一个线程进行处理,如果这个连接不做任何事情会造成 不必要的线程开销
Java NIO
Java NIO : 同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求就进行处理
Java AIO
Java AIO(NIO.2) : 异步 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,一般适用于连接数较多且连接时间较长的应用
适用场景
1 BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序简单易理解。
2 NIO 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务器间通讯等。编程比较复杂,JDK1.4 开始支持。
3 AIO 方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用 OS 参与并发操作,编程比较复杂,JDK7 开始支持。
JAVA BIO的深入剖析
Java BIO 就是传统的 java io 编程,其相关的类和接口在 java.io
BIO(blocking I/O) : 同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户连接服务器).
比如IO包下的字节流 字符流 就是BIO
JAVA BIO的工作机制
案例
BIO模式下单发和单收消息
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;public class ClientDemo {public static void main(String[] args) throws Exception {System.out.println("==客户端的启动==");// (1)创建一个Socket的通信管道,请求与服务端的端口连接。Socket socket = new Socket("127.0.0.1",8888);// (2)从Socket通信管道中得到一个字节输出流。OutputStream os = socket.getOutputStream();// (3)把字节流改装成自己需要的流进行数据的发送PrintStream ps = new PrintStream(os);// (4)开始发送消息ps.println("我是客户端,你好");ps.flush();}
}
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;/*** 服务端*/
public class ServerDemo {public static void main(String[] args) throws Exception {System.out.println("==服务器的启动==");// (1)注册端口ServerSocket serverSocket = new ServerSocket(8888);//(2)开始在这里暂停等待接收客户端的连接,得到一个端到端的Socket管道Socket socket = serverSocket.accept();//(3)从Socket管道中得到一个字节输入流。InputStream is = socket.getInputStream();//(4)把字节输入流包装成自己需要的流进行数据的读取。BufferedReader br = new BufferedReader(new InputStreamReader(is));//(5)读取数据String line ;if((line = br.readLine())!=null){//为什么要用if 因为客户端发了一句话就会关闭管道,但是如果使用//服务端还是会等待消息发第二条过来,但是管道关闭了,那么服务端就会宕机//所以应该要用if去让接收完一条消息后就根关闭 System.out.println("服务端收到:"+line);}}
}
BIO实现多发多收
在第一份代码上进行修改
用Scanner扫描仪优化
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;public class ClientDemo {public static void main(String[] args) throws Exception {System.out.println("==客户端的启动==");// (1)创建一个Socket的通信管道,请求与服务端的端口连接。Socket socket = new Socket("127.0.0.1",8888);// (2)从Socket通信管道中得到一个字节输出流。OutputStream os = socket.getOutputStream();// (3)把字节流改装成自己需要的流进行数据的发送PrintStream ps = new PrintStream(os);Scanner sc=new Scanner(System.in);while(true){System.out.print("请说:");String msg=sc.nextLine();ps.println(msg);ps.flush();}}
}
服务端就把if改成while就行
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;/*** 服务端*/
public class ServerDemo {public static void main(String[] args) throws Exception {System.out.println("==服务器的启动==");// (1)注册端口ServerSocket serverSocket = new ServerSocket(8888);//(2)开始在这里暂停等待接收客户端的连接,得到一个端到端的Socket管道Socket socket = serverSocket.accept();//(3)从Socket管道中得到一个字节输入流。InputStream is = socket.getInputStream();//(4)把字节输入流包装成自己需要的流进行数据的读取。BufferedReader br = new BufferedReader(new InputStreamReader(is));//(5)读取数据String line ;while((line = br.readLine())!=null){System.out.println("服务端收到:"+line);}}
}
BIO实现接收多个客户端
我们之前的代码只能实现一个服务端接收一个客户端 为什么这样说?
因为之前服务端的代码只有一次接收服务端请求的代码
Socket socket = serverSocket.accept();
如果想要实现多接收客户端,那么就要用多线程实现
每次serverSocket.accept();时就创建一个线程
服务端代码
/**服务端*/
public class ServerDemo {public static void main(String[] args) throws Exception {System.out.println("==服务器的启动==");// (1)注册端口ServerSocket serverSocket = new ServerSocket(7777);while(true){//(2)开始在这里暂停等待接收客户端的连接,得到一个端到端的Socket管道Socket socket = serverSocket.accept();new ServerReadThread(socket).start();System.out.println(socket.getRemoteSocketAddress()+"上线了!");}}
}class ServerReadThread extends Thread{private Socket socket;public ServerReadThread(Socket socket){this.socket = socket;}@Overridepublic void run() {try{//(3)从Socket管道中得到一个字节输入流。InputStream is = socket.getInputStream();//(4)把字节输入流包装成自己需要的流进行数据的读取。BufferedReader br = new BufferedReader(new InputStreamReader(is));//(5)读取数据String line ;while((line = br.readLine())!=null){System.out.println("服务端收到:"+socket.getRemoteSocketAddress()+":"+line);}}catch (Exception e){System.out.println(socket.getRemoteSocketAddress()+"下线了!");}}
}
客户端不变
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;public class ClientDemo {public static void main(String[] args) throws Exception {System.out.println("==客户端的启动==");// (1)创建一个Socket的通信管道,请求与服务端的端口连接。Socket socket = new Socket("127.0.0.1",8888);// (2)从Socket通信管道中得到一个字节输出流。OutputStream os = socket.getOutputStream();// (3)把字节流改装成自己需要的流进行数据的发送PrintStream ps = new PrintStream(os);Scanner sc=new Scanner(System.in);while(true){System.out.print("请说:");String msg=sc.nextLine();ps.println(msg);ps.flush();}}
}
对于这个案例的小结
1 每个Socket接收到,都会创建一个线程,线程的竞争、切换上下文影响性能;
2 每个线程都会占用栈空间和CPU资源;
3 并不是每个socket都进行IO操作,无意义的线程处理;
4 客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。
伪异步IO编程
在上述案例中:客户端的并发访问增加时。服务端将呈现1:1的 线程开销,访问量越大,系统将发生线程栈溢出,线程创建失 败,最终导致进程宕机或者僵死,从而不能对外提供服务。
接下来我们采用一个伪异步I/O的通信框架,采用线程池和任 务队列实现,当客户端接入时,将客户端的Socket封装 成一个Task(该任务实现java.lang.Runnable线程任务接口) 交给后端的线程池中进行处理。JDK的线程池维护一个消息 队列和N个活跃的线程,对消息队列中Socket任务进行处 理,由于线程池可以设置消息队列的大小和最大线程 数,因此,它的资源占用是可控的,无论多少个客户端 并发访问,都不会导致资源的耗尽和宕机。
服务端
public class Server {public static void main(String[] args) {try {System.out.println("----------服务端启动成功------------");ServerSocket ss = new ServerSocket(9999);// 一个服务端只需要对应一个线程池HandlerSocketThreadPool handlerSocketThreadPool =new HandlerSocketThreadPool(3, 1000);// 客户端可能有很多个while(true){Socket socket = ss.accept() ; // 阻塞式的!System.out.println("有人上线了!!");// 每次收到一个客户端的socket请求,都需要为这个客户端分配一个// 独立的线程 专门负责对这个客户端的通信!!handlerSocketThreadPool.execute(new ReaderClientRunnable(socket));}} catch (Exception e) {e.printStackTrace();}}}
class ReaderClientRunnable implements Runnable{private Socket socket ;public ReaderClientRunnable(Socket socket) {this.socket = socket;}@Overridepublic void run() {try {// 读取一行数据InputStream is = socket.getInputStream() ;// 转成一个缓冲字符流Reader fr = new InputStreamReader(is);BufferedReader br = new BufferedReader(fr);// 一行一行的读取数据String line = null ;while((line = br.readLine())!=null){ // 阻塞式的!!System.out.println("服务端收到了数据:"+line);}} catch (Exception e) {System.out.println("有人下线了");}}
}
线程池
// 线程池处理类
public class HandlerSocketThreadPool {// 线程池 private ExecutorService executor;public HandlerSocketThreadPool(int maxPoolSize, int queueSize){this.executor = new ThreadPoolExecutor(3, // 8maxPoolSize, 120L, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(queueSize) );}public void execute(Runnable task){this.executor.execute(task);}
}
客户端不变
public class Client {public static void main(String[] args) {try {// 1.简历一个与服务端的Socket对象:套接字Socket socket = new Socket("127.0.0.1", 9999);// 2.从socket管道中获取一个输出流,写数据给服务端 OutputStream os = socket.getOutputStream() ;// 3.把输出流包装成一个打印流 PrintWriter pw = new PrintWriter(os);// 4.反复接收用户的输入 BufferedReader br = new BufferedReader(new InputStreamReader(System.in));String line = null ;while((line = br.readLine()) != null){pw.println(line);pw.flush();}} catch (Exception e) {e.printStackTrace();}}
}
小结
伪异步io采用了线程池实现,因此避免了为每个请求创建一个 独立线程造成线程资源耗尽的问题,但由于底层依然是采用 的同步阻塞模型,因此无法从根本上解决问题。 如果单个消息处理的缓慢,或者服务器线程池中的全部线程都 被阻塞,那么后续socket的i/o消息都将在队列中排队。新的 Socket请求将被拒绝,客户端会发生大量连接超时。
基于BIO的文件上传
上传任意类型的文件数据
客户端
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.Socket;/**目标:实现客户端上传任意类型的文件数据给服务端保存起来。*/
public class Client {public static void main(String[] args) {try(InputStream is = new FileInputStream("C:\\Users\\dlei\\Desktop\\BIO,NIO,AIO\\文件\\java.png");){// 1、请求与服务端的Socket链接Socket socket = new Socket("127.0.0.1" , 8888);// 2、把字节输出流包装成一个数据输出流DataOutputStream dos = new DataOutputStream(socket.getOutputStream());// 3、先发送上传文件的后缀给服务端dos.writeUTF(".png");// 4、把文件数据发送给服务端进行接收byte[] buffer = new byte[1024];int len;while((len = is.read(buffer)) > 0 ){dos.write(buffer , 0 , len);}dos.flush();socket.shutdownOutput();//通知服务端已经发完了}catch (Exception e){e.printStackTrace();}}
}
服务端
import java.net.ServerSocket;
import java.net.Socket;/**目标:服务端开发,可以实现接收客户端的任意类型文件,并保存到服务端磁盘。*/
public class Server {public static void main(String[] args) {try{ServerSocket ss = new ServerSocket(8888);while (true){Socket socket = ss.accept();// 交给一个独立的线程来处理与这个客户端的文件通信需求。new ServerReaderThread(socket).start();}}catch (Exception e){e.printStackTrace();}}
}
import java.io.DataInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.UUID;public class ServerReaderThread extends Thread {private Socket socket;public ServerReaderThread(Socket socket){this.socket = socket;}@Overridepublic void run() {try{// 1、得到一个数据输入流读取客户端发送过来的数据DataInputStream dis = new DataInputStream(socket.getInputStream());// 2、读取客户端发送过来的文件类型String suffix = dis.readUTF();System.out.println("服务端已经成功接收到了文件类型:" + suffix);// 3、定义一个字节输出管道负责把客户端发来的文件数据写出去OutputStream os = new FileOutputStream("C:\\Users\\dlei\\Desktop\\BIO,NIO,AIO\\文件\\server\\"+UUID.randomUUID().toString()+suffix);// 4、从数据输入流中读取文件数据,写出到字节输出流中去byte[] buffer = new byte[1024];int len;while((len = dis.read(buffer)) > 0){os.write(buffer,0, len);}os.close();System.out.println("服务端接收文件保存成功!");}catch (Exception e){e.printStackTrace();}}
}
JAVA BIO模式下的端口转发思想
需求:需要实现一个客户端的消息可以发送给所有客户端接收
JAVA NIO深入剖析
Java NIO(New IO)也有人称之为 java non-blocking IO是 从Java 1.4版本开始引入的一个新的IO API,可以替代标准 的Java IO API。NIO与原来的IO有同样的作用和目的,但 是使用的方式完全不同,NIO支持面向缓冲区的、基于 通道的IO操作。
NIO将以更加高效的方式进行文件的读 写操作。NIO可以理解为非阻塞IO,传统的IO的read和write只 能阻塞执行,线程在读写IO期间不能干其他事情,比如 调用socket.read()时,如果服务器一直没有数据传输 过来,线程就一直阻塞,而NIO中可以配置socket为非阻塞模式。
NIO 相关类都被放在 java.nio 包及子包下,并且对 原 java.io 包中的很多类进行改写。
NIO 有三大核心部分: Channel( 通道) ,Buffer( 缓冲区), Selector( 选择器)
Java NIO 的非阻塞模式,使一个线程从某通道发送 请求或者读取数据,但是它仅能得到目前可用的数据,如 果目前没有数据可用时,就什么都不会获取,而不是保 持线程阻塞,所以直至数据变的可以读取之前,该线程 可以继续做其他的事情。 非阻塞写也是如此,一个线程 请求写入一些数据到某通道,但不需要等待它完全写 入,这个线程同时可以去做别的事情。
通俗理解:NIO 是可以做到用一个线程来处理多个操 作的。假设有 1000 个请求过来,根据实际情况,可以分 配20 或者 80个线程来处理。不像之前的阻塞 IO 那 样,非得分配 1000 个。
NIO和BIO扥比较
BIO 以流的方式处理数据,而 NIO 以块的方式处理数据 ,块 I/O 的效率比流 I/O 高很多
BIO 是阻塞的,NIO 则是非阻塞的
BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道
NIO三大核心原理示意图
Buffer缓冲区
缓冲区本质上是一块可以写入数据,然后可以从中读取 数据的内存。这块内存被包装成NIO Buffer对象,并提供 了一组方法,用来方便的访问该块内存。相比较直接对 数组的操作,Buffer API更加容易操作和管理。
Channel(通道)
Java NIO的通道类似流,但又有些不同:既可以从通道 中读取数据,又可以写数据到通道。但流的(input或output)读写 通常是单向的。 通道可以非阻塞读取和写入通道,通道可 以支持读取或写入缓冲区,也支持异步地读写。
Selector选择器
Selector是 一个Java NIO组件,可以能够检查一个 或多个 NIO 通道,并确定哪些通道已经准备好进行读 取或写入。这样,一个单独的线程可以管理多 个channel,从而管理多个网络连接,提高效率
每个 channel 都会对应一个 Buffer
一个线程对应Selector , 一个Selector对应多个 channel(连接) 程序切换到哪个 channel 是由事件决定的 Selector 会根据不同的事件,在各个通道上切换 Buffer 就是一个内存块 , 底层是一个数组 数据的读取写入是通过 Buffer完成的 , BIO 中要么是输入流,或 者是输出流, 不能双向,但是 NIO 的 Buffer 是可以读也可以写。
Java NIO系统的核心在于:通道(Channel)和缓冲区 (Buffer)。 通道表示打开到 IO 设备(例如:文件、 套接字)的连接。若需 要使用 NIO 系统,需要获取 用于连接 IO 设备的通道以及 用于容纳数据的缓冲 区。然后操作缓冲区,对数据进 行处理。简而言之,Channel 负责传输, Buffer 负责 存取数据
NIO核心一:缓冲区(Buffer)
一个用于特定基本数据类型的容器。由 java.nio 包定义的,所 有缓冲区 都是 Buffer 抽象类的子类.。Java NIO 中 的 Buffer 主要用于与 NIO 通道进行 交互,数据是从通道 读入缓冲区,从缓冲区写入通道中的
Buffer类机器子类
Buffer就像一个数组,可以保存多个相同类型的数据。
根据 数据类型不同 ,有以下 Buffer 常用子类:
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
上述 Buffer 类他们都采用相似的方法进行管理数据,只 是各自 管理的数据类型不同而已。都是通过如下方法获 取一个 Buffer 对象:
static XxxBuffer allocate(int capacity) :
创建一个容量为capacity 的 XxxBuffer 对象
缓冲区的基本属性
Buffer 中的重要概念:
容量 (capacity) :作为一个内存块,Buffer具有一定的固定大小, 也称为"容量",缓冲区容量不能为负,并且创建后不能更改。
限制 (limit):表示缓冲区中可以操作数据的大小 (limit 后数据不能进行读写)。缓冲区的限制不能 为负,并且不能大于其容量。 写入模式,限制等于 buffer的容量。读取模式下,limit等于写入的数据量。
位置 (position):下一个要读取或写入的数据的索引。 缓冲区的位置不能为 负,并且不能大于其限制
标记 (mark)与重置 (reset):标记是一个索引, 通过 Buffer 中的 mark() 方法 指定 Buffer 中一个 特定的 position,之后可以通过调用 reset() 方法恢 复到这 个 position.
标记、位置、限制、容量遵守以下不变式:
0 <= mark <= position <= limit <= capacity
Buffer常见方法
Buffer clear() 清空缓冲区并返回对缓冲区的引用
Buffer flip() 为 将缓冲区的界限设置为当前位置,
并将当前位置充值为 0
int capacity() 返回 Buffer 的 capacity 大小
boolean hasRemaining() 判断缓冲区中是否还有元素
int limit() 返回 Buffer 的界限(limit) 的位置
Buffer limit(int n) 将设置缓冲区界限为 n,并返回一个具有新 limit 的缓冲区对象
Buffer mark() 对缓冲区设置标记
int position() 返回缓冲区的当前位置 position
Buffer position(int n) 将设置缓冲区的当前位置为 n,
并返回修改后的 Buffer 对象
int remaining() 返回 position 和 limit 之间的元素个数
Buffer reset() 将位置 position 转到以前设置的mark
所在的位置
Buffer rewind() 将位置设为为 0, 取消设置的 mark
缓冲区的数据操作
Buffer 所有子类提供了两个用于数据操作的方法:get()put()
方法取获取 Buffer中的数据
get() :读取单个字节
get(byte[] dst):批量读取多个字节到 dst 中
get(int index):读取指定索引位置的字节(不会移动 position)放到 入数据到 Buffer 中 中
put(byte b):将给定单个字节写入缓冲区的当前位置
put(byte[] src):将 src 中的字节写入缓冲区的当前位置
put(int index, byte b):将指定字节写入缓冲区的索引
位置(不会移动 position)
使用Buffer读写数据一般遵循以下四个步骤
1 写入数据到Buffer
2 调用flip()方法,转换为读取模式
3 从Buffer中读取数据
4 调用buffer.clear()方法或者buffer.compact()方 法清除缓冲区
直接与非直接缓冲区
根据官方文档的描述: byte byffer可以是两种类型,一种是基于直接内存(也就是 非堆内存);另一种是非直接内存(也就是堆内存)。对于直 接内存来说,JVM将会在IO操作上具有更高的性能,因为它 直接作用于本地系统的IO操作。而非直接内存,也就是堆内 存中的数据,如果要作IO操作,会先从本进程内存复制到直接 内存,再利用本地IO处理。
从数据流的角度,非直接内存是下面这样的作用链:
本地IO-->直接内存-->非直接内存-->直接内存-->本地IO
而直接内存是: 本地IO-->直接内存-->本地IO
很明显,在做IO处理时,比如网络发送大量数据时,直接内 存会具有更高的效率。直接内存使用allocateDirect创建,但 是它比申请普通的堆内存需要耗费更高的性能。不过,这 部分的数据是在JVM之外的,因此它不会占用应用的内 存。所以呢,当你有很大的数据要缓存,并且它的生命 周期又很长,那么就比较适合使用直接内存。只是一般 来说,如果不是能带来很明显的性能提升,还是推荐直接 使用堆内存。字节缓冲区是直接缓冲区还是非直接缓冲 区可通过调用其 isDirect() 方法来确定。
直接内存使用场景
1 有很大的数据需要存储,它的生命周期又很长
2 适合频繁的IO操作,比如网络并发场景
NIO核心二:通道(Channel)
通道Channe概述
通道(Channel):由 java.nio.channels 包定义 的。 Channel 表示 IO 源与目标打开的连接。 Channel 类似于传统的“流”。只不过 Channel 本身不 能直接访问数据,Channel 只能与 Buffer 进行交互。
1 NIO 的通道类似于流,但有些区别如下:
通道可以同时进行读写,而流只能读或者只能写
通道可以实现异步读写数据
通道可以从缓冲读数据,也可以写数据到缓冲
2 BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel) 是双向的,可以读操作,也可以写操作。
3 Channel 在 NIO 中是一个接口
public interface Channel extends Closeable{}
常见的Channel实现类
FileChannel:用于读取、写入、映射和操作文件的通道。
DatagramChannel:通过 UDP 读写网络中的数据通道。
SocketChannel:通过 TCP 读写网络中的数据。
ServerSocketChannel:可以监听新进来的 TCP 连接, 对每一个新进来的连接都会创建一个 SocketChannel。 【ServerSocketChanne 类似 ServerSocket , SocketChannel 类似 Socket】
FileChannel类
获取通道的一种方式是对支持通道的对象调用getChannel() 方法。 支持通道的类如下: FileInputStream
FileOutputStream
RandomAccessFile
DatagramSocket
Socket
ServerSocket
获取通道的其他方式是使用 Files 类的静态方法 newByteChannel() 获取字节通道。或者通过通道的静态 方法 open() 打开并返回指定通道
FileChannel的常用方法
int read(ByteBuffer dst) 从Channel到中读取数据到
ByteBuffer
long read(ByteBuffer[] dsts) 将Channel到中的数
据“分散”到ByteBuffer[]
int write(ByteBuffer src)将ByteBuffer 到中的
数据写入到 Channel
long write(ByteBuffer[] srcs)将ByteBuffer[] 到中
的数据“聚集”到 Channel
long position() 返回此通道的文件位置
FileChannel position(long p) 设置此通道的文件位置
long size() 返回此通道的文件的当前大小
FileChannel truncate(long s) 将此通道的文件截取为给定大小
void force(boolean metaData) 强制将所有对此通道的文
件更新写入到存储设备中
案例1-本地文件写数据
import org.junit.Test;import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;public class ChannelTest {@Testpublic void write(){try {// 1、字节输出流通向目标文件FileOutputStream fos = new FileOutputStream("data01.txt");// 2、得到字节输出流对应的通道ChannelFileChannel channel = fos.getChannel();// 3、分配缓冲区ByteBuffer buffer = ByteBuffer.allocate(1024);buffer.put("hello,黑马Java程序员!".getBytes());// 4、把缓冲区切换成写出模式buffer.flip();channel.write(buffer);channel.close();System.out.println("写数据到文件中!");} catch (Exception e) {e.printStackTrace();}}
}
案例2-本地文件读数据
public class ChannelTest {@Testpublic void read() throws Exception {// 1、定义一个文件字节输入流与源文件接通FileInputStream is = new FileInputStream("data01.txt");// 2、需要得到文件字节输入流的文件通道FileChannel channel = is.getChannel();// 3、定义一个缓冲区ByteBuffer buffer = ByteBuffer.allocate(1024);// 4、读取数据到缓冲区channel.read(buffer);buffer.flip();// 5、读取出缓冲区中的数据并输出即可String rs = new String(buffer.array(),0,buffer.remaining());System.out.println(rs);}
案例3-使用Buffer完成文件复制
@Test
public void copy() throws Exception {// 源文件File srcFile = new File("C:\\Users\\dlei\\Desktop\\BIO,NIO,AIO\\文件\\壁纸.jpg");File destFile = new File("C:\\Users\\dlei\\Desktop\\BIO,NIO,AIO\\文件\\壁纸new.jpg");// 得到一个字节字节输入流FileInputStream fis = new FileInputStream(srcFile);// 得到一个字节输出流FileOutputStream fos = new FileOutputStream(destFile);// 得到的是文件通道FileChannel isChannel = fis.getChannel();FileChannel osChannel = fos.getChannel();// 分配缓冲区ByteBuffer buffer = ByteBuffer.allocate(1024);while(true){// 必须先清空缓冲然后再写入数据到缓冲区buffer.clear();// 开始读取一次数据int flag = isChannel.read(buffer);if(flag == -1){break;}// 已经读取了数据 ,把缓冲区的模式切换成可读模式buffer.flip();// 把数据写出到osChannel.write(buffer);}isChannel.close();osChannel.close();System.out.println("复制完成!");
}
案例4-分散 (Scatter) 和聚集 (Gather)
分散读取(Scatter ):是指把Channel通道的数据读入到 多个缓冲区中去
聚集写入(Gathering )是指将多个 Buffer 中的数 据“聚集”到 Channel。
//分散和聚集
@Test
public void test() throws IOException{RandomAccessFile raf1 = new RandomAccessFile("1.txt", "rw");//1. 获取通道FileChannel channel1 = raf1.getChannel();//2. 分配指定大小的缓冲区ByteBuffer buf1 = ByteBuffer.allocate(100);ByteBuffer buf2 = ByteBuffer.allocate(1024);//3. 分散读取ByteBuffer[] bufs = {buf1, buf2};channel1.read(bufs);for (ByteBuffer byteBuffer : bufs) {byteBuffer.flip();}System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));System.out.println("-----------------");System.out.println(new String(bufs[1].array(), 0, bufs[1].limit()));//4. 聚集写入RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw");FileChannel channel2 = raf2.getChannel();channel2.write(bufs);
}
案例5-transferFrom()
从目标通道中去复制原通道数据
@Test
public void test02() throws Exception {// 1、字节输入管道FileInputStream is = new FileInputStream("data01.txt");FileChannel isChannel = is.getChannel();// 2、字节输出流管道FileOutputStream fos = new FileOutputStream("data03.txt");FileChannel osChannel = fos.getChannel();// 3、复制osChannel.transferFrom(isChannel,isChannel.position(),isChannel.size());isChannel.close();osChannel.close();
}
案例6-transferTo()
把原通道数据复制到目标通道
@Test
public void test02() throws Exception {// 1、字节输入管道FileInputStream is = new FileInputStream("data01.txt");FileChannel isChannel = is.getChannel();// 2、字节输出流管道FileOutputStream fos = new FileOutputStream("data04.txt");FileChannel osChannel = fos.getChannel();// 3、复制isChannel.transferTo(isChannel.position() , isChannel.size() , osChannel);isChannel.close();osChannel.close();
}
NIO核心三:选择器(Selector)
选择器(Selector) 是 SelectableChannle 对象的多路复用器,Selector 可以同时监控多个 SelectableChannel 的 IO 状况,也就是说,利用 Selector可使一个单独的线程管理多
个 Channel。Selector 是非阻塞 IO 的核心
Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到 Selector(选择器)
Selector 能够检测多个注册的通道上是否有事件发生(注意:多个 Channel 以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。
只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程避免了多线程之间的上下文切换导致的开销
选择器(Selector)的应用
创建 Selector :通过调用 Selector.open() 方法创建一个 Selector。
Selector selector = Selector.open();
向选择器注册通道:SelectableChannel.register(Selector sel, int ops)
//1. 获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//2. 切换非阻塞模式
ssChannel.configureBlocking(false);
//3. 绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//4. 获取选择器
Selector selector = Selector.open();
//5. 将通道注册到选择器上, 并且指定“监听接收事件”
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
当调用 register(Selector sel, int ops) 将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数 ops 指定。可以监听的事件类型(用 可使用 SelectionKey 的四个常量 表示):
读 : SelectionKey.OP_READ (1)
写 : SelectionKey.OP_WRITE (4)
连接 : SelectionKey.OP_CONNECT (8)
接收 : SelectionKey.OP_ACCEPT (16)
若注册时不止监听一个事件,则可以使用“位或”操作符连接。
int interestSet = SelectionKey.OP_READ|SelectionKey.OP_WRITE
NIO非阻塞式网络通信原理分析
Selector可以实现: 一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。
服务端流程
1 当客户端连接服务端时,服务端会通过 ServerSocketChannel 得到 SocketChannel:1 获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
2 切换非阻塞模式
ssChannel.configureBlocking(false);
3 绑定连接
ssChannel.bind(new InetSocketAddress(9999));
4 获取选择器
Selector selector = Selector.open();
5 将通道注册到选择器上, 并且指定“监听接收事件”
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
6 轮询式的获取选择器上已经“准备就绪”的事件
//轮询式的获取选择器上已经“准备就绪”的事件while (selector.select() > 0) {System.out.println("轮一轮");//7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”Iterator<SelectionKey> it = selector.selectedKeys().iterator();while (it.hasNext()) {//8. 获取准备“就绪”的是事件SelectionKey sk = it.next();//9. 判断具体是什么事件准备就绪if (sk.isAcceptable()) {//10. 若“接收就绪”,获取客户端连接SocketChannel sChannel = ssChannel.accept();//11. 切换非阻塞模式sChannel.configureBlocking(false);//12. 将该通道注册到选择器上sChannel.register(selector, SelectionKey.OP_READ);} else if (sk.isReadable()) {//13. 获取当前选择器上“读就绪”状态的通道SocketChannel sChannel = (SocketChannel) sk.channel();//14. 读取数据ByteBuffer buf = ByteBuffer.allocate(1024);int len = 0;while ((len = sChannel.read(buf)) > 0) {buf.flip();System.out.println(new String(buf.array(), 0, len));buf.clear();}}//15. 取消选择键 SelectionKeyit.remove();}}}
客户端流程
1 获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
2 切换非阻塞模式
sChannel.configureBlocking(false);
3 分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
4 发送数据给服务端
Scanner scan = new Scanner(System.in);while(scan.hasNext()){String str = scan.nextLine();buf.put((new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(System.currentTimeMillis())+ "\n" + str).getBytes());buf.flip();sChannel.write(buf);buf.clear();}//关闭通道sChannel.close();
NIO非阻塞式网络通信入门案例
需求:服务端接收客户端的连接请求,并接收多个客户端发 送过来的事件。
/**客户端*/
public class Client {public static void main(String[] args) throws Exception {//1. 获取通道SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));//2. 切换非阻塞模式sChannel.configureBlocking(false);//3. 分配指定大小的缓冲区ByteBuffer buf = ByteBuffer.allocate(1024);//4. 发送数据给服务端Scanner scan = new Scanner(System.in);while(true){String str = scan.nextLine();buf.put(("有人说:"+str).getBytes());buf.flip();sChannel.write(buf);buf.clear();}//5. 关闭通道sChannel.close();}
}/**服务端*/
public class Server {public static void main(String[] args) throws IOException {//1. 获取通道ServerSocketChannel ssChannel = ServerSocketChannel.open();//2. 切换非阻塞模式ssChannel.configureBlocking(false);//3. 绑定连接ssChannel.bind(new InetSocketAddress(9999));//4. 获取选择器Selector selector = Selector.open();//5. 将通道注册到选择器上, 并且指定“监听接收事件”ssChannel.register(selector, SelectionKey.OP_ACCEPT);//6. 轮询式的获取选择器上已经“准备就绪”的事件while (selector.select() > 0) {System.out.println("轮一轮");//7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”Iterator<SelectionKey> it = selector.selectedKeys().iterator();while (it.hasNext()) {//8. 获取准备“就绪”的是事件SelectionKey sk = it.next();//9. 判断具体是什么事件准备就绪if (sk.isAcceptable()) {//10. 若“接收就绪”,获取客户端连接SocketChannel sChannel = ssChannel.accept();//11. 切换非阻塞模式sChannel.configureBlocking(false);//12. 将该通道注册到选择器上sChannel.register(selector, SelectionKey.OP_READ);} else if (sk.isReadable()) {//13. 获取当前选择器上“读就绪”状态的通道SocketChannel sChannel = (SocketChannel) sk.channel();//14. 读取数据ByteBuffer buf = ByteBuffer.allocate(1024);int len = 0;while ((len = sChannel.read(buf)) > 0) {buf.flip();System.out.println(new String(buf.array(), 0, len));buf.clear();}}//15. 取消选择键 SelectionKeyit.remove();}}}
}
JAVA AIO深入剖析
Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
AIO
异步非阻塞,基于NIO的,可以称之为NIO2.0BIO NIO AIO
Socket SocketChannel AsynchronousSocketChannel
ServerSocket ServerSocketChannel AsynchronousServerSocketChannel与NIO不同,当进行读写操作时,只须直接调用API的
read或write方法即可, 这两种方法均为异步的,对于
读操作而言,当有流可读取时,操作系统会将可读的
流传入read方法的缓冲区,对于写操作而言,当操作
系统将write方法传递的流写入完毕时,操作系统
主动通知应用程序即可以理解为,read/write方法都是异步的,完成后
会主动调用回调函数。在JDK1.7中,这部分内容被
称作NIO.2,主要在Java.nio.channels包下增加
了下面四个异步通道:
AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel
课程总结
BIO、NIO、AIO:
Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,
即客户端有连接请求时服务器端就需要启动一个线程进行处
理,如果这个连接不做任何事情会造成不必要的线程开销,
当然可以通过线程池机制改善。
Java NIO : 同步非阻塞,服务器实现模式为一个请求一个
线程,即客户端发送的连接请求都会注册到多路复用器
上,多路复用器轮询到连接有I/O请求时才启动一个线程
进行处理。
Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有
效请求一个线程,客户端的I/O请求都是由OS先完成了
再通知服务器应用去启动线程进行处理。BIO、NIO、AIO适用场景分析:
BIO方式适用于连接数目比较小且固定的架构,这种方
式对服务器资源要求比较高,并发局限于应用中,
JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的
架构,比如聊天服务器,并发局限于应用中,编程比较
复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的
架构,比如相册服务器,充分调用OS参与并发操作,编
程比较复杂,JDK7开始支持。Netty!
相关文章:

BIONIOAIO
通信技术整体解决的问题 1.局域网内的通信要求 2.多系统间的底层消息传递机制 3.高并发下,大数据量的通信场景需要 4.游戏行业。无论是手游服务端、还是大型网络游戏,java的应用越来越广 IO模型基本说明 就是用什么样的通道或者说是通信模式和架构…...

SpringSecurity学习总结(三更草堂)
SpringSecurity安全框架的核心功能是认证和授权: 认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户。 授权:经过认证后判断当前用户是否具有进行某个操作的权限。 一般来说中大型的项目都是使用SpringSecurit…...

C++20中的jthread
一、多线程开发 c11以前,是不包含线程库的,开发多线程,必须使用OS系统自带的线程API,这就导致了很多问题,最主要的是,跨平台的开发,一般要保持几个主流应用的库的代码支持,特别是对…...

Xception模型详解
简介 Xception的名称源自于"Extreme Inception",它是在Inception架构的基础上进行了扩展和改进。Inception架构是Google团队提出的一种经典的卷积神经网络架构,用于解决深度卷积神经网络中的计算和参数增长问题。 与Inception不同࿰…...

【合合TextIn】AI构建新质生产力,合合信息Embedding模型助力专业知识应用
目录 一、合合信息acge模型获MTEB中文榜单第一 二、MTEB与C-MTEB 三、Embedding模型的意义 四、合合信息acge模型 (一)acge模型特点 (二)acge模型功能 (三)acge模型优势 五、公司介绍 一、合合信息…...

Flutter 拦截系统键盘,显示自定义键盘
一、这里记录下在开发过程中,下单的时候输入金额需要使用自定义的数字键盘 参考链接: https://juejin.cn/post/7166046328609308685 效果图 二、屏蔽系统键盘 怎样才能够在输入框获取焦点的时候,不让系统键盘弹出呢?同时又显示我们自定义的…...

内存泄漏是什么?如何避免内存泄漏?
1.2 内存泄漏 使用new开辟空间泄漏,抛出异常 int main() {int size 0;try{while (1){//int* p (int*)malloc(sizeof(int) * 1024 * 1024);/*if (p NULL){break;}*/int* p new int[1024 * 1024];size size 4 * 1024 * 1024;cout << p << endl;}}…...

linux 中的syslog的含义和用法
在Linux系统中,syslog是一种系统日志服务,用于收集、存储和管理系统和应用程序生成的日志消息。syslog服务负责记录系统的运行状态、错误信息、警告、调试信息等,以便系统管理员可以监控系统的健康状况、故障排查和性能优化。 含义和作用&am…...

kubernetes(K8S)学习(一):K8S集群搭建(1 master 2 worker)
K8S集群搭建(1 master 2 worker) 一、环境资源准备1.1、版本统一1.2、k8s环境系统要求1.3、准备三台Centos7虚拟机 二、集群搭建2.1、更新yum,并安装依赖包2.2、安装Docker2.3、设置hostname,修改hosts文件2.4、设置k8s的系统要求…...

巧克力(蓝桥杯)
文章目录 巧克力题目描述解题分析贪心 巧克力 题目描述 小蓝很喜欢吃巧克力,他每天都要吃一块巧克力。 一天小蓝到超市想买一些巧克力。超市的货架上有很多种巧克力,每种巧克力有自己的价格、数量和剩余的保质期天数,小蓝只吃没过保质期的…...

Python爬虫之pyquery和parsel的使用
三、pyquery的使用 1、准备工作 pip3 install pyquery2、初始化 2.1、字符串初始化 把HTML的内容当做参数,来初始化PyQuery对象。 html <div><ul><li class"item-0">first item</li><li class"item-1">&l…...

移动硬盘怎么加密?移动硬盘加密软件有哪些?
移动硬盘是我们在工作中最常用的移动存储设备,为了保护数据安全,需要使用专业的移动硬盘加密软件加密保护。那么,移动硬盘加密软件有哪些? BitLocker BitLocker是Windows的磁盘加锁功能,可以用于加密保护移动硬盘中…...

openEuler 22.03 安装 .NET 8.0
openEuler 22.03 安装 .NET 8.0 openEuler 22.03 安装 .NET 8.0 openEuler 22.03 安装 .NET 8.0 查看内核信息 [jeffPC-20240314EIAA ~]$ cat /proc/version Linux version 5.15.146.1-microsoft-standard-WSL2 (root65c757a075e2) (gcc (GCC) 11.2.0, GNU ld (GNU Binutils)…...

【转载】OpenCV ECC图像对齐实现与代码演示(Python / C++源码)
发现一个有很多实践代码的git 库,特记录下: 地址:GitHub - luohenyueji/OpenCV-Practical-Exercise: OpenCV practical exercise 作者博客地址:https://blog.csdn.net/LuohenYJ 已关注。 Items项目Resources1age_gender1基于深度学习识别人脸性别和年龄Model2OpenCV_dlib_…...

每日一题(相交链表 )
欢迎大家来我们主页进行指导 LaNzikinh-CSDN博客 160. 相交链表 - 力扣(LeetCode) 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 图示两个链表在节…...

C#WPF控件大全
本文列出WPF控件大全,点击可以进入详情页查看。 列表如下: AccessText用下划线来指定用作访问键的字符。 ActivatingKeyTipEventArgs为 ActivatingKeyTip 事件提供数据。...

好书推荐 《AIGC重塑金融》
作者:林建明 来源:IT 阅读排行榜 本文摘编自《AIGC 重塑金融:AI 大模型驱动的金融变革与实践》,机械工业出版社出版 这是最好的时代,也是最坏的时代。尽管大模型技术在金融领域具有巨大的应用潜力,但其应…...

【Linux】权限理解
权限理解 1. shell命令以及运行原理2. Linux权限的概念3. Linux权限管理3.1 文件访问者的分类(人)3.2 文件类型和访问权限(事物属性)3.2.1 文件类型3.2.2 基本权限 3.3 文件权限值的表示方法3.4 文件访问权限的相关设置方法3.4.1 …...

插入排序、归并排序、堆排序和快速排序的稳定性分析
插入排序、归并排序、堆排序和快速排序的稳定性分析 一、插入排序的稳定性二、归并排序的稳定性三、堆排序的稳定性四、快速排序的稳定性总结 在计算机科学中,排序是将一组数据按照特定顺序进行排列的过程。排序算法的效率和稳定性是评价其优劣的两个重要指标。稳定…...

【pytest、playwright】多账号同时操作
目录 方案实现思路: 方案一: 方案二: 方案实现思路: 依照上图所见,就知道,一个账号是pytest-playwright默认的环境,一个是 账号登录的环境 方案一: 直接上代码: imp…...

软考 系统架构设计师系列知识点之云原生架构设计理论与实践(8)
接前一篇文章:软考 系统架构设计师系列知识点之云原生架构设计理论与实践(7) 所属章节: 第14章. 云原生架构设计理论与实践 第2节 云原生架构内涵 14.2 云原生架构内涵 关于云原生的定义有众多版本,对于云原生架构的…...

【C++】stack、queue和优先级队列
一、前言 二、stack类 2.1 了解stack 2.2 使用stack (1)empty (2)size (3)top (4)push (5)pop 2.3 stack的模拟实现 三、queue类 3.1 了解queue …...

第十三届蓝桥杯国赛真题 Java C 组【原卷】
文章目录 发现宝藏试题 A: 斐波那契与 7试题 B: 小蓝做实验试题 C: 取模试题 D: 内存空间试题 E \mathrm{E} E : 斐波那契数组试题 F: 最大公约数试题 G: 交通信号试题 I: 打折试题 J: 宝石收集 发现宝藏 前些天发现了一个巨牛的人工智能学习网站,通俗易懂&#x…...

docker部署ubuntu
仓库: https://hub.docker.com/search?qUbuntu 拉一个Ubuntu镜像 docker pull ubuntu:18.04 查看本地镜像: docker images 运行容器 docker run -itd --name ubuntu-18-001 ubuntu:18.04 通过ps命令可以查看正在运行的容器信息 docker ps 进入容器 最…...

iOS问题记录 - App Store审核新政策:隐私清单 SDK签名(持续更新)
文章目录 前言开发环境问题描述问题分析1. 隐私清单 & SDK签名1.1. 隐私清单 - 数据使用声明1.2. 隐私清单 - 所用API原因描述1.3. SDK签名 2. 即将发布的第三方SDK要求 解决方案最后 前言 前段时间用Flutter开发的iOS App提交了新版本,结果刚过两分钟就收到了…...

ES学习日记(二)-------集群设置
上一节写了elasticsearch单节点安装和配置,现在说集群,简单地说就是在多台服务器上搭建单节点,在配置文件里面增加多个ip地址即可,过程同单节点部署,主要说集群配置 注意:不建议在之前单节点es上修改配置为集群,据说运行之后会生成很多文件,在单点基础上修改容易出现未知问题,…...

农村集中式生活污水分质处理及循环利用技术指南
立项单位:生态环境部土壤与农业农村生态环境监管技术中心、山东文远环保科技股份有限公司、北京易境创联环保有限公司、中国环境科学研究院、广东省环境科学研究院、中铁第五勘察设计院集团有限公司、中华环保联合会水环境治理专业委员会 本文件规定了集中式村镇生活…...

linux 一些命令
文章目录 linux 一些命令fdisk 磁盘分区parted 分区文件系统mkfs 格式化文件系统fsck 修复文件系统 mount 挂载swap 交换分区清除linux缓存df du 命令raid 命令基本原理硬raid 和 软raid案例raid 10 故障修复,重启与卸载 lvm逻辑卷技术LVM的使用方式LVM 常见名词解析…...

移动硬盘损坏打不开?别急,这里有解决方案!
在日常工作和生活中,移动硬盘几乎成为了我们必不可少的存储设备,它小巧便捷,能够容纳大量的数据。然而,当移动硬盘突然损坏打不开时,那份焦虑与无助几乎无法用言语来形容。那些重要的文件、珍贵的照片,似乎…...

微信小程序【从入门到精通】——服务器的数据交互
👨💻个人主页:开发者-曼亿点 👨💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨💻 本文由 曼亿点 原创 👨💻 收录于专栏:…...