欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

NIO

程序员文章站 2022-04-23 23:43:21
...

介绍

在传统的Socket网络模型中,通常使用Blocking IO的形式进行读写,所谓的Blocking IO即BIO,实际上是一种阻塞IO模型。其模型可以概括为,在接收到另一端的数据之前,一直是阻塞状态以等待连接建立和数据接收,只有当连接建立完毕或接收到数据之后,才进一步进行操作,整个过程,线程一直被占用,浪费了大量的CPU资源。从而在JDK1.4之后,提出一种NIO(Non-bloking IO)模型,也是一种epoll模型,即非阻塞模型,用来解决阻塞状态线程占用问题。

原理

NIO模型,是一种非阻塞模型,是以Buffer和Channel作为载体和传输介质,将数据一次写入到Buffer中通过Channel进行传输,一端发送,另一端接收,无需阻塞等待接收。通过监听器的形式,当建立连接或是收到数据之后,监听器会收到来自操作系统底层连接建立消息,则将其转交给其他线程处理。
在传统的BIO模型中,是通过CPU来监听是否有连接建立,而在NIO模型中,通过操作系统底层DMA的支持,无需CPU一直等待,而是将任务转交给DMA处理。同时在NIO中,有一个Selector监听器,对已建立的channel进行轮询,一旦发现有新的事件,如读事件、写事件需要处理,则进一步通过CPU对其进行处理,这个过程可以通过新生成一个Thread对其进行处理。
由于DMA无法直接操作用户态的内存,具体指JVM内存,从而需要zorecopy操作,将数据在内核态缓存区与用户态缓冲区之间进行copy,效率不高。因此,NIO还提供了直接内存映射的方式,即直接通过操作内核态的内存来对数据进行操作,但直接操作内核态内存也带来了内存操作的安全问题。

本文通过介绍一个Buffer的例子和一个Channel的例子来说明其如何使用,并介绍非阻塞IO的使用方法,如果你想知道更多代码,请到我的Gitbub仓库中下载更多的源码,地址:https://github.com/a994947007/My-NIO-test-project

Buffer

上述简要阐述了NIO的原理,本文从Buffer开始详细介绍NIO的特性。
在NIO中,提供了如下几种Buffer。

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

本文以ByteBuffer为例,介绍其使用方法,其他类型Buffer亦是如此。
在Buffer中有4个关键的指标:

  • capacity :容量 ,表示整个Buffer的大小
  • limit:界限,表示缓冲区中可以操作的数据大小
  • position:位置,表示缓冲区中正在操作的数据大小
  • mark:标记,通过mark()记录当前position的位置,之后可以通过reset()进行复位。

几个关键的函数:
Buffer.put(byte[]) :向缓冲区写数据
Buffer.get(byte[]):从缓冲区读入数据到byte[]中
Buffer.hasRemaining():缓冲区中是否存在还可以操作的数据
Buffer.remaining():获取缓冲区中还可以操作的数据量
Buffer.allocate(int):开辟一个缓冲区

String str = "abc";
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(str.getBytes());
buffer.flip();	//对postion指针进行复位,由于上面put操作后,position指针会变成最后读的位置。

byte [] buf = new byte[buf.limit()];
buffer.get(buf);
System.out.println(new String(bs));
buf.clear();

以上介绍了ByteBuffer的操作流程,其他类型的Buffer操作大同小异。

Channel

本文以FileChannel为例,介绍其使用方法:

文件copy:

		FileChannel inChannel = FileChannel.open(Paths.get("G:/Feb_21_235618.hsn"), StandardOpenOption.READ);
		FileChannel outChannel = FileChannel.open(Paths.get("G:/Feb_21_235618_3.hsn"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
		inChannel.transferTo(0, inChannel.size(), outChannel);		//连接两个Channel
		
		inChannel.close();
		outChannel.close();

直接使用内核态内存进行文件Copy:

		FileChannel inChannel = FileChannel.open(Paths.get("G:/Feb_21_235618.hsn"), StandardOpenOption.READ);		//第2个参数表示需要对Channel进行怎样的操作
		FileChannel outChannel = FileChannel.open(Paths.get("G:/Feb_21_235618_3.hsn"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
		
		MappedByteBuffer inMappedBuffer = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
		MappedByteBuffer outMapBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
	
		//直接对缓冲区进行数据的读写操作
		byte[] dst = new byte[inMappedBuffer.limit()];
		inMappedBuffer.get(dst);
		outMapBuffer.put(dst);
		inChannel.close();
		outChannel.close();
非阻塞IO(核心)

非阻塞IO也是NIO的核心内容,其能够合理利用CPU资源,提高系统的并发能力。
服务端:

	@Test
	public void server() throws Exception{
		ServerSocketChannel ssc = ServerSocketChannel.open();
		ssc.configureBlocking(false);	//非阻塞模式
		ssc.bind(new InetSocketAddress(9999));
		
		Selector selector = Selector.open();
		ssc.register(selector, SelectionKey.OP_ACCEPT);	//将当前通道注册到选择器
		
		while(selector.select() > 0){		//轮询式的获取选择器上已经准备就绪的事件
			Set<SelectionKey> set = selector.selectedKeys();
			Iterator<SelectionKey> it = set.iterator();
			while(it.hasNext()){
				SelectionKey selectionKey = it.next();
				if(selectionKey.isAcceptable()){		//接收连接就绪,则获取连接
					SocketChannel sc = ssc.accept();
					sc.configureBlocking(false);		//将连接切换成非阻塞模式
					sc.register(selector, SelectionKey.OP_READ);
				}else if(selectionKey.isReadable()){
					ByteBuffer buf = ByteBuffer.allocate(1024);
					SocketChannel sc = (SocketChannel) selectionKey.channel();
					int len = 0;
					while((len = sc.read(buf)) > 0){
						buf.flip();
						System.out.println(new String(buf.array(),0,len));
						buf.clear();
					}
				}
				//取消选择键
				it.remove();
			}
		}
		selector.close();
		ssc.close();
	}

其核心步骤,需要一个Selector来进行事件监听,当收到accept事件之后,将Channel切换成非阻塞模式继续注册到Selector中,并通过,然后继续通过轮询,监听其readable事件,当收到事件后,对其进行处理,这里也可以通过一个新的线程进行处理,本文未做扩展,有兴趣的同学可以去我的Github下载源码,查看其具体操作。

客户端:

	@Test
	public void client() throws Exception{
		Scanner scan = new Scanner(System.in);
		SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1",9999));
		sc.configureBlocking(false);		//设置成非阻塞模式
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		while(scan.hasNext()){
			String str = scan.next();
			buf.put(str.getBytes());
			buf.flip();
			sc.write(buf);
			buf.clear();
		}
		
		scan.close();
		sc.close();
	}

总结

本文简要介绍了NIO的原理,介绍了ByteBuffer、FileChannel和非阻塞ServerSocketChannel的使用方法,其核心为非阻塞IO的使用。
更多内容可以在参考文献中查找,或在我的Github下,下载相关源码进行练习。

参考文献

NIO教程 http://ifeve.com/java-nio-all/
JAVA IO 以及 NIO 理解 https://www.cnblogs.com/hapjin/p/5736188.html
https://github.com/a994947007/My-NIO-test-project

相关标签: NIO 非阻塞IO