网易视频云:从0到1学系统通信——BIO,NIO,AIO的理解(一)

网易视频云是网易公司旗下的视频云服务产品,以Paas服务模式,向开发者提供音视频编解码SDK和开放API,助力APP接入音视频功能。现在,网易视频云的技术专家给大家分享一篇技术性文章:从0到1学系统通信——BIO,NIO,AIO的理解。

在介绍BIO(Blocking IO),NIO(Non-Blocking IO),AIO(Asynchronous IO)这几个概念之前,我们先明确这是系统IO的概念,不特定于某种语言。在介绍之前,我们先花点时间弄清楚几个基本的概念,同步,异步,阻塞,非阻塞。如果已经掌握的可以跳过。


编号 名词 解释 举例
1 同步 指的是用户进程触发IO操作并等待(阻塞)或者轮询(非阻塞)的去查看IO操作是否就绪 烧水的时候,一直等待水烧开,别的事情都不做。
2 异步 异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知(异步的特点就是通知 烧水之后,可以切菜,打扫卫生,等水烧开了发出声音通知我们再去关火。(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS)
3 阻塞 所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待 状态, 直到有东西可读或者可写为止 去公交站充值,发现这个时候,充值员不在(可能上厕所去了),然后我们就在这里等待,一直等到充值员回来为止。(当然现实社会,可不是这样,但是在计算机里确实如此。)
4 非阻塞 非阻塞状态下, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待, 银行里取款办业务时,领取一张小票,领取完后我们自己可以玩玩手机,或者与别人聊聊天,当轮我们时,银行的喇叭会通知,这时候我们就可以去了。

由以上定义可知:
1,同步和异步是针对应用程序和内核的交互而言的。 
2,阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。 

下面我们再来理解组合方式的几种IO类型 

BIO同步阻塞IO

从程序上来说,就是当发起IO的读/写的操作时,均为阻塞方式,只有当读到了流或将流写入操作系统后,才会释放资源。

在这个模型中,用户级别的应用程序执行一个系统调用,这会导致应用程序阻塞。这意味着应用程序会一直阻塞,直到系统调用完成为止(数据传输完成或发生错误)。调用应用程序处于一种不再消费 CPU 而只是简单等待响应的状态。

以read位例,应用程序(application)为了执行这个read操作,会调用相应的一个system call,将系统控制权交给kernel,然后就进行等待(这其实就是被阻塞了)。kernel开始执行这个system call,执行完毕后会向应用程序返回响应,应用程序得到响应后,就不再阻塞,并进行后面的工作。


NIO同步非阻塞IO

NIO是基于事件驱动思想的,实现上通常采用Reactor模式,从程序角度而言,当发起IO的读或写操作时,是非阻塞的;当socket有流可读或可写入socket时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。对于网络IO而言,主要有连接建立、流读取及流写入三种事件、linux2.6以后的版本使用epoll方式实现NIO。

select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。然而必须指出的是因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom),所以某些场景下性能可能还要更差一些。但是,用select/epoll的优势在于它可以同时处理多个connection。进一步了解select,poll ,epoll 原理


AIO异步IO方式

AIO为异步IO方式,同样基于事件驱动思想,实现上通常采用Proactor模式。从程序的角度而言,与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。较之NIO而言,AIO一方面简化了程序的编写,流的读取和写入都由操作系统来代替完成;另一方面省去了NIO中程序要遍历事件通知队列(selector)的代价。


以上几种通信模型,广泛应用于各个通信框架中,jdk1.6及之前的版本都只实现BIO 和 NIO直到jdk1.7开始支持AIO,即NIO 2.0。因此目前很多JAVA项目都只停留在NIO,还没有使用AIO。本章最后再介绍一下JAVAAIO的使用。


异步channel API提供了两种方式监控/控制异步操作(connect,accept, read,write等)。

1.返回java.util.concurrent.Future对象, 检查Future的状态可以得到操作是否完成还是失败,还是进行中, future.get阻塞当前进程。
2.为操作提供一个回调参数java.nio.channels.CompletionHandler,这个回调类包含completed,failed两个方法。

channel的每个I/O操作都为这两种方式提供了相应的方法, 你可以根据自己的需要选择合适的方式编程。

下面以一个简单的例子演示如何使用异步I/O。 客户端连接到服务器后服务器就发送一个当前的时间字符串给客户端。 客户端毋须发送请求。

服务端代码:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class Server {
	private static Charset charset = Charset.forName("US-ASCII");
        private static CharsetEncoder encoder = charset.newEncoder();
	
	public static void main(String[] args) throws Exception {
		AsynchronousChannelGroup group =              AsynchronousChannelGroup.withThreadPool(Executors.newFixedThreadPool(4));
		AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(group).bind(new InetSocketAddress("0.0.0.0", 8013));
		server.accept(null, new CompletionHandler() {
			@Override
			public void completed(AsynchronousSocketChannel result, Void attachment) {
				server.accept(null, this); // 接受下一个连接
				try {
					 String now = new Date().toString();
					 ByteBuffer buffer = encoder.encode(CharBuffer.wrap(now + "\r\n"));
					 Future f = result.write(buffer);//第一种方法
					 f.get();//第一种方法,阻塞住线程
					 System.out.println("sent to client: " + now);
					 result.close();
				} catch  (IOException | InterruptedException | ExecutionException e) {
					 e.printStackTrace();
				}
			}
			@Override
			public void failed(Throwable exc, Void attachment) {
				exc.printStackTrace();
			}
		});
		group.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
	}
}

客户端代码:

public class Client {

	public static void main(String[] args) throws Exception {
		AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
		Future future = client.connect(new InetSocketAddress("127.0.0.1", 8013));
		future.get();
		
		ByteBuffer buffer = ByteBuffer.allocate(100);
		client.read(buffer, null, new CompletionHandler() { //第二种方法,采用回到函数的形式
			@Override
			public void completed(Integer result, Void attachment) {
				System.out.println("client received: " + new String(buffer.array()));
				
			}
			@Override
			public void failed(Throwable exc, Void attachment) {
				exc.printStackTrace();
				try {
					client.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
								
			}
		});
		
		Thread.sleep(10000);
	}
}
通过实例可以看到,AIO的接口非常简单,使用起来很方便。
本章,我们介绍了BIO,NIO,AIO的概念,特别介绍了JAVA AIO的使用方法。在掌握基本概念和原理的基础上,为将来的学习打下来基础,下一章将介绍一下通信框架中使用比较多的两种经典设计模式Reactor模式和Proactor模式。
    
    
    
    

你可能感兴趣的:(云计算,通信技术,视频云,网易视频云)