什么是NIO(new IO)
NIO可以理解为 NON Blocking IO 非阻塞式IO,可以替代标准的java IO API,NIO与原来的IO有同样的作用和目的,但是使用方式完全不同,NIO支持面向缓冲区的,几句通道的IO操作,NIO比IO更高效
NIO和IO的区别
1、IO面向流,NIO面向缓冲区
IO面向流
一句话总结:家用自来水
使用程序操作文件 ,要想传输数据 就要简历程序和磁盘间的通道,在这里将通道理解为自来水管道
面向流举例
:家里用水 将家里用水这个行为比喻成程序,水厂比喻成磁盘 网络中的文件,将水管中的水比喻成流,将水厂的水引入到家中需要一个自来水管,这个水管的目的就是传输水 没有别的目的 就是面向流的,使用IO操作的时候 这个管道面向的就是流
单向流举例
:传统的IO流是单向的
不能说自己家的水全部都被自来水厂用了
输入输出举例
:现在将程序作为参照物 程序读取网络上的文件 就是输入流,程序将信息写入到磁盘上就是输出流,将自己家的水龙头比作程序,水厂的水来了 就是输入了,我用这些水浇花,洗菜等 都是拿来用了 就是输出流
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210304140124712.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mzg1NTg1,size_16,color_FFFFFF,t_70)
NIO面向缓冲区
一句话总结:坐高铁
NIO依旧是文件和程序之间的传输,要想传输数据 就要简历一个通道,这里就不能将通道理解成水流了 在这里将通道理解为高铁铁轨
铁路本身不负责运送人,是高铁负责运送人,这个通道说白了就是目标地点和原有地点的连接
面向缓冲区举例:
铁路虽然不传输数据,但是高铁传输数据,举个例子 我从武汉到上海 从武汉上车 车子到了高铁后 可以下客也可以上客
双向流举例:
举个例子 高铁一定是可以双向行驶的,从武汉始发 上海终点的车今天开来上海了 那么明天就会继续开回去,不可能说这趟车从武汉到上海了之后就报废了
简而言之 通道主要负责连接的,而真正数据的存取靠缓冲区
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210304140240770.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mzg1NTg1,size_16,color_FFFFFF,t_70)
缓冲区和通道
缓冲区
在java nio中负责数据的存储,缓冲区的底层就是数组,用于存储不同数据类型的数据, 为什么说 nio可以提高效率
直接缓冲区和非直接缓冲区
非直接缓冲区
通过 allocate()方法 分配缓冲区,将缓冲区建立在Jvm的内容中
什么叫做非直接缓冲区
:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210305093145672.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mzg1NTg1,size_16,color_FFFFFF,t_70)
如果是传统的Io,或者现在的非直接缓冲区的话 用的都是图片中的这种方式,物理磁盘和应用程序直接没有办法直接进行传输
如果应用程序向物理磁盘发送了一个读数据的请求调用 read()方法 会经历如下过程:
- 先将这些数据读取到内核地址空间中,
- 把内核地址空间中的数据copy到用户地址空间(jvm内存)
- 把jvm内存中的数据读取到应用程序中
如果应用程序向物理磁盘发送了一个写数据的请求调用 write()方法 会经历如下过程:
- 先将这些数据写到 用户地址空间 ( jvm内存 ) 中
- 把jvm内存中的数据拷贝到内核地址空间中
- 最后把内存地址空间中的数据写到物理磁盘中
缺点:
- 用户地址空间到内核地址空间的数据是一样的,没有必要拷贝,如果可以想一个办法 直接操作一块内存空间 应用程序操作者块空间直接从磁盘中读 或写到磁盘中 速度会快很多
直接缓冲区解决了这个缺点
直接缓冲区
通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中
使用直接缓冲区,应用程序直接在操作系统的物理内存中开辟一块缓冲区,此时如果想要读写数据的话,直接使用开辟的这一块物理内存即可,非直接缓冲区中拷贝的步骤就省略了
缺点:
- 如果直接在物理内存中开辟缓冲区 消耗的资源会非常大
- 应用程序把想要操作的数据写到物理内存中以后,这些数据就不归程序管了(管不了),物理内存中的数据什么时候写到磁盘中由操作系统决定
- 这种情况 分配和销毁耗费的资源很大,得等垃圾回收机制释放引用 应用程序和物理内存映射文件才会断开连接,虽然可以调用System.gc() 加快垃圾回收运行,但是什么时候执行还是由程序决定的 ( 不易控制 )
如果有一些数据可以长时间在内存中进行操作的话 可以使用这种方式
举个生活中的例子
:直接缓冲区和非直接缓冲区类似于寄一个跨国快递
效率:
非直接缓冲区就相当于是在国外 首先使用国外的快递公司 例如FedEx,然后到了海关之后又将快递转交给国内的快递公司顺丰寄送
直接缓冲区就相当于是在不管是国内还是国外都使用中国的快递公司,直接将快递交给指定人手上,相对来说直接使用顺丰的效率会提高
消耗资源:
直接缓冲区 如果直接使用顺丰国际进行转运的话 首先顺丰的飞机 他就那么大 如果你寄的东西多的话 首先快递费很贵,其次占的位置多,飞机装其他东西就装得少了 ( 消耗的资源大 )
是否可控制 ( 假设快递上了飞机就不能撤回了 ) :
将快递交给FedEx 只要还没有达到海关,我还可以将我的快递撤回 不寄了
将快递交给顺丰了之后,飞机起飞后 自己就不能控制快递是否在被送回来了,全权交给顺丰负责把快递寄到指定的人手中
缓冲区的创建
根据数据类型不同 ( boolean类型除外 ) ,提供了相应类型的缓冲区:ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer上述缓冲区的管理方式几乎一致,通过 allocate() 获取缓冲区
举个例子:创建一个Byte类型的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
缓冲区的核心方法、属性:
put():
存入数据到缓冲区
get():
获取缓冲区中的数据
缓冲区中 ( Buffer类 ) 的4个核心属性
capacity:
容量 表示缓冲区中最大存储数据的容量。一旦声明不能改变
limit:
界限,表示缓冲区中可以操作数据的大小。(limit 后的数据不能进行读写)
position:
缓冲区中正在操作数据的位置 position <= limit <= capacity
举个例子
public static void main(String[] args) {
//1.初始阶段
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//2. 往缓冲区中存入数据
String str = "abcde";
buffer.put(str.getBytes());
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//3.切换成读取数据的模式
buffer.flip();
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//4.执行读取数据
byte[] dst = new byte[buffer.limit()];
buffer.get(dst);
System.out.println(new String(dst, 0, dst.length));
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//5.rewind()
buffer.rewind();
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//6.清空缓冲区
buffer.clear();
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
System.out.println((char)buffer.get());
}
图1:假设现在生产了一架高铁 共有10个位置(座位号从0开始 ( 数组 ) )
那么这个capacity:高铁总共的座位,
pasition:待坐的作为号,
limit:检票员的位置,写数据模式为 = capacity, 读数据模式为待坐的位置
图2:执行了 put方法后,现在有5个人坐进来了,那么第6个位置是待坐的 编号为5 position = 5 ,高铁的总座位数不变capacity = 10,检票员还没有开始检票 limit = 10
图3: 切换成读数据模式,检票员站在待坐的那个位子上,数一共有多少个乘客,从座位号为0的那个位置开始数position被limit操作从0开始准备读取 注意了:position表示的是 准备数的作为编号
图4:读数据模式 检票员开始读取数据,读到了现在所在的位置 ( position = 5 ,limit = 5 ) capacity = 10 不变
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210304161705973.png)
图5:调用 buffer.rewind() 方法 相当于是这个检票员为了确认 又从0开始准备读一遍
图6: 调用buffer.clear() 方法 回到了最初状态,但是里面的数据并没有被清空,只不过这些数据处于被遗忘状态,不知道里面数据的limit等信息,不能正确的读取数据
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210304163129971.png)
mark属性:标记,表示记录当前ppsition的位置,可以通过reset() 恢复到mark的位置
mark( ) 方法,reset( ) 方法
举个例子:
public static void main(String[] args) {
//1.初始阶段
ByteBuffer buffer = ByteBuffer.allocate(10);
//2. 往缓冲区中存入数据
String str = "abcde";
buffer.put(str.getBytes());
//3.切换成读取数据的模式
buffer.flip();
byte[] dst = new byte[buffer.limit()];
buffer.get(dst, 0, 2);
System.out.println(new String(dst, 0, 2));
System.out.println(buffer.position());
//4.执行标记
buffer.mark();
//5.读取座位号为2的信息 向后读2个
buffer.get(dst, 2, 2);
System.out.println(new String(dst, 2, 2));
System.out.println(buffer.position());
//6.调用reset() 方法
buffer.reset();
System.out.println(buffer.position());
//7.判断缓冲字节中是否还存在没有数的人
if (buffer.hasRemaining()) {
//计算缓冲字节中还有多少没有数的人
System.out.println(buffer.remaining());
}
}
图4:还是拿高铁举例 我刚读2个人的时候 突然有人叫住我了 问一些问题,我为了防止忘记回答完问题以后不知道从哪里开始读,就在本子上记录了一下读到哪里了 position = 2 (下一个人是从2号作为开始)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210304165401903.png)
图5:回答完问题后 发现没有忘记刚才从哪里开始读 我就继续开始读 从2开始 向后读了2个,现在position = 4 ( position 表示 准备数的作为编号 )
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210304170125199.png)
图6: 调用reset() 方法, 恢复到mark的位置,本子上面已经记录了2这个位置了 在读或者不读都没有关系了
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210304165401903.png)
规律:0 <= mark <= position <= limit <= capacity
首先 mark 肯定是mark数组上的信息,肯定是不会小于0 的
mark <= position 检票员数人:刚才的例子 可以知道 position是准备数的那个,那么mark最多只能标记到这个人
position <= limit limt是检票员现在站的位置,是那个待坐的位置,最极限的情况就是2个相等,不可能position > limit
capacity 这个就不用说了
buffer.hasRemaining()
:是否还有没有数的数据
buffer.remaining()
:如果有的话还有几个没有数
调用reset方法后,position的位置就在2了,现在开始后面还有3个没有数的人,分别是 当前位置,和后面2个位置
通道
使用应用程序操作计算机实际上是调用了操作系统IO接口中的read( ),write( ) 方法来进行读写操作,
最早期应用程序读写操作示意图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210306142312622.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mzg1NTg1,size_16,color_FFFFFF,t_70)
计算机操作系统中的IO接口全部都是由电脑的CPU负责的,当发生了大量的读写请求是,CPU的占用率非常高,以至于不能执行其他操作,CPU利用率很低
改进:
![在这里插入图片描述](https://img-blog.csdnimg.cn/2021031110495933.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mzg1NTg1,size_16,color_FFFFFF,t_70)
IO流使用的是这种形式
在内存和IO接口之间设置了一个DMA- 直接存储器存储,应用程序调用 read() 或write() 方法时 DMA会先想CPU申请权限,DMA获取权限以后 read(), write() 全部交由DMA处理
优点:在执行读写请求时 CPU不需要干预 可以去处理其他操作 提高了CPU的利用率
缺点:当发生大量的读写请求时 DMA会频繁的向CPU请求权限,就会建立很多个DMA总线,当DMA总线过多时 会发生总线冲突,如果总线冲突也会影响性能
改进:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210311105032155.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mzg1NTg1,size_16,color_FFFFFF,t_70)
将DMA的方式改为了通道的方式,当然DMA也是存在的
通道是一个完全独立的处理器,专门用于IO操作,当有大量的io请求发出时,不需要向CPU获取权限,因为通道本身就是一个完全独立的处理器,只要有IO操作 直接通过通道来解决,通道拥有一套自己的命令 、传输方式
用于源节点与目标节点的连接。在java NIO中负责缓冲区中数据的传输。通道本身不存储数据,因此需要配合缓冲区进行传输
通道的主要实现类
java.nio.Channel接口
FileChannel 本地
SocketChannel TCP
ServerSocketChannel TCP
DatagramChannel UDP
获取通道的3中方式
-
java针对支持通道的类提供了getChannel ( ) 方法
本地IO
FileInputStream/FileOutputStream、RandomAccessFile
网络IO
Socket
ServerSocket
DatagramSocket
-
在JDK1.7中的NIO.2 针对各个通道提供了静态方法 open( )
-
在JDK1.7种的NIO.2的Files 工具类的newByteChannel( )
通过上面的例子 可以知道 通道表示打开到IO设备 ( 例如:文件,套接字 ) 的连接,若需要使用nio系统,需要获取用于连接IO设备的通道 以及用于容纳数据的缓冲区,然后操作缓冲区,对数据进行处理,简而言之 Channel 负责传输数据,Buffer 负责存储数据
结合通道、缓冲区实现文件的copy
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210306161522637.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mzg1NTg1,size_16,color_FFFFFF,t_70)
方式1.使用非直接缓冲区
//使用非直接缓冲区copy文件
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel fisChannel = null;
FileChannel fosChannel = null;
try {
fis = new FileInputStream("1.png");
fos = new FileOutputStream("2.png");
//1.创建通道
fisChannel = fis.getChannel();
fosChannel = fos.getChannel();
//2.创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//3.将通道中的数据读到到缓冲区
while (fisChannel.read(buffer) != -1) {
//4.将缓冲区中的数据写入通道
//先切换成读数据模式
buffer.flip();
//将缓冲区中的数据写入通道
fosChannel.write(buffer);
//清空缓冲区
buffer.clear();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭连接
if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } }
if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } }
if (fisChannel != null) { try { fisChannel.close(); } catch (IOException e) { e.printStackTrace(); } }
if (fosChannel != null) { try { fosChannel.close(); } catch (IOException e) { e.printStackTrace(); } }
}
}
方式2:使用直接缓冲区
//使用直接缓冲区拷贝文件, 在JDK1.7中的NIO.2 针对各个通道提供了静态方法 open( )
public static void main(String[] args) throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("1.png"), StandardOpenOption.READ);
//StandardOpenOption.CREATE: 如果文件存在就覆盖, 不存在就创建
//StandardOpenOption.CREATE_NEW: 存在就报错,不存在就创建
FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE,
StandardOpenOption.READ, StandardOpenOption.CREATE);
//这里得到的就是内存映射文件
MappedByteBuffer inMapBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
//内存映射文件要求通道拥有 read,write 2中模式,如果outChannel 只有READ 或 WRITE 会出现异常
MappedByteBuffer outMapBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
byte[] dst = new byte[inMapBuffer.limit()];
inMapBuffer.get(dst);
outMapBuffer.put(dst);
inChannel.close();
outChannel.close();
}
方式3:直接使用通道(本质上还是使用的直接缓冲区)
直接使用通道进行传输( 本质还是使用的直接缓冲区 )
public static void main(String[] args) throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("1.png"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE,
StandardOpenOption.READ, StandardOpenOption.CREATE);
//也是使用直接缓冲区的方式
inChannel.transferTo(0, inChannel.size(), outChannel);
outChannel.transferFrom(inChannel, 0, outChannel.size());
inChannel.close();
outChannel.close();
}
分散读取(Scatter)与聚集写入(Gather)
分散读取( Scattering Reads ):将通道中的数据分散到多个缓冲区中, 上一个缓冲区读慢了之后 然后是下一个缓冲区
![在这里插入图片描述](https://img-blog.csdnimg.cn/2021030708470285.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mzg1NTg1,size_16,color_FFFFFF,t_70)
聚集写入( Gathering Writes ): 将多个缓冲区中的数据聚集到通道中
![在这里插入图片描述](https://img-blog.csdnimg.cn/2021030708484826.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mzg1NTg1,size_16,color_FFFFFF,t_70)
分散读取聚集写入实例
public static void main(String[] args) throws IOException {
//分散读取, 1.txt 中为 使用英文是为了防止乱码的拼音
RandomAccessFile readAccessFile = new RandomAccessFile("1.txt", "rw");
//获取通道
FileChannel channel1 = readAccessFile.getChannel();
//分配指定大小的缓冲区
ByteBuffer buffer1 = ByteBuffer.allocate(3);
// 这里的容量要设置足够装满txt中的所有内容 要不然会造成乱码
ByteBuffer buffer2 = ByteBuffer.allocate(1024);
ByteBuffer[] buffers = {buffer1, buffer2};
channel1.read(buffers);
// 切换成读模式
for (ByteBuffer buffer: buffers) {
buffer.flip();
}
System.out.println(new String(buffers[0].array(), 0, buffers[0].limit()));
System.out.println("--------------------------------------------");
System.out.println(new String(buffers[1].array(), 0, buffers[1].limit()));
//聚集写入
RandomAccessFile writeAccessFile = new RandomAccessFile("2.txt", "rw");
FileChannel channel2 = writeAccessFile.getChannel();
channel2.write(buffer2);
channel1.close();
channel2.close();
}
字符集
public static void main(String[] args) throws CharacterCodingException {
Charset charset = Charset.forName("GBK");
//获取编码器
CharsetEncoder charsetEncoder = charset.newEncoder();
//获取解码器
CharsetDecoder charsetDecoder = charset.newDecoder();
CharBuffer charBuffer = CharBuffer.allocate(1024);
charBuffer.put("山本我日你仙人");
//切换成读模式
charBuffer.flip();
//编码,将字符转转换成字节数组
ByteBuffer byteBuffer = charsetEncoder.encode(charBuffer);
for (int i = 0; i < 14; i++) {
System.out.println(byteBuffer.get());
}
//解码
byteBuffer.flip();
CharBuffer decode = charsetDecoder.decode(byteBuffer);
System.out.println(decode.toString());
}
阻塞与非阻塞
阻塞和非阻塞是相较于网络通信而言的
阻塞:当客户端发送一个请求到服务端以后, 如果服务端不能确定客户端发送过来的请求真实有效时,该线程将一直处于阻塞状态
举个生活中的例子:
假设我家运水和运油都是用的一个管道,先运送的是水,运送了一部分水之后突然停水了,那么此时这条管道就处于阻塞状态,而且油此时也只能等待水运送完了之后再运过来,不然水和油就混在一起了,此时我就只能等水全部流过来了以后然后在接油,水没有流完 我 ( cpu ) 就只能等着
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210307091605719.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mzg1NTg1,size_16,color_FFFFFF,t_70)
解决方案:
1、多线程
举个生活中的例子
:还是上面那个例子 我现在将管道设置成了多个, 有的管道是正常执行的 但是有的管道还是会处于阻塞状态,虽然我一直在干活 但是水管还是会处于阻塞状态,还是会等待水运完了之后然后开始运送油
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210307091420436.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mzg1NTg1,size_16,color_FFFFFF,t_70)
2、nio 选择器
客户端发送到服务端的请求首先会通过一个选择器,使用这个选择器用来判断发送过来的请求是否已经准备就绪完成,如果准备就绪完成 然后创建一个或多个线程到服务端
举个生活中的例子
:现在控制水龙头的不是我 而是一个类似门铃的东西,如果门铃响那么水一定会全部流过来 而不是只流一部分,门铃响了以后 我把水龙头打开 然后水流通过管道流入我家,我也可以开启多个水龙头 同时接很多水,如果门铃不响 那么我做什么事情都可以
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210307092226567.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mzg1NTg1,size_16,color_FFFFFF,t_70)
阻塞式实例
使用客户端发送一张图片给服务端,服务端接收到客户端发送的图片 然后反馈给客户端接收成功的消息
public class BlockNonBlockReceiveMsgTest {
@Test
public void client() throws IOException {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
FileChannel fileChannel = FileChannel.open(Paths.get("1.png"), StandardOpenOption.READ);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//将本地文件读取到服务器
while (fileChannel.read(byteBuffer) != -1) {
byteBuffer.flip();
socketChannel.write(byteBuffer);
byteBuffer.clear();
}
//告诉服务端, 客户端发送消息完毕,可以接收了
socketChannel.shutdownOutput();
//接收服务端的反馈
int len = 0;
while ((len = socketChannel.read(byteBuffer)) != -1) {
byteBuffer.flip();
System.out.println(new String(byteBuffer.array(), 0, len));
byteBuffer.clear();
}
fileChannel.close();
socketChannel.close();
}
@Test
public void server() throws IOException {
// 获取通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//绑定端口号
serverSocketChannel.bind(new InetSocketAddress(9898));
//获取客户端连接的通道
SocketChannel socketChannel = serverSocketChannel.accept();
//创建指定大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//接收客户端的数据 并保存到本地, 想要保存到本地 要有一个FileChannel
FileChannel fileChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
// 注意了 这里的while 一定不能写成 if 不会就会出现pipeLine Broke异常
while (socketChannel.read(buffer) != -1) {
buffer.flip();
fileChannel.write(buffer);
buffer.clear();
}
//发送反馈给客户端
buffer.put("接收客户端消息成功".getBytes());
buffer.flip();
socketChannel.write(buffer);
socketChannel.close();
fileChannel.close();
serverSocketChannel.close();
}
}
Socket非阻塞式案例 tcp
public class NonBlockSocketTest {
@Test
public void client() throws IOException {
//获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
//切换成非阻塞模式
socketChannel.configureBlocking(false);
//分配指定大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//发送数据给服务端
byteBuffer.put("山本我日你仙人".getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
byteBuffer.clear();
//关闭通道
socketChannel.close();
}
@Test
public void server() throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
//绑定连接
serverSocketChannel.bind(new InetSocketAddress(9898));
//获取选择器
Selector selector = Selector.open();
//把选择器注册到通道中 ops: 表示这个通道监听选择器的什么状态, 现在SelectionKey.OP_ACCEPT 表示接受事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//通过选择器轮询获取选择器中已经准备就绪的事件 selector.select() > 0 表示至少有1个准备就绪了
while (selector.select() > 0) {
//得到所有注册到选择器中的事件
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
// 判断是哪种时间准备就绪
//isAcceptable() 方法 表示接受时间就绪
if (selectionKey.isAcceptable()) {
//获取客户端连接
SocketChannel socketChannel = serverSocketChannel.accept();
//切换成非阻塞模式
socketChannel.configureBlocking(false);
//将该通道注册到选择器上
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
//获取当前选择器中读就绪状态的通道
SocketChannel channel = (SocketChannel) selectionKey.channel();
//读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
while ((len = channel.read(buffer)) != -1) {
buffer.flip();
System.out.println(new String(buffer.array(), 0, len));
buffer.clear();
}
}
// 取消选择键
iterator.remove();
}
}
}
}
Datagram非阻塞式案例 udp
public class NonBlockDatagramTest {
@Test
public void send() throws IOException {
//创建通道
DatagramChannel datagramChannel = DatagramChannel.open();
//设置为非阻塞模式
datagramChannel.configureBlocking(false);
//创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put((new Date().toString() + ", 山本我日你仙人").getBytes());
buffer.flip();
datagramChannel.send(buffer, new InetSocketAddress("127.0.0.1", 8989));
buffer.clear();
//关闭通道
datagramChannel.close();
}
@Test
public void receive() throws IOException {
DatagramChannel datagramChannel = DatagramChannel.open();
datagramChannel.configureBlocking(false);
//绑定端口号
datagramChannel.bind(new InetSocketAddress(8989));
Selector selector = Selector.open();
datagramChannel.register(selector, SelectionKey.OP_READ);
while (selector.select() > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey next = iterator.next();
if (next.isReadable()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
datagramChannel.receive(buffer);
buffer.flip();
System.out.println(new String(buffer.array(), 0, buffer.limit()));
buffer.clear();
}
}
iterator.remove();
}
datagramChannel.close();
selector.close();
}
}
管道
管道是2个线程之间的单项数据连接,Pipe有一个source通道和一个sink通道,数据会被写到sink通道,从source通道读取
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210311100835866.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mzg1NTg1,size_16,color_FFFFFF,t_70)
实现方式:
@Test
public void test1() throws IOException {
//1.获取管道
Pipe pipe = Pipe.open();
ByteBuffer buffer = ByteBuffer.allocate(1024);
Pipe.SinkChannel sinkChannel= pipe.sink();
buffer.put("山本我日你仙人".getBytes());
buffer.flip();
sinkChannel.write(buffer);
// 读取缓冲区中的数据
Pipe.SourceChannel sourceChannel = pipe.source();
buffer.flip();
int len = sourceChannel.read(buffer);
System.out.println(new String(buffer.array(), 0, len));
sinkChannel.close();
sourceChannel.close();
}