java----BIO/NIO/AIO
1F
说一说I/O
首先来说一下什么是I/O?
在计算机系统中I/O就是输入(Input)和输出(Output)的意思,针对不同的操作对象,可以划分为磁盘I/O模型,网络I/O模型,内存映射I/O, Direct I/O、数据库I/O等,只要具有输入输出类型的交互系统都可以认为是I/O系统,也可以说I/O是整个操作系统数据交换与人机交互的通道,这个概念与选用的开发语言没有关系,是一个通用的概念。
在如今的系统中I/O却拥有很重要的位置,现在系统都有可能处理大量文件,大量数据库操作,而这些操作都依赖于系统的I/O性能,也就造成了现在系统的瓶颈往往都是由于I/O性能造成的。因此,为了解决磁盘I/O性能慢的问题,系统架构中添加了缓存来提高响应速度;或者有些高端服务器从硬件级入手,使用了固态硬盘(SSD)来替换传统机械硬盘;在大数据方面,Spark越来越多的承担了实时性计算任务,而传统的Hadoop体系则大多应用在了离线计算与大量数据存储的场景,这也是由于磁盘I/O性能远不如内存I/O性能而造成的格局(Spark更多的使用了内存,而MapReduece更多的使用了磁盘)。因此,一个系统的优化空间,往往都在低效率的I/O环节上,很少看到一个系统CPU、内存的性能是其整个系统的瓶颈。也正因为如此,Java在I/O上也一直在做持续的优化,从JDK 1.4开始便引入了NIO模型,大大的提高了以往BIO模型下的操作效率。
这里先给出BIO、NIO、AIO的基本定义与类比描述:
BIO (Blocking I/O):同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。这里使用那个经典的烧开水例子,这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。
NIO (New I/O):同时支持阻塞与非阻塞模式,但这里我们以其同步非阻塞I/O模式来说明,那么什么叫做同步非阻塞?如果还拿烧开水来说,NIO的做法是叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作。
AIO ( Asynchronous I/O):异步非阻塞I/O模型。异步非阻塞与同步非阻塞的区别在哪里?异步非阻塞无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开水中就是,为每个水壶上面装了一个开关,水烧开之后,水壶会自动通知我水烧开了。
进程中的IO调用步骤大致可以分为以下四步:
进程向操作系统请求数据 ;
操作系统把外部数据加载到内核的缓冲区中;
操作系统把内核的缓冲区拷贝到进程的缓冲区 ;
进程获得数据完成自己的功能 ;
当操作系统在把外部数据放到进程缓冲区的这段时间(即上述的第二,三步),如果应用进程是挂起等待的,那么就是同步IO,反之,就是异步IO,也就是AIO 。
BIO(Blocking I/O)同步阻塞I/O
这是最基本与简单的I/O操作方式,其根本特性是做完一件事再去做另一件事,一件事一定要等前一件事做完,这很符合程序员传统的顺序来开发思想,因此BIO模型程序开发起来较为简单,易于把握。
但是BIO如果需要同时做很多事情(例如同时读很多文件,处理很多tcp请求等),就需要系统创建很多线程来完成对应的工作,因为BIO模型下一个线程同时只能做一个工作,如果线程在执行过程中依赖于需要等待的资源,那么该线程会长期处于阻塞状态,我们知道在整个操作系统中,线程是系统执行的基本单位,在BIO模型下的线程 阻塞就会导致系统线程的切换,从而对整个系统性能造成一定的影响。当然如果我们只需要创建少量可控的线程,那么采用BIO模型也是很好的选择,但如果在需要考虑高并发的web或者tcp服务器中采用BIO模型就无法应对了,如果系统开辟成千上万的线程,那么CPU的执行时机都会浪费在线程的切换中,使得线程的执行效率大大降低。此外,关于线程这里说一句题外话,在系统开发中线程的生命周期一定要准确控制,在需要一定规模并发的情形下,尽量使用线程池来确保线程创建数目在一个合理的范围之内,切莫编写线程数量创建上限的代码。
NIO (New I/O) 同步非阻塞I/O
关于NIO,国内有很多技术博客将英文翻译成No-Blocking I/O,非阻塞I/O模型 ,当然这样就与BIO形成了鲜明的特性对比。NIO本身是基于事件驱动的思想来实现的,其目的就是解决BIO的大并发问题,在BIO模型中,如果需要并发处理多个I/O请求,那就需要多线程来支持,NIO使用了多路复用器机制,以socket使用来说,多路复用器通过不断轮询各个连接的状态,只有在socket有流可读或者可写时,应用程序才需要去处理它,在线程的使用上,就不需要一个连接就必须使用一个处理线程了,而是只是有效请求时(确实需要进行I/O处理时),才会使用一个线程去处理,这样就避免了BIO模型下大量线程处于阻塞等待状态的情景。
相对于BIO的流,NIO抽象出了新的通道(Channel)作为输入输出的通道,并且提供了缓存(Buffer)的支持,在进行读操作时,需要使用Buffer分配空间,然后将数据从Channel中读入Buffer中,对于Channel的写操作,也需要现将数据写入Buffer,然后将Buffer写入Channel中。
如下是NIO方式进行文件拷贝操作的示例,见下图:

通过比较New IO的使用方式我们可以发现,新的IO操作不再面向 Stream来进行操作了,改为了通道Channel,并且使用了更加灵活的缓存区类Buffer,Buffer只是缓存区定义接口, 根据需要,我们可以选择对应类型的缓存区实现类。在java NIO编程中,我们需要理解以下3个对象Channel、Buffer和Selector。
- Channel
首先说一下Channel,国内大多翻译成“通道”。Channel和IO中的Stream(流)是差不多一个等级的。只不过Stream是单向的,譬如:InputStream, OutputStream。而Channel是双向的,既可以用来进行读操作,又可以用来进行写操作,NIO中的Channel的主要实现有:FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel;通过看名字就可以猜出个所以然来:分别可以对应文件IO、UDP和TCP(Server和Client)。
- Buffer
NIO中的关键Buffer实现有:ByteBuffer、CharBuffer、DoubleBuffer、 FloatBuffer、IntBuffer、 LongBuffer,、ShortBuffer,分别对应基本数据类型: byte、char、double、 float、int、 long、 short。当然NIO中还有MappedByteBuffer, HeapByteBuffer, DirectByteBuffer等这里先不具体陈述其用法细节。
说一下 DirectByteBuffer 与 HeapByteBuffer 的区别?
它们 ByteBuffer 分配内存的两种方式。HeapByteBuffer 顾名思义其内存空间在 JVM 的 heap(堆)上分配,可以看做是 jdk 对于 byte[] 数组的封装;而 DirectByteBuffer 则直接利用了系统接口进行内存申请,其内存分配在c heap 中,这样就减少了内存之间的拷贝操作,如此一来,在使用 DirectByteBuffer 时,系统就可以直接从内存将数据写入到 Channel 中,而无需进行 Java 堆的内存申请,复制等操作,提高了性能。既然如此,为什么不直接使用 DirectByteBuffer,还要来个 HeapByteBuffer?原因在于, DirectByteBuffer 是通过full gc来回收内存的,DirectByteBuffer会自己检测情况而调用 system.gc(),但是如果参数中使用了 DisableExplicitGC 那么就无法回收该快内存了,-XX:+DisableExplicitGC标志自动将 System.gc() 调用转换成一个空操作,就是应用中调用 System.gc() 会变成一个空操作,那么如果设置了就需要我们手动来回收内存了,所以DirectByteBuffer使用起来相对于完全托管于 java 内存管理的Heap ByteBuffer 来说更复杂一些,如果用不好可能会引起OOM。Direct ByteBuffer 的内存大小受 -XX:MaxDirectMemorySize JVM 参数控制(默认大小64M),在 DirectByteBuffer 申请内存空间达到该设置大小后,会触发 Full GC。
- Selector
Selector 是NIO相对于BIO实现多路复用的基础,Selector 运行单线程处理多个 Channel,如果你的应用打开了多个通道,但每个连接的流量都很低,使用 Selector 就会很方便。例如在一个聊天服务器中。要使用 Selector , 得向 Selector 注册 Channel,然后调用它的 select() 方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新的连接进来、数据接收等。
这里我们再来看一个NIO模型下的TCP服务器的实现,我们可以看到Selector 正是NIO模型下 TCP Server 实现IO复用的关键,请仔细理解下段代码while循环中的逻辑,见下图:

4F
AIO (Asynchronous I/O) 异步非阻塞I/O
Java AIO就是Java作为对异步IO提供支持的NIO.2 ,Java NIO2 (JSR 203)定义了更多的 New I/O APIs, 提案2003提出,直到2011年才发布, 最终在JDK 7中才实现。JSR 203除了提供更多的文件系统操作API(包括可插拔的自定义的文件系统), 还提供了对socket和文件的异步 I/O操作。 同时实现了JSR-51提案中的socket channel全部功能,包括对绑定, option配置的支持以及多播multicast的实现。
从编程模式上来看AIO相对于NIO的区别在于,NIO需要使用者线程不停的轮询IO对象,来确定是否有数据准备好可以读了,而AIO则是在数据准备好之后,才会通知数据使用者,这样使用者就不需要不停地轮询了。当然AIO的异步特性并不是Java实现的伪异步,而是使用了系统底层API的支持,在Unix系统下,采用了epoll IO模型,而windows便是使用了IOCP模型。关于Java AIO,本篇只做一个抛砖引玉的介绍,如果你在实际工作中用到了,那么可以参考Netty在高并发下使用AIO的相关技术。
IO 面向流,堵塞
管道可以理解为水管,可以直接运输水流(字节数据)

NIO 面向缓冲区,非堵塞
管道可以理解铁路,需要依赖火车(缓冲区)才能运输数据。

Java NIO系统的核心在于:通道/管道(Channel)和缓冲区(Buffer)。通道表示打开到I0设备(例如:文件、套接字)的连接。若需要使用NIO系统,需要获取用于连接I0设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。简而言之,Channel负责传输,Buffer负责存储
缓冲区
概念
缓冲区就是数组,用户存储不同数据类型的数据,根据数据类型不同(boolean除外),提供了相应类型的缓冲区
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
上述缓冲区的管理方式几乎一致,通过allocate()获取缓冲区,缓冲区存储数据的连个核心方法 put()、get()
缓冲区中的四个核心属性:
capacity:容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变。 limit: 界限,表示缓冲区中可以操作数据的大小。(limit后数据不能进行读写) position:位置,表示缓冲区中正在操作数据的位置。 mark:标记,表示记录当前position的位置。可以通过reset()恢复到mark的位置 0<=mark<=position<=limit<=capacity

基本操作
public class Main {
public static void main(String[] args) {
//test1();
test2();
}
public static void test1(){
ByteBuffer byteBuffer= ByteBuffer.allocate(1024);
System.out.println("-----allocate-----");
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
byteBuffer.put("abcde".getBytes());
System.out.println("-----put()-----");
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
//切换读取模式
byteBuffer.flip();
System.out.println("-----flip()-----");
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
System.out.println("-----get()-----");
byte[] bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes);//如果bytes的空间大于byteBuffer.limit(),会报错
System.out.println(new String(bytes,0,byteBuffer.limit()));
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
//可重复读取数据
byteBuffer.rewind();
System.out.println("-----rewind()-----");
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
//清空缓冲区(缓冲区的数据并没有真正意义上的清空,但处于被遗忘的状态)
byteBuffer.clear();
System.out.println("-----clear()-----");
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
}
public static void test2(){
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put("abcde".getBytes());
byteBuffer.flip();
byte[] bytes = new byte[5];
byteBuffer.get(bytes, 0, 2);
byteBuffer.mark();
System.out.println(new String(bytes,0,2));
System.out.println(byteBuffer.position());
byteBuffer.get(bytes, 0, 2);
//position又回到了mark标记的配置
byteBuffer.reset();
byteBuffer.get(bytes, 0, 2);
System.out.println(new String(bytes,0,2));
System.out.println(byteBuffer.position());
}
}
直接缓冲区和非直接缓冲区
非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中
直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率


内核空间与用户空间
内核空间主要指操作系统用于程序调度、虚拟内存的使用或者连接硬件资源等的程序逻辑。为了保证操作系统的稳定向,运行在操作系统中的用户进程不能访问操作系统所使用的内存空间。如果用户程需要访问硬件资源,如网络连接等,可以调用操作系统提供的接口来实现,这个接口的调用其实也是系统调用。每次系统调用都会存在两个内存空间的切换,通常的网络传输也是一次系统调用,通过网络传输的数据先是从内核空间从远程主机接受数据,然后再从内核空间复制到用户空间,供程序使用。这种复制手段很费时,虽然包住了程序运行时的安全性与稳定性,但是也牺牲了部分效率。现在linux系统上提供了sendfile文件传输方式来减少这种复制方式的成本。
内核空间和用户空间大小分配也是个需要权衡的问题,如果是一台登录服务器要分配更多的内核空间,因为没有个登录用户操作系统都会初始化一个用户进程,这个进程大部分在内核空间运行。当前windows内核:用户为1:1(也就是大约2G内核空间,2G用户空间),linux为1:3。

补充参考:https://blog.csdn.net/u012129558/article/details/82878994
通道

DMA技术的重要性在于,利用它进行数据传送时不需要CPU的参与。每台电脑主机板上都有DMA控制器,通常计算机对其编程,并用一个适配器上的ROM(如软盘驱动控制器上的ROM)来储存程序,这些程序控制DMA传送数据。一旦控制器初始化完成,数据开始传送,DMA就可以脱离CPU,独立完成数据传送。
参考:https://baike.baidu.com/item/DMA%E9%80%9A%E9%81%93/7492727?fr=aladdin
通道(Channe1):用于源节点与目标节点的连接。在Java NIO中负责缓冲区中数据的传输。Channe1本身不存储数据,因此需要配合缓冲区进行传输。
通道的主要实现类

注意FileChannel不能切换非堵塞模式,通过上面的图可以看出SelectableChannel(监听器),下面没有FileChannel
获取通道
1.Java针对支持通道的类提供了getChanne1()方法
本地IO:
FileInputStream/FileOutputStream
RandomAccessFile
网络IO:
Socket
ServerSocket
DatagramSocket
2.在JDK1.7中的NIO.2针对各个通道提供了静态方法open()
3.在JDK 1.7中的NIO.2的Files 工具类的newByteChannel()
1、利用通道完成文件复制(使用非直接缓存区),速度比面向流块
public static void main(String[] args) {
String from_file = "C:\\Users\\zhengyan\\Desktop\\test1\\x.txt";
String to_file = "C:\\Users\\zhengyan\\Desktop\\test1\\t.txt";
copyFile(from_file,to_file);
}
private static void copyFile(String from_file, String to_file) {
try {
//创建输入文件通道
FileChannel fcIn = new FileInputStream(from_file).getChannel();
//创建输出文件通道
FileChannel fcOut = new FileOutputStream(to_file).getChannel();
//创建缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
while(fcIn.read(buf)!=-1){
buf.flip();
fcOut.write(buf);
buf.clear();
}
fcIn.close();
fcOut.close();
System.out.println("copy successful");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
2、利用通道完成文件复制(使用直接缓存区,内存映射),速度比非直接缓冲区块
public static void main(String[] args) throws IOException {
String from_file = "C:\\Users\\zhengyan\\Desktop\\test1\\x.txt";
String to_file = "C:\\Users\\zhengyan\\Desktop\\test1\\t.txt";
copyFile(from_file,to_file);
}
private static void copyFile(String from_file, String to_file) throws IOException {
FileChannel inchannel = FileChannel.open(Paths.get(from_file), StandardOpenOption.READ);
FileChannel outchannel = FileChannel.open(Paths.get(to_file), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
//内存映射文件
MappedByteBuffer inByteBuffer = inchannel.map(FileChannel.MapMode.READ_ONLY, 0, inchannel.size());
MappedByteBuffer outByteBuffer = outchannel.map(FileChannel.MapMode.READ_WRITE, 0, inchannel.size());
//直接对缓冲区进行数据读写操作
byte bytes[] = new byte[1024];
for(int i=0;i<inchannel.size();i++){
outByteBuffer.put(inByteBuffer.get());
}
inchannel.close();
outchannel.close();
}
2、利用通道完成文件复制(使用直接缓存区,内存映射),速度比非直接缓冲区快(transferTo、transferFrom)
public static void main(String[] args) throws IOException {
String from_file = "C:\\Users\\zhengyan\\Desktop\\test1\\x.txt";
String to_file = "C:\\Users\\zhengyan\\Desktop\\test1\\t.txt";
copyFile(from_file,to_file);
}
private static void copyFile(String from_file, String to_file) throws IOException {
FileChannel inchannel = FileChannel.open(Paths.get(from_file), StandardOpenOption.READ);
FileChannel outchannel = FileChannel.open(Paths.get(to_file), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
inchannel.transferTo(0,inchannel.size(),outchannel);
inchannel.close();
outchannel.close();
}
分散和聚集
public static void main(String[] args) throws IOException, InterruptedException {
String from_file = "C:\\Users\\zhengyan\\Desktop\\test1\\x.txt";
String to_file = "C:\\Users\\zhengyan\\Desktop\\test1\\t.txt";
copyFile(from_file,to_file);
}
private static void copyFile(String from_file, String to_file) throws IOException, InterruptedException {
RandomAccessFile r = new RandomAccessFile(from_file, "rw");
RandomAccessFile rw = new RandomAccessFile(to_file, "rw");
FileChannel rChannel = r.getChannel();
FileChannel rwChannel = rw.getChannel();
ByteBuffer byteBuffer1 = ByteBuffer.allocate(1);
ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024);
//分散读取数据
ByteBuffer byteBuffers[] = {byteBuffer1,byteBuffer2};
rChannel.read(byteBuffers);
while (rChannel.read(byteBuffers)!=-1){
for (ByteBuffer byteBuffer:byteBuffers){
byteBuffer.flip();
}
//聚合写入数据
rwChannel.write(byteBuffers);
for (ByteBuffer byteBuffer:byteBuffers){
byteBuffer.clear();
}
}
r.close();
rw.close();
}
字符集编码和解码
private static void charsettest() throws IOException {
Charset charset = Charset.forName("UTF-8");
ByteBuffer estr = charset.encode("sdfsd的df");
//estr.flip();
CharBuffer decode = charset.decode(estr);
System.out.println(decode.toString());
estr.rewind();
Charset gbk = Charset.forName("GBK");
CharBuffer decode1 = gbk.decode(estr);
//会出现乱码
System.out.println(decode1.toString());
}
NIO核心非堵塞
注意堵塞和非堵塞以及同步和异步的区别
网络过程中IO堵塞
假如服务器只有一个线程来处理用户请求,由于某种原因(数据还没到达)造成线程堵塞(线程放弃了CPU执行权),此时如果有其他的用户请求,该线程就不能及时的处理该请求。传统的解决方式就是开一个线程池,多线程来处理用户请求,但是这样可能依然会造成堵塞的情况。
NIO解决非堵塞
利用的是select选择器。将用户的请求注册到select上,select来监听所有的请求数据(通过单独的一个线程),如果请求的数据准备完毕,才将该请求任务分配到服务器的一个或者多个线程上执行。

1、使用NIO,阻塞式完成通讯
服务端
//服务端
public class BlockNIOServer {
public static void main(String[] args) throws IOException, InterruptedException {
//获取通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//绑定端口
serverSocketChannel.bind(new InetSocketAddress(8090));
while (true){
//获取客户端连接通道
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("连接成功,等待用户发送数据");
SocketAddress remoteAddress = socketChannel.getRemoteAddress();
String s = remoteAddress.toString();
String[] split = s.split(":");
//写入本地
String path = "C:\\Users\\zhengyan\\Desktop\\test1\\"+split[1]+".txt";
FileChannel fileChannel = FileChannel.open(Paths.get(path), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
//接受客户端的数据并且写入文件
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (socketChannel.read(byteBuffer)!=-1){
byteBuffer.flip();
fileChannel.write(byteBuffer);
byteBuffer.clear();
}
//可以给客户端提供反馈信息
byteBuffer.put("数据已经接受完毕...".getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
fileChannel.close();
socketChannel.close();
System.out.println("写入数据成功....");
}
}
}
客户端
//客户端
public class BlockNIOClient {
public static void main(String[] args) throws IOException, InterruptedException {
//获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8090));
FileChannel fileChannel = FileChannel.open(Paths.get("C:\\Users\\zhengyan\\Desktop\\test1\\x.txt"), StandardOpenOption.READ);
//System.out.println("模拟10秒之后发送数据...");
//可以开启两个客户端,一个睡10秒发送数据(先请求),一个不用睡眠(后请求),发现,必须等第一个用户处理完毕之后,第二个用户才可以被处理
//Thread.sleep(10000);
//分配缓冲区大小
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//读取本地文件发送到服务器
while (fileChannel.read(byteBuffer)!=-1){
byteBuffer.flip();
socketChannel.write(byteBuffer);
byteBuffer.clear();
}
//告诉服务器,我的数据已经发送完毕
socketChannel.shutdownOutput();
//接受服务器返回来的消息
StringBuffer stringBuffer = new StringBuffer();
int len =-1;
while ((len=socketChannel.read(byteBuffer))!=-1){
byteBuffer.flip();
stringBuffer.append(new String(byteBuffer.array(),0,len));
byteBuffer.clear();
}
System.out.println(stringBuffer);
socketChannel.close();
fileChannel.close();
}
}
1、使用NIO,非阻塞式完成通讯(通过select作为监听器)
服务端
//服务端
public class BlockNIOServer {
public static void main(String[] args) throws IOException, InterruptedException {
//获取通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//切换非阻塞模式
serverSocketChannel.configureBlocking(false);
//绑定端口
serverSocketChannel.bind(new InetSocketAddress(8090));
//获取选择器
Selector selector = Selector.open();
//将该通道注册到select中,让select监听该通道的连接是否准备就绪
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
Iterator<SelectionKey> iterator = null;
//通过选择器轮询获取已经准备就绪的事件
while (selector.select()>0){
iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
//如果获取的是准备连接就绪的事件
if (selectionKey.isAcceptable()){
System.out.println("有客户端已经准备好连接了....");
//开始接受连接客户端
SocketChannel accept = serverSocketChannel.accept();
//切换非阻塞模式
accept.configureBlocking(false);
//将通道注册到selector中,让select监听该通道的数据是否准备就绪
accept.register(selector,SelectionKey.OP_READ);
}
else if (selectionKey.isReadable()){
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
Random random = new Random();
int i = random.nextInt(100);
String path = "C:\\Users\\zhengyan\\Desktop\\test1\\"+i+".txt";
FileChannel fileChannel = FileChannel.open(Paths.get(path), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (socketChannel.read(byteBuffer)!=-1){
byteBuffer.flip();
fileChannel.write(byteBuffer);
byteBuffer.clear();
}
byteBuffer.put("数据已经接受完毕...".getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
fileChannel.close();
socketChannel.close();
System.out.println("写入数据成功....");
}
//取消选择键
iterator.remove();
}
}
}
}
客户端
//客户端
public class BlockNIOClient {
public static void main(String[] args) throws IOException, InterruptedException {
//获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8090));
FileChannel fileChannel = FileChannel.open(Paths.get("C:\\Users\\zhengyan\\Desktop\\test1\\x.txt"), StandardOpenOption.READ);
//System.out.println("模拟10秒之后发送数据...");
//可以开启两个客户端,一个睡10秒发送数据(先请求),一个不用睡眠(后请求),发现,必须等第一个用户处理完毕之后,第二个用户才可以被处理
//Thread.sleep(20000);
//分配缓冲区大小
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//读取本地文件发送到服务器
while (fileChannel.read(byteBuffer)!=-1){
byteBuffer.flip();
socketChannel.write(byteBuffer);
byteBuffer.clear();
}
//告诉服务器,我的数据已经发送完毕
socketChannel.shutdownOutput();
//接受服务器返回来的消息
StringBuffer stringBuffer = new StringBuffer();
int len =-1;
while ((len=socketChannel.read(byteBuffer))!=-1){
byteBuffer.flip();
stringBuffer.append(new String(byteBuffer.array(),0,len));
byteBuffer.clear();
}
System.out.println(stringBuffer);
socketChannel.close();
fileChannel.close();
}
}
使用UDP
服务端
//服务端
public class BlockNIOServer {
public static void main(String[] args) throws IOException, InterruptedException {
//获取通道
DatagramChannel datagramChannel = DatagramChannel.open();
//切换非阻塞模式
datagramChannel.configureBlocking(false);
//绑定端口
datagramChannel.bind(new InetSocketAddress(8090));
//获取选择器
Selector selector = Selector.open();
//只需要监听数据是否到来
datagramChannel.register(selector, SelectionKey.OP_READ);
while (selector.select()>0){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
if (selectionKey.isReadable()){
DatagramChannel channel = (DatagramChannel) selectionKey.channel();
ByteBuffer buf=ByteBuffer.allocate(1024);
channel.receive(buf);
buf.flip();
System.out.println(new String(buf.array(),0,buf.limit()));
}
//取消选择键
iterator.remove();
}
}
}
}
客户端
//客户端
public class BlockNIOClient {
public static void main(String[] args) throws IOException{
DatagramChannel datagramChannel = DatagramChannel.open();
ByteBuffer buf=ByteBuffer.allocate(1024);
buf.put(new Date().toString().getBytes());
buf.flip();
datagramChannel.send(buf,new InetSocketAddress("127.0.0.1",8090));
buf.clear();
datagramChannel.close();
}
}
管道(Pipe)
Java NIO 管道是两个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
public class PipeTest {
public static void main(String[] args) throws IOException, InterruptedException {
//获取管道
Pipe pipe = Pipe.open();
new Thread(new MyThread1(pipe)).start();
Thread.sleep(3000);
new Thread(new MyThread2(pipe)).start();
}
}
class MyThread1 implements Runnable{
private Pipe pipe;
public MyThread1(Pipe pipe){
this.pipe = pipe;
}
@Override
public void run() {
Pipe.SinkChannel sink = pipe.sink();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put("ssss".getBytes());
byteBuffer.flip();
try {
sink.write(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
}
byteBuffer.clear();
}
}
class MyThread2 implements Runnable{
private Pipe pipe;
public MyThread2(Pipe pipe){
this.pipe = pipe;
}
@Override
public void run() {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
Pipe.SourceChannel source = pipe.source();
try {
source.read(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(new String(byteBuffer.array(),0,byteBuffer.limit()));
}
}

浙公网安备 33010602011771号