java之AIO实例

37 篇文章 4 订阅

AIO介绍

JDK1.7升级了NIO类库,升级后的NIO类库被称为NIO 2.0,Java正式提供了异步文件I/O操作,同时提供了与UNIX网络编程事件驱动I/O对应的AIO。AIO是真正的异步非阻塞I/O。它不需要通过多路复用器(Selector)对注册的通道进行轮询操作即可实现异步读写,从而简化了NIO的编程模型。NIO 2.0引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。目前的AIO与NIO底层都使用了epoll(Linux中),所以二者性能都很好,主要差异在于同步与异步,NIO是同步的,始终只有一个线程在进行结果处理,而AIO的异步回调则是基于多线程的,如果NIO结果处理中引入多线程,个人认为二者性能是相仿的。

什么是epoll?
epoll是Linux中多路复用IO接口select/poll的增强版本,select/poll模型是忙轮询,即一直不停地轮询看哪些操作已经结束可以获取操作结果了,而epoll则是将已经结束的操作的操作结果放入队列中,然后只需要遍历处理队列中的操作就可以了,避免了CPU的浪费,提升程序运行效率。

AIO与NIO有什么区别?

1.NIO是同步非阻塞I/O,AIO是异步非阻塞I/O;
2.AIO与NIO的操作结果获取方式不同,NIO的操作结束后会将操作就绪的I/O放在队列中,由Selector依次循环获取处理;AIO操作结束后则会直接回调CompletionHandler的实现类的相应函数来进行处理;
3.处理操作结果时NIO是单线程,即由Selector依次在当前线程中进行处理,如果需要多线程处理需要自行实现,这也是为什么它是同步而非异步;而AIO在回调处理操作结果时,是多线程的,其底层设有线程池。

AIO既然是异步的,那么如何获得操作结果?

1.通过返回的Future模式java.util.concurrent.Future类来表示异步操作的结果;
2.在执行异步操作时传入一个java.nio.channel,并传入CompletionHandler接口的实现类作为操作完成的回调,CompletionHandler顾名思义就是专门用来处理完成结果的。

我更推荐用CompletionHandler的方式,这些handler的调用是由 AsynchronousChannelGroup的线程池派发的。显然,线程池的大小是性能的关键因素。AsynchronousChannelGroup允许绑定不同的线程池,通过三个静态方法来创建:

AIOAPI介绍

java.nio.channels.AsynchronousChannel
       标记一个channel支持异步IO操作。

 java.nio.channels.AsynchronousServerSocketChannel
       ServerSocket的aio版本,创建TCP服务端,绑定地址,监听端口等。

java.nio.channels.AsynchronousSocketChannel
       面向流的异步socket channel,表示一个连接

java.nio.channels.AsynchronousChannelGroup
       异步channel的分组管理,目的是为了资源共享。一个AsynchronousChannelGroup绑定一个线程池,这个线程池执行两个任务:处理IO事件和派发CompletionHandler。AsynchronousServerSocketChannel创建的时候可以传入一个 AsynchronousChannelGroup,那么通过AsynchronousServerSocketChannel创建的 AsynchronousSocketChannel将同属于一个组,共享资源。

java.nio.channels.CompletionHandler
       异步IO操作结果的回调接口,用于定义在IO操作完成后所作的回调工作.  CompletionHandler有三个方法,分别对应于处理成功、失败、当操作完成时,会回调completed,出现异常失败时会回调failed。

completed

操作完成时,回调completed函数,其有result和attachment两个参数:

  • result是操作完成后的操作结果;
  • attachment是在进行回调时可以传入的附件,用于回调内的操作;

failed操作异常时回调failed函数,其有exc和attachment两个参数:

  • exc即进行操作时出现的异常;
  • attachment和completed中的一致,为在进行回调时传入的附件,用于回调内操作;

 其中的泛型参数V表示IO调用的结果,而A是发起调用时传入的attchment。

client端:

public class SimpleTimeClient {
	private String host;
	private int port;
	private CountDownLatch latch;
	private AsynchronousSocketChannel channel;//异步socket通道
	public static void main(String [] args){
		while (true) {
            new Thread(() -> {
                try {
                    System.out.println("time client thread: " + Thread.currentThread());
                    SimpleTimeClient client = new SimpleTimeClient("localhost", 8088);
                    client.latch.await();
                } catch (IOException | InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
	}
	private SimpleTimeClient(String host, int port) throws IOException{
		 this.host = host;
	     this.port = port;
	     this.latch = new CountDownLatch(1);
	     initChannel();
	}
	private void initChannel() throws IOException
	{	
        channel = AsynchronousSocketChannel.open();// 打开异步socket通道
        // 异步连接指定地址,连接完成后会回调ConnectionCompletionHandler
        //A attachment :AsynchronousSocketChannel的附件,用于回调通知时作为参数传递,调用者可以自定义。
        //CompletionHandler<Void,? super A> handler 异步操作回调通知接口
        channel.connect(new InetSocketAddress(host, port), null, new ConnectionCompletionHandler());
	}
	private class ConnectionCompletionHandler implements CompletionHandler<Void, Void> {
		@Override
		public void completed(Void result, Void attachment)
		{
			System.out.println("connection thread: " + Thread.currentThread());
			String msg = "query time order";
			ByteBuffer writeBuffer = ByteBuffer.allocate(msg.length());
            writeBuffer.put(msg.getBytes(StandardCharsets.UTF_8)).flip();
            // 异步写入发送数据,写入完成后会回调WriteCompletionHandler
            channel.write(writeBuffer, writeBuffer, new WriteCompletionHandler());
		}
		@Override
		public void failed(Throwable exc, Void attachment)
		{
			 exc.printStackTrace();
	         latch.countDown();//异常时执行让线程执行完毕
		}	
	}
	//写数据完成回调处理类
	private class WriteCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {
		@Override
		public void completed(Integer result, ByteBuffer buffer){
			System.out.println("write thread: " + Thread.currentThread());
			if(buffer.hasRemaining())
				channel.write(buffer, buffer, this);
			else{
	            ByteBuffer readBuffer = ByteBuffer.allocate(1024);
	            //异步读取返回的数据,读取结束后会回调ReadCompletionHandler
	            channel.read(readBuffer, readBuffer, new CompletionHandler<Integer, ByteBuffer>(){
					@Override
					public void completed(Integer result, ByteBuffer buffer)
					{
						System.out.println("read thread: " + Thread.currentThread());
						buffer.flip();
						byte[] bytes = new byte[buffer.remaining()];
						buffer.get(bytes);
						String body;
						body = new String(bytes, StandardCharsets.UTF_8);
						System.out.println("now is " + body);
						latch.countDown();
					}
					@Override
					public void failed(Throwable exc, ByteBuffer attachment)
					{
						try{
							channel.close();
							latch.countDown();
						} catch (IOException e){							
							e.printStackTrace();
						}
					}
	            	
	            });
			}      
		}
		@Override
		public void failed(Throwable exc, ByteBuffer attachment)
		{
			exc.printStackTrace();
            latch.countDown();
		}	
	}
}

server端:

public class SimpleTimeServer implements Runnable{
	//维持服务线程的门闩
	private CountDownLatch latch;
	//异步socket服务通道
	private AsynchronousServerSocketChannel asynchronousServerSocketChannel;
	public static void main(String [] args){
		try{
			System.out.println("我是主线程: " + Thread.currentThread());
			new SimpleTimeServer(8088).run();
			System.out.println("监听线程已挂");
		} catch (IOException e) {
            e.printStackTrace();
        }
	}
	private SimpleTimeServer(int port) throws IOException{
		//开启异步socket服务
        asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();
        //绑定端口
        asynchronousServerSocketChannel.bind(new InetSocketAddress(port));
        System.out.println("simple time server start in " + port);
	}
	@Override
	public void run()
	{
		latch = new CountDownLatch(1);//阻塞当前线程防止服务端任务执行完退出,
		//在实际项目中,不需要启动独立的线程来处理asynchronousServerSocketChannel,这里仅仅是个demo。
		System.out.println("我是监听线程:" + Thread.currentThread());
		//异步socket服务接收请求,传递一个attach对象和实现了CompletionHandler的回调来处理AIO操作结果
		asynchronousServerSocketChannel.accept(this, new AcceptCompletionHandler());
		try{
			latch.await();
		}catch (InterruptedException e) {
            e.printStackTrace();
        }
	}
	//接收请求的结束动作处理类,当异步socket服务接收到一个请求时,会回调此handler,从而对收到的请求进行处理 
	//AsynchronousSocketChannel为处理结果 SimpleTimeServer为发起调用时传入的参数
	private class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, SimpleTimeServer>{
		@Override
		public void completed(AsynchronousSocketChannel channel, SimpleTimeServer attachment)
		{
			System.out.println("我是处理线程:" + Thread.currentThread());
			//循环监听,进行监听操作的是SimpleTimeServer运行的线程,这样做的目的是因为一个asynchronousServerSocketChannel
			//可以接收成千上万个客户端,所以当系统回调我们传入的CompletionHandler时,表示新的客户端已经接入成功,
			//所以继续调用accept接受其他客户端  如果处理不过来回用新的线程来接收其他接入
			attachment.asynchronousServerSocketChannel.accept(attachment, this);			
			ByteBuffer buffer = ByteBuffer.allocate(1024);
			//ByteBuffer dst :接受缓冲区,用于从异步的channel中读取数据包
            //A attachment :异步channel携带的附件,通知回调时作为参数传递使用。
            //CompletionHandler<Integer,? super A> handler 接收通知回调的业务handleer
			channel.read(buffer, buffer, new ReadCompletionHandler(channel));
		}
		@Override
		public void failed(Throwable exc, SimpleTimeServer attachment)
		{
			//接收请求失败,打印异常信息,将门闩减一,服务线程终止
			exc.printStackTrace();
			attachment.latch.countDown();
		}
		//读取数据的结束动作处理类,当系统将数据读取到buffer中,会回调此handler
		private class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {
			//将AsynchronousSocketChannel通过参数传递到ReadCompletionHandler中 当做成员变量用来读取包中消息和发送应答
			private AsynchronousSocketChannel channel;
	        ReadCompletionHandler(AsynchronousSocketChannel channel) {
	            this.channel = channel;
	        }
			@Override
			public void completed(Integer result, ByteBuffer attachment)
			{
				//首先对attachment进行flip操作,为后续从缓冲区读取数据做准备
				//根据缓冲区的可读字节创建byte数组,然后通过newString方法创建请求消息,对请求消息进行判断
				//如果是“query time order” 则获取当前系统的服务器时间,调用dowrite方法发送客户端。
				attachment.flip();
				byte[] body = new byte[result];//attachment.remaining()
				attachment.get(body);
				String req = new String(body, StandardCharsets.UTF_8);
				System.out.println("the time server received order: " + req);
				String currentTime = "query time order".equalsIgnoreCase(req) ? new Date(System.currentTimeMillis())
						.toString() : "BAD ORDER";
				doWrite(currentTime);
			}
			@Override
			public void failed(Throwable exc, ByteBuffer attachment)
			{
				try{//读取失败关闭通道
					channel.close();
				} catch (IOException e){			
					e.printStackTrace();
				}
			}	
			private void doWrite(String msg)
			{
				//将字符串解码为字节数组再调用AsynchronousSocketChannel的write方法
				if(msg != null){
					byte[] bytes = msg.getBytes(StandardCharsets.UTF_8);
					ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
					writeBuffer.put(bytes);
					writeBuffer.flip();
					channel.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>(){
						@Override
						public void completed(Integer result, ByteBuffer attachment)
						{	
							//如果没有发送完成就继续发送
							if(attachment.hasRemaining())
								channel.write(attachment, attachment, this);
						}
						@Override //可以对异常判断如果是I/O异常,就关闭链路,释放资源,如果是其他异常按照业务逻辑处理。
						public void failed(Throwable exc, ByteBuffer attachment)
						{
							try{
								channel.close();
							} catch (IOException e){
								e.printStackTrace();
							}
						}					
					});
				}
			}
		}
	}
}

AIO读取文件:AsynchronousFileChannel

第一种方式是调用返回值为Future的read()方法:这种方式中,read()接受一个ByteBuffer座位第一个参数,数据会被读取到ByteBuffer中。 第二个参数是开始读取数据的文件位置。read()方法会立刻返回,即使读操作没有完成。我们可以通过isDone()方法检查操作是否完成。

static void read1() throws IOException{
		AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
				Paths.get("E:/ModMudEngine/mt/trunk/logic/gsever/test/ser.ser"), StandardOpenOption.READ);
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		long position = 0;
		Future<Integer> operation = fileChannel.read(buffer, position);
		while (!operation.isDone());
		buffer.flip();
		byte[] data = new byte[buffer.limit()];
		buffer.get(data);
		System.out.println(new String(data));
		buffer.clear();
	}

第二种通过CompletionHandler读取数据

static void read2() throws IOException
	{
		AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
				Paths.get("E:/ModMudEngine/mt/trunk/logic/gsever/test/ser.ser"), StandardOpenOption.READ);
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		long position = 0;
		fileChannel.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
		    @Override
		    public void completed(Integer result, ByteBuffer attachment) {
		        System.out.println("result = " + result);
		        attachment.flip();
		        byte[] data = new byte[attachment.limit()];
		        attachment.get(data);
		        System.out.println(new String(data));
		        attachment.clear();		        
		    }		 
		    @Override
		    public void failed(Throwable exc, ByteBuffer attachment) {
		    	try
				{
					fileChannel.close();
				} catch (IOException e){					
					e.printStackTrace();
				}
		    }
		});
	}

通过Future写数据: 第二个参数是开始写入数据的文件位置

static void write() throws IOException{
		Path path = Paths.get("E:/ModMudEngine/mt/trunk/logic/gsever/test/copy.txt");
		AsynchronousFileChannel fileChannel = 
		    AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);	 
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		long position = 0;	 
		buffer.put("test data".getBytes());
		buffer.flip();		 
		Future<Integer> operation = fileChannel.write(buffer, position);
		buffer.clear(); 
		while(!operation.isDone());
		System.out.println("Write done");
	}

通过CompletionHandler写数据

	static void write2() throws IOException{
		Path path = Paths.get("E:/ModMudEngine/mt/trunk/logic/gsever/test/copy.txt");
		if(!Files.exists(path)){
		    Files.createFile(path);
		}
		AsynchronousFileChannel fileChannel = 
		    AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);	 
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		long position = 0; 
		buffer.put("test dataa".getBytes());
		buffer.flip(); 
		fileChannel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
		    @Override
		    public void completed(Integer result, ByteBuffer attachment) {
		        System.out.println("bytes written: " + result);
		    } 
		    @Override
		    public void failed(Throwable exc, ByteBuffer attachment) {
		        System.out.println("Write failed");
		        exc.printStackTrace();
		        try{
					fileChannel.close();
				} catch (IOException e)
				{					
					e.printStackTrace();
				}
		    }
		});
	}

close时如果有未完成的操作或继续启动操作会抛出异常。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值