NIO和AIO

更多的讲线程的控制,线程的操作,相关的内容,但是考虑到NIO,以NIO为主呢,虽然没有多线程的控制及协作,

提出一些特别的观点,但是他改变了线程应用层的一种协作方式,可以说他解决了一些实际的困难,节省了系统的

成本,AIO叫做异步IO,所谓异步的操作,之前也说过,异步调用会在后台,后台使用线程的方式,去做某件事情,使得前面的

调用就会很快的返回,因此或多或少会和线程又关系,所以在这个地方呢,把NIO和AIO做一个简单的介绍,我们主要是分这几个

部分,我们不会吧NIO做一个很完整的介绍,我们只是把最基本的部分,以及跟线程使用相关的部分,给大家做一个梳理,首先我们会

介绍一个buffer,buffer是NIO基本的一个组件,然后channel,selector,最后看一下AIO相关的一些内容

下面我们来看一下什么是NIO,NIO是new IO的一个简称,与旧式的流相对的,它是一套新的IO的标准,它是在JDK1.4当中

被纳入到JDK当中去的,他主要有这么几个特点,传统的标准的IO呢,基于流的,NIO是基于块的,我们硬盘上存储数据的时候呢,

就是基于块去做存储的,并不是一个字节一个字节存储的,他读写最小单位是块,因此NIO的性能比基于流的要好一些,另外他有

buffer的支持,使用Buffer的对象,对于每一种原始的数据类型呢,都有它对应的一种buffer,他还使用了通道,与标准的IO相比呢,

使用的是流,inputstream,outputstream,这里使用了Channel,作为一个新的原始IO的抽象,他还支持文件锁和内存映射文件,

什么叫文件锁呢,有时候我们会在程序里面看到,程序在运行的时候呢,会生成什么.lock,这个就是文件的锁,当有这个文件存在

的时候呢,表示在当前程序当中呢,有线程来调用了这把锁,其他线程同样要使用这把锁呢,就会陷入等待,它使用完这把锁之后呢,

把这个文件给删除掉,其他线程才能进去做一些相应的操作,那文件系统来实现这个锁,与我们之前讲过的ReentrantLock相比呢,

我们使用内存当中的一个原子整数,AtomicInteger来实现这个锁,这里使用一个文件来实现这个锁,NIO就支持你使用这个方式来写

代码,什么叫内存文件映射,一个文件是在硬盘磁盘里面的,他可以 把整个文件映射到内存里面去,把文件读到内存里面来,这种

方式呢是很快的,比传统的把数据一个个读上来,要快很多,还有对于网络操作来讲呢,提供selector

下面我们来看buffer和channel,buffer缓冲区,它是NIO核心的部分,当你从通道里读数据的时候呢,那你要先写入这个

buffer,当你要从通道里写数据的时候呢,你要先写入buffer,通道就是一个IO的抽象,通道的另一端就是我们要操作的

文件,或者是socket,这里就是一个抽象的通道,我们通过buffer的读写,来对实际的IO进行修改

这里是JAVA当中buffer的实现,基本的类型,基本的类型都有对应的buffer,使用最多的就是byte,

正常我们都是喜欢一个字节一个字节的读数据

下面来看一下简单buffer的使用,这里就是新建了一个文件,新建文件之后拿到的就是InputStream,然后通过这个

InputStream,我们就可以得到Channel,对应的就是这个文件,所以当你往FileChannel里面读数据和写数据的时候呢,

其实你就是对这个文件进行操作,然后我们对ByteBuffer分配了1K的buffer,这个buffer大小是1K,1024个字节,然后

我们就从Channel里面读数据,字节,读到这个buffer里面去,这样buffer里面就存了文件的内容,然后我们可以把这个

Channel给关闭掉,我们会读取buffer里面的数据,flip操作后面会介绍,这里就是我们一般意义上通过buffer进行NIO

的基本操作,大概就是这几个步骤,要搞定一个buffer,然后把channel和buffer的关系处理起来,是read还是write,

最后操作完毕之后呢,我们要做close

复制文件需要一个源和地址,写有fos,表示fileoutputstream,不管源也好目标也好,我们都能够拿到一个channel,

一个通道,源就是read,目标就是write,然后我们分配1K的buffer,我们读和写都要通过buffer去做,首先我们要把

buffer清空,里面什么都没有,然后我们去读这么一段buffer上来,我会尝试去读1K,但是如果有时候你文件里1K都没有,

那显然就读不到1K,这个时候我就需要知道我到底读了多少数据上来,我实际读取的数据,如果我实际读取的数据是-1呢,

我读取完了,文件读到头了,我就break,while就出来了,如果文件还没有读取完呢,我下一步还得继续读,因此在read之后

呢,这个buffer里面的数据是什么呢,实际上就是文件的内容,read文件的内容,然后我们做读写转换,flip我们=在后面会做一个

详细的介绍,这里只需要知道这里是一个读写转换,然后把buffer写到writeChannel当中去,通过这样的一个循环,最后会1K,1K的

把源数据的内容,读出来,然后写进去,读1K写1K,知道最后可能不到1K,那么我们就把最后的部分写进去,最终会读完,-1表示读完了,

读完之后我们就关掉,这个就实现了文件的复制

在buffer当中有三个重要的标记位,其实buffer当中还有很多函数的操作,函数的操作很大一部分呢,修改了

几个位置,我们只要函数的实际含义,位置的实际含义搞清楚以后,我们对他大部分的API,形成了比较容易的理解,

对position,capacity容量做一些调整,位置是什么呢,表示操作当前的缓冲区,当前的buffer,我已经读到,或者写到,

哪个位置了,我下一次读和写呢,我就会从position的下一个位置呢,开始写数据或者读数据,如果是写呢,就从下一个

位置写,读就从下一个位置去读,我当前操作的位置,我知道当前操作的位置呢,我就知道下一个要操作的是什么位置,

容量就是我整个缓冲区的上限,我到底一共能放多少数据,这个Limit上限是什么呢,是我缓冲区的实际上限,这两个是

什么意思呢,比如我整个缓冲区呢,一共有10个byte,那指的是什么呢,我容量有10个byte,我最多不能超过10个byte,但是

如果你去读了一个文件,这个文件可能只有5个byte,这个时候你缓冲区当中,直接用的只有5个byte,因为你文件只有5个byte,

这个时候我认为,简单理解说,上限只有5,虽然你缓冲区一共能放10,但是你后面5个没有放东西,相当于说是没有用的,你只

放了5,我有意义的数据只到5为止,上限之下的数据才有意义的,一定是小于或等于容量的,因为你不可能超过容量,如果你新建

了一个buffer呢,通常情况下和容量是相等的,上限刚才说了,通常会表示有意义的数据的这个位置,因此它代表可读的总容量,

通常和上一次写是相同的,例如你上一些写到5,读我只能读到5,这就是容量和上限的区别,整个缓冲区三个重要的位置呢,

三个属性呢,就是这个位置,容量和上限,尤其是这个容量和上限呢

下面来看一个例子来理解一下,position,limit,这里我们做几个操作,首先我们分配15个字节的缓冲区,

然后我往里面写10个字节,每次都写一个byte进去,写10次,就写10个byte,然后我做一个flip,然后我再读5个

byte出来,然后我再去做一个flip

首先你新建的时候呢,你新建的是15,所以我们当前位置是在哪里呢,位置在这个0,你开始做操作呢,

那我这个limit,capacity就是15,最后一个位置,因为我刚刚成立的时候呢,我容量肯定是最大的容量,那么我的

限制在哪里呢,我的上限肯定也是我的最大上限,还没有做任何操作,我存了10个数据进去,我一个一个往下存,最后的

位置呢,一定是10,我当前的position是在这里,但是我的capacity和limit还是没有动的,因为我只是存数据,我这两个值

是不会动,我put完10个数据以后呢,变成这样子

接着就是一个重点,flip操作,flip操作会做些什么事情呢,他其实做两件事情,第一个我position是0,

把position放到第0个位置上来了,表示我下一个操作是什么位置呢,是从头开始操作的,我刚刚跑到了这里,

但到这里不行,我把你拉回来,接着还有一个非常重要的操作,把limit放到了这个地方,capacity是不会动的,

他表示的是整个缓冲区的容量,你申请的是多少,他还是多少,如果是15他就是15,不会因为你操作的变化而发生

变化,所以一般是不会动的,所以当你做slip之后呢,它是两个操作,第一个是position是0,第二个是limit到了刚才

position的位置,这个位置大家注意,是刚才做操作的时候呢,这个position的位置,然后这个position变成了limit,

这样做有什么意义呢,他的意思在于说,我下一步操作,我自然是从0开始操作,我操作到什么时候为止呢,操作到哪个

位置是有意义的呢,操作到limit是有意义的,换句话说,从position到limit的数据,是我上一次写入的数据,是有意义

的数据,而10到15并不是我上次写入的数据,他很有可能是没有意义的数据,因此如果我这个时候要去读缓存区,那我

只需要读position到limit的数据就可以了,因此flip通常是用在读写转换,在我flip之前,通常是写,我把数据往缓冲区

里写,我flip之后,通常需要去读这个缓冲区,为什么我可以读呢,是因为我位置已经到0了,并且我往下读呢,能够保证我不会

读到没有意义的数据,只会读到limit,他不仅重置了position,还将limit重置到当前的position位置,读写转换的时候呢,

需要执行这个方法

然后再进行5次的读操作,所以position来到这里,limit到这里,然后再做flip,又变成这样子了,

position又归零,limit又跑到position的位置上了,显然这个操作意义并不大,后面的数据相当于没有用了

Buffer还支持下面几个比较重要的操作,flip我们已经讲过了,在读写转换的时候,是非常有用的,

此外还有rewind,将position清零,这里只是简单的把position给拉回来,有时候我们可能需要一遍一遍的去扫描

缓冲区,扫一遍还拿不到足够的信息来做判断,因为有些时候数据结构可能会比较复杂一些,所以我们可能需要扫两遍,

这个时候我们就可以把position拿到前面去扫描第二遍,你把position清空,limit是不会变的,不像flip一样,把limit

也变一下,这样你第二次读的数据和第一次读的数据未必是一样的,clear清空,清空什么意思呢,就是我position置0了,

我limit设置为capacity大小,清空之后我们可以认为里面的数据都没有意义的,因为你不知道有意义的数据放在哪个位置,

比如我本来是0到15,我0到10是有意义的,然后我把limit也设置为15,这个时候你就不知道读到什么时候为止,这个数据才是

有效的,我清空之后一般不会去读,clear之后一般会做写入的操作,这就是buffer常用的几个函数

下面就是关于文件映射到内存,这个很简单,把fc这个channel,直接映射到内存当中,这样整个文件给读进来了,

然后你对MappedByteBuffer的修改呢,修改这个文件,这个速度是非常快的,要比你传统的修改要快

上面这些基础之后呢,我们看看NIO的网络编程,首先我们介绍一般意义上的多线程的结构,我们这个多线程的服务器呢,

一个简单的做法呢,一般是这样做的,有多个客户端,去连接服务器,有一个派发线程,线程只做accept,accept客户端之后呢,

不会做实际的业务处理,它会把接到的客户端呢,派发给某一个线程做处理,当然把socket给线程,每个线程对应一个客户端,

做这样的一个处理,这个时候你有100个客户端过来,排100个线程,去做这个处理,那么在这个处理当中呢,我们当然也会涉及

到数据的读和写,像这样的一个结构呢

我们会实现一个简单的多线程的服务器,读到了客户端的数据呢,就往服务端回写相同的数据,

他读到hello,他就回写hello,他读到world,他就回写world,那么这个程序就是说,相对来说还是比较简单的,

把这个socket建立起来,做一个监听,8000端口做一个监听,等待accept,等待客户端连接,如果等到之后呢,

通过线程池去执行这个操作,把这个消息进行处理,处理这个消息,处理这个消息的时候呢,它使用了一个新的线程,

线程池里挑一个线程做处理,同时很重要的一点呢,clientSocket,是哪一个客户端要把它带过去,新的线程和客户端

进行一个通信,主线程只做accept,做客户端的接入

先从客户端里读进来,把客户端发的数据读进来,读到的是什么,就发出去,outputstream就是从socket里面读出来的,

那么在这个时候呢,我们就会把读到的数据写回去,这里我们还统计了一下时间,我们读之间记录一个时间戳,写完之后

写一个时间戳,我们这个线程实际上,从读到写,一共花了多长时间,最后我们会关闭一些资源

看一下客户端,客户端就新建一个socket,然后去连8000端口,它会有一个PrintWriter,然后去print这个信息,

然后呢再去读,读到的数据应该是服务器返回的数据,读的的时候也是从socket里面,inputstream里面去读这个数据,

把这个信息呢做一个输出,执行代码我们就看到客户端,就会读到客户端的输出

传统的网络编程,使用这种方式做的时候呢,它会产生什么问题呢,我们可以来解释一下,在这种模式之下呢,

其实系统是为每一个客户端,去使用的一个线程,每个客户端使用一个线程,系统当中最不可靠的就是网络,

CPU也好,内存也好,硬盘也好,都还是比较可靠地,如果你从硬盘里面读不到数据,从内存里面读不到数据,那基本上

就是硬件上的问题,但是网络问题呢,比较特殊,网络有各种问题你可能读不到,网络拥塞,网络非常的延迟,可能会出现

大量的丢包,有可能网线没有插好,WIFI出现了什么问题,都是有可能的,信号太弱,因此网络相对来讲呢,不是一个很可靠的

一个东西,因此有可能在网络传输的时候呢,可能出现延迟中断等等,对整个系统交互产生影响,线程是一对一的服务的,我

新建一个线程就是为客户端服务的,客户端出现问题的时候呢,或者网络出现问题的时候呢,线程可能会占用比较长的时间,

因为数据的准备和读取呢,都是由新建的线程负责,如果说客户端数量众多,大量的并发连接,这些并发连接,有若干个不太好的

状态呢,导致线程会卡死在那边,就会花很多时间在客户端,可能会发生比较大的消耗,NIO就是用来解决这个问题,它是个非

阻塞的IO,非阻塞什么意思呢,我读取数据,不需要去做等待,为什么说我读取数据不需要等待呢,我来一个线程一个客户端,

线程在读客户端数据的时候呢,当然就得等待了,我要等你这个数据读完,我才能把这个数据拿下来,但是IO操作通常会分为

两个部分,一个是准备,准备IO,一个是真正的去做IO,非阻塞是指说,我准备去做IO的时候,我不会在这里做线程的阻塞,

我只有当这个数据准备好了之后呢,我去读一下,去写一下,因此NIO可以认为是一个非阻塞的IO,如果客户端反应是很迟钝的,

很缓慢的,或者出现延迟等东西,这延时的时间呢,其实不会出现在NIO的线程当中,因为如果你没有准备好数据,那么其实这个

线程是不会开始工作的

这个客户端做connect之后,他去做输出,每做一个输出就要休眠,休眠一秒钟,他休眠了6秒钟时间,这个模拟了一个

客户端延迟比较严重,客户端他可能会出现各种各样的问题,同样客户端去连这个服务器,因此在传统的IO上面呢,

我们会得到这么一个输出,因为客户端发任何的写数据,或者网络延迟比较严重,他每处理这个数据都要花6秒钟的

时间,这里开了若干个客户端线程在这里做

刚刚看到的6秒钟时间就是读和写的时间,这里可以看到的呢,6秒钟时间花在哪里呢,因为你读的时候读不到数据,

数据根本就没有准备好,写的很慢,这回导致服务器的输出是这样子,每个线程都被卡了6秒钟

如果NIO来处理这个问题会怎么做呢,首先Channel有点类似Socket的流,一个Channel可以对应的是文件或者网络Socket流,

这个当然我们在网络编程当中呢,对应的是Socket,这个Channel有一种selectable channel,可选的Channel,可选的Channel

是什么意思呢,他可以被轮询,可以被选择,那么selector是什么东西呢,它是个选择器,他可以选中某一个Channel,做这个数据

的处理,一个selector他只对应一个线程,一个线程只对应一个selector,一个selector就可以轮询多个channel,这是很关键的,

一个selector可以轮询多个channel,一个channel对应的是一个socket,换句话说,就是一个线程轮询了多个socket,selector

通常情况之下,他要看channel,客户端,每个socket就是客户端,他看哪个客户端准备好了,有两种方式,一个叫select,一个叫

selectnow,我们现在讲select,我现在已经有客户端准备好数据了,如果说没有客户端准备好数据,那你在selector上面调用

select方法呢,是会阻塞的,虽然NIO是非阻塞的,非阻塞也不是一个绝对上的含义,当你没有数据准备好的时候,实际上是无事可做的,

当你有数据准备好的时候,select就返回,返回之后呢,你实际上在后面可以得到一个selectKey的东西,那你只有在执行select

之后才可以执行selectKey,selectionKey其实表示一对selector和channel,关系就是selector和channel,我在每一个selector

上面,发现哪个channel是数据已经准备好的,只有准备好的数据的channel,client socket,才会被select选中,才会触发select

做返回,如果没有一个数据准备好的,这select是被阻塞,不会返回的,这样就使得我一个线程,我可以同时监控多个客户端,我不管

有几个,而当你客户端反应很迟钝的时候,我只是说这个线程本身不会做返回,做这个阻塞,但是我只有一个线程在里边,或者说

我这个线程数量是很少的,因此我极少的线程监控大量的客户端,对我整个系统的影响相对来说要好一些,如果我有大量的客户端,

每个客户端都阻塞一个线程,那么这个时候我又大量的线程会阻塞,而我有极少量的线程,来监控我大量的客户端,计算大家都阻在

那里不动了,我也只有少量的线程受到影响,如果我真的监控大量的客户端,不可能所有的客户端都出问题,都在里面不动,总有

几个是准备好的,给我返回的,你准备好了过来就行了,那我就可以拿到selectionKey,那我从selectionKey当中,我就可以拿到

selectionKey和channel,因为channel里面是有数据的,因为channel后面代表socket,你既然数据准备好了,那我就把这个channel

拿过来,把数据读出来就可以了,那么在这个读数据的过程当中,你就不需要做一些等待操作,因为我是准备好了才被选上来的,

那selectNow是什么意思呢,selectNow是和select功能是一样的,他也是选择已经准备好的socket,他跟select的区别就是,select

会阻塞,如果当前没有数据准备好,他就卡在那边不动了,直到有人准备好之后才开始,selectNow他不阻塞,它是直接返回,如果当前

没有人准备好,就返回0,如果有人准备好,就返回多少个客户端准备好了,所以这个是不阻塞的,如果你要完全的不阻塞的话,你可以选择

selectNow,但是大部分的情况下呢,这个select已经够用了,因为当你没有数据准备好的时候,你确实是应该陷入一个等待

下面我们来参考一下代码,因为代码会比较长,感觉不是很好读

https://my.oschina.net/hosee/blog/615269
package com.learn.thread;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MultiThreadNIOEchoServer {
	public static Map<Socket, Long> geym_time_stat = new HashMap<Socket, Long>();

	class EchoClient {
		private LinkedList<ByteBuffer> outq;

		EchoClient() {
			outq = new LinkedList<ByteBuffer>();
		}

		public LinkedList<ByteBuffer> getOutputQueue() {
			return outq;
		}

		public void enqueue(ByteBuffer bb) {
			outq.addFirst(bb);
		}
	}

	class HandleMsg implements Runnable {
		SelectionKey sk;
		ByteBuffer bb;

		public HandleMsg(SelectionKey sk, ByteBuffer bb) {
			super();
			this.sk = sk;
			this.bb = bb;
		}

		@Override
		public void run() {
			// TODO Auto-generated method stub
			EchoClient echoClient = (EchoClient) sk.attachment();
			echoClient.enqueue(bb);
			sk.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
			selector.wakeup();
		}

	}

	private Selector selector;
	private ExecutorService tp = Executors.newCachedThreadPool();

	private void startServer() throws Exception {
		selector = SelectorProvider.provider().openSelector();
		ServerSocketChannel ssc = ServerSocketChannel.open();
		ssc.configureBlocking(false);
		InetSocketAddress isa = new InetSocketAddress(8000);
		ssc.socket().bind(isa);
		// 注册感兴趣的事件,此处对accpet事件感兴趣
		SelectionKey acceptKey = ssc.register(selector, SelectionKey.OP_ACCEPT);
		for (;;) {
			selector.select();
			Set readyKeys = selector.selectedKeys();
			Iterator i = readyKeys.iterator();
			long e = 0;
			while (i.hasNext()) {
				SelectionKey sk = (SelectionKey) i.next();
				i.remove();
				if (sk.isAcceptable()) {
					doAccept(sk);
				} else if (sk.isValid() && sk.isReadable()) {
					if (!geym_time_stat.containsKey(((SocketChannel) sk
							.channel()).socket())) {
						geym_time_stat.put(
								((SocketChannel) sk.channel()).socket(),
								System.currentTimeMillis());
					}
					doRead(sk);
				} else if (sk.isValid() && sk.isWritable()) {
					doWrite(sk);
					e = System.currentTimeMillis();
					long b = geym_time_stat.remove(((SocketChannel) sk
							.channel()).socket());
					System.out.println("spend:" + (e - b) + "ms");
				}
			}
		}
	}

	private void doWrite(SelectionKey sk) {
		// TODO Auto-generated method stub
		SocketChannel channel = (SocketChannel) sk.channel();
		EchoClient echoClient = (EchoClient) sk.attachment();
		LinkedList<ByteBuffer> outq = echoClient.getOutputQueue();
		ByteBuffer bb = outq.getLast();
		try {
			int len = channel.write(bb);
			if (len == -1) {
				disconnect(sk);
				return;
			}
			if (bb.remaining() == 0) {
				outq.removeLast();
			}
		} catch (Exception e) {
			// TODO: handle exception
			disconnect(sk);
		}
		if (outq.size() == 0) {
			sk.interestOps(SelectionKey.OP_READ);
		}
	}

	private void doRead(SelectionKey sk) {
		// TODO Auto-generated method stub
		SocketChannel channel = (SocketChannel) sk.channel();
		ByteBuffer bb = ByteBuffer.allocate(8192);
		int len;
		try {
			len = channel.read(bb);
			if (len < 0) {
				disconnect(sk);
				return;
			}
		} catch (Exception e) {
			// TODO: handle exception
			disconnect(sk);
			return;
		}
		bb.flip();
		tp.execute(new HandleMsg(sk, bb));
	}

	private void disconnect(SelectionKey sk) {
		// TODO Auto-generated method stub
		//省略略干关闭操作
	}

	private void doAccept(SelectionKey sk) {
		// TODO Auto-generated method stub
		ServerSocketChannel server = (ServerSocketChannel) sk.channel();
		SocketChannel clientChannel;
		try {
			clientChannel = server.accept();
			clientChannel.configureBlocking(false);
			SelectionKey clientKey = clientChannel.register(selector,
					SelectionKey.OP_READ);
			EchoClient echoClinet = new EchoClient();
			clientKey.attach(echoClinet);
			InetAddress clientAddress = clientChannel.socket().getInetAddress();
			System.out.println("Accepted connection from "
					+ clientAddress.getHostAddress());
		} catch (Exception e) {
			// TODO: handle exception
		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		MultiThreadNIOEchoServer echoServer = new MultiThreadNIOEchoServer();
		try {
			echoServer.startServer();
		} catch (Exception e) {
			// TODO: handle exception
		}

	}

}
private static ExecutorService tp= Executors.newCachedThreadPool();  
		private static final int sleep_time=1000*1000*1000;  
		public static class EchoClient implements Runnable{   
			public void run(){          
				try {              
					client = new Socket();              
					client.connect(new InetSocketAddress("localhost", 8000)); 
					writer = new PrintWriter(client.getOutputStream(), true); 
					writer.print("H");              
					LockSupport.parkNanos(sleep_time);       
					writer.print("e");           
					LockSupport.parkNanos(sleep_time);      
					writer.print("l");       
					LockSupport.parkNanos(sleep_time);  
					writer.print("l");       
					LockSupport.parkNanos(sleep_time);  
					writer.print("o");     
					LockSupport.parkNanos(sleep_time);  
					writer.print("!");         
					LockSupport.parkNanos(sleep_time);    
					writer.println();      
					writer.flush(); 
				}catch(Exception e)
				{
				}
			}
		}
NIO会将数据准备好之后,再交由应用进行处理,因此数据没有准备好呢,是跑不到你应用里面去的,

从这个角度来说呢,他可以节省系统资源,读取和写入过程本身呢,是在应用线程本身完成的,就是当你真正的

去读,只是把等待的时间剥离到单独的线程去等待,从而提高运行的效率

来看一下AIO异步IO,对于NIO来讲,数据准备好了再来通知我,但是读数据和写数据的事情,还是要我这个线程来做的,

他只是把我等待IO的时间呢,全部剥离到极少线程中去完成,避免大量的IO线程进行等待,造成资源的浪费,但是AIO更进一步,

等你读完了或者写完了再通知我,对于我们应用来说,你既不需要读也不需要写,你只需要等到那里就行了,你只需要系统把数据

都处理完了之后呢,你把你的回调函数放进去,就会来执行,这里要注意的是呢,AIO并不会加快IO,IO变多快,其实不管什么方式,

包括前面的NIO也好,IO本身的速度并没有加快的,它是改变了原来对IO的处理方式,而导致看起来你系统的性能变更高,我们只是

进行了一个更加合理的线程上的调度,因此AIO并不会把IO本身变快,他只说说你读完或者写完之后呢,给我一个通知,我什么时候

拿到这个通知,我什么时候做这个事情,当我没有写完的时候呢,我也是不干活的,但是我这个不干活的时候呢,我可以去处理其他的

一些事情,从而把这个时间充分的利用起,AIO当中使用的时候呢,定义大量的回调函数,业务处理

AIO就是用这个类,AsynchronousServerSocketChannel,异步的ServerSocketChannel,使用这种方式可以打开Socket,

在这个端口上进行监听,如果你要去accept的时候呢,它是异步IO,accept本身不可以去阻塞,因为阻塞你就谈不上异步了,

异步是马上返回的,但是我马上返回我怎么做事情呢,照理说你是有一个accept上来了,然后我拿到accept的信息,然后我才能够

干活,他们这里是有一个回调,回调就是CompletionHandler,在你accept的时候呢,你接受一个回调函数,实际上是一个接口,回调接口

做什么呢,做处理,处理什么呢,处理当有人真正accept上来的时候呢,我会来调用你这个handler,然后我把我这个accept相关的

信息呢,传给你,然后你再去做accept之后的一些逻辑,这个就是AIO的基本思想

那我们来看一下异步的ServerSocketChannel的方法,最重要的是read和write,read就是从socket里面读数据,

因为我是异步方法,所以我读数据的时候呢,那我自然是不能够等待我读完了再返回,那就不叫异步了,所有的read

方法都是立即返回的,我们先来看第一个方法,第一个方法我读到哪里去,byteBuffer,读到哪里去,读到这个地方去,

这里的long和TimeUnit,是指读数据的超时时间,如果超时我就不读了,我读完之后做什么呢,就做这个事情,就把完成的

Handler,调用一下,第二个函数也是类似的,我读到这个地方去,读到这个buffer里面去,然后我读完之后呢,他的返回值

是什么,返回值是Future,一个Future模式,它能够马上返回,但是没有读完,没有读完会给你一个Future,Future里面有一个

Integer,告诉你将来读完之后呢,告诉你到底读到了多少个字节,最后一个也是类似的,但是最后一个会比较复杂,它会把一个

Socket数据,读到byteBuffer数组当中去,当然数组里面可以指定一个offset,你准备读到哪些buffer里面,你不一定从第0个

开始,可以从第一个或者第二个写数据,这个通常是用来做什么的呢,有时候我们一些网络协议报文,头部通常情况下,可能

是固定的,因为头不可变的话,处理起来会非常的麻烦,因此有些网络协议报文,这个时候我们可以是使用这种方式呢,很快的

把头部给剥离出来,比如你头部是20个字节,那我就定义一个byteBuffer的数组,其中第一个就是24,很快就把这个24个字节给

剥离掉,读完之后也是到CompletionHandler里面去,写是非常类似的,write的时候也是返回一个Future,写的时候会立即返回,

将来某个时间段呢,把这个Future拿到呢,Future到底写完了没有,到底写了多少字节,有关这个东西呢

用异步IO来做异步的实现,大家注意这个代码虽然看起来很长,但是他只有一个函数,就是accept,后面接收了一个

参数,这个参数就是completionHandler,这个CompletionHandler的定义呢,他就特别长,这里只是执行了一个accept

而已,当然这里看起来是很长的代码,但是在执行的时候呢,其实是立即返回的,立马就会执行完,只是把completionHanlder

传进去而已,当他accept完了之后,在这里我们使用result呢,我们使用一个future的函数,我们限时100秒,如果超过100秒

还没有读到数据呢,读完之后我们就进行读写转换,然后去写,这个写还是返回的Future,这里就是AIO的一个使用

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值