前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java NIO原理和应用

java NIO原理和应用

作者头像
actionzhang
发布2022-11-30 17:03:02
2610
发布2022-11-30 17:03:02
举报
文章被收录于专栏:action的经验之路

之前做的一个项目,先开始用的是BIO(即阻塞式IO),然后因为一些性能问题,然后用NIO(即非阻塞式IO)替换了BIO。

我们先说说BIO有什么缺点为什么要使用NIO:

以java中TCP为例来讲解:

我们知道,在客户端java调用connect方法会阻塞,调用read的时候也会阻塞也就是读不到就一直阻塞在那里,而服务器端呢,调用accept()方法会阻塞,调用read方法也会阻塞这样的话,会对性能造成很大的影响。

接下来我们讲解NIO的原理,还是以TCP为例,现在很多框架中都在用NIO像是mina框架,以及java中RMI调用等等都会用到NIO,如果堆NIO原理不清楚的话,就很容易造成性能,上的问题:

1.NIO中主要的概念:

        第一个概念就是Channel,即通道,在BIO中是通过输入输出流来交互的,但是这些流是单向的,不能双向流通,那么在NIO中用Channel代替了流,作为通信,那么Channel是双向的,也就是数据流动的方式是双向的,也就服务器或者是客户端,谁想读取数据,就从channel中读取,谁想写入数据就向channel中写入数据。现在把流替换成管道就能实现NIO了吗,当然不可以,所以设计到了,第二个概念。

        第二个主要概念就是Selector,NIO中就是通过这个概念来解决了阻塞的问题,在讲这个概念的时候我们得先,知道NIO的一点工作方式,NIO之所以是非阻塞的是因为,NIO是事件驱动的,而不是BIO中,通过阻塞实现的,这么说有点不清楚,那么我们举例说明,先讲什么是事件驱动,例如你没话费了,而且手机坏了不能接收短信,同时你也不能上网,那么你必须跑去营业厅查看,是不是欠费了,而如果营业厅没开门,你就回家去干其他的事。而阻塞是什么呢,就是如果营业厅不开门,你一直等在营业厅门口,知道开门查完你你才回家去,当然在营业厅的时候你不能干任何事情。那么我的NIO就是需要你不断的去看营业厅是否开门了,就是多跑几趟。这时问题出现了,那如果你回家了,这时候营业厅门开了,但是你没去,那岂不是就错过了。而NIO对于这个问题的解决方法是这样的,如果你和营业厅的人很熟,那么可能有人会把要是放在一个容器中说,你要是想查话费就去这个容器中取钥匙吧,假设这个容器是个盒子,那么你如果错过了的话,你就可以去盒子中取钥匙然后开门查看。那么Selector其实就相当于这个容器,而事件就相当于是钥匙,每次如果说想查看没有数据需要读取,那么就遍历这个盒子,如果盒子中有代表读事件的钥匙,那么就去管道当中读取,因为在通信过程中有好多事件,像是读(read),写(write),连接(connect),接收连接(accept),那么就相当于有四种钥匙,看到不同的时间去执行,不同的操作。那么这个时候新的问题又来了,假如你的手机是移动的,而且放钥匙的盒子在一个公共的地方,也就是说,移动的人能够往这个盒子中放钥匙,联通的人也能往这个盒子中放钥匙,那么你假如拿钥匙的时候,拿了联通的钥匙,然后你还去开联通的门,这时候你是不是就进错房间了,房间就相当于是channel,selector是公共的,你拿到的别的连接的时间,那么你岂不是就进错channel了,另一中情况是你不知道,拿着该时间,往哪个通道里写数据或者是读数据,这时候第三个重要的概念,就出现了。

       第三个主要概念就是register注册,就是将selector注册给通道,也就是每一个通道一个selector,当然某些时候selector是可以被channel公用的,但是建议一个channel注册一个selector,这样的话在用你的channel的时候,就获取不到别的channel的事件了,也就是移动和联通每个都有一个盒子,你去不同地方拿钥匙,就会去到不同的房间。但是这样还是会有问题,你如果只是想去查手机话费,但是移动的盒子里放着两把,钥匙一把是移动营业厅的前门钥匙,一把是移动营业厅的后门钥匙,但是你指向从前门进,不想从后门进,也就是说,你对后门那把钥匙不感兴趣,我们人当然是不去拿他就好了,但是在程序中没这么智能,也就是在程序中,必须得看看这个钥匙是不是前门的,但是就是看这个动作在程序中也是耗时的,这时候为了解决这个问题,又出现第四个概念。

      第四个主要概念,也可以说是一种规范,不是强制的,就是设置selector中的时间类型,如果说selector中只对读感兴趣,那么其他类型的时间一旦到达的话,会被selector忽视也就是不会,把时间放入selector,也就是你告诉营业厅的人,不要把后门钥匙放在盒子里。

2.工作原理:

     经过以上的四个概念的阐述,我们队工作原理已经很清楚了,就是一个通信节点,先建立一个存储事件的selector,然后你还得注册感兴趣的时间,不然任何事件不会被放入,selector中,然后就是获取管道,因为管道总是需要通信双方有一方建立,建立通道后,然后给出你要连接的IP和端口,然后将IP和端口绑定在管道上,那么这个管道,会根据IP和port寻找你要通信的目标机,那么对方肯定会在一个已有的管道上,监听这个请求,如果监听到,那么就相当于管道,就对接好了,然后往管道上注册selector,管道双方的节点,都需要注册自己的selector,这样就可以通信了。一个节点,去查询selector,查询到事件后,然后响应时间,或是往通道里写数据,或是从通道里读数据,这样就可以通信了。基本原理是这样,但是在具体实施的时候还有一些细节问题需要搞懂,还有就是TCP连接的话,在客户端与服务器端是有点区别的,因为管道分为普通管道和服务器管道,但是两者其实没大区别,就是服务器管道主要是用来监听连接的,主要对accept事件,感兴趣,而普通管道主要是对读写事件感兴趣。为了帮助大家更好的理解,下面是我写个服务器代码和客户端代码,大家可以参考,基本就是一个客户端连接,那么在服务器端就建立一个线程进行处理。

服务器代码:

代码语言:javascript
复制
package com.test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NIOServer {
	 //服务端用于监听的selector
     private Selector selector;
     
     public void initServer(int port) throws IOException{
    	 //获取一个ServerSocket通道
    	 ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
         //设置为非阻塞的
    	 serverSocketChannel.configureBlocking(false);
    	 //绑定端口
    	 serverSocketChannel.socket().bind(new InetSocketAddress(port));
    	 //获取一个监听器
    	 this.selector=Selector.open();
    	 //把channal于selector绑定
    	 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
     }
     static class TCPservice implements Runnable{
        
    	private Selector serviceSelector;
    	
    	private SocketChannel serviceChannel;
    	
    	public TCPservice(SocketChannel serviceChannel){
    		this.serviceChannel=serviceChannel;
    	}
    	private void init() throws IOException{
    		serviceSelector=Selector.open();
    		//注册读事件,绑定
    		serviceChannel.register(serviceSelector, SelectionKey.OP_READ);
    	}
    	 
		@Override
		public void run() {
			try {
				init();
				//轮询读事件
				int count=10;
				while(count>0){
					count--;
					//先写一条数据
					serviceChannel.write(ByteBuffer.wrap(new String("Hello client").getBytes()));
				    serviceSelector.select();
				    Iterator<SelectionKey> it=serviceSelector.selectedKeys().iterator();
				    while(it.hasNext()){
				    	SelectionKey key=it.next();
				    	it.remove();
				    	if(key.isReadable()){
				    		 ByteBuffer buffer = ByteBuffer.allocate(10);
				    		 serviceChannel.read(buffer);
				    		 String msg=new String(buffer.array());
				    		 System.out.println("服务器收到信息:  "+msg);
				    		 serviceChannel.write(ByteBuffer.wrap("ServerAck".getBytes()));
				    	}
				    }
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			
		}
    	 
     }
     //监听连接
     public void listen() throws IOException{
    	 System.out.println("服务端启动成功!");
    	 while(true){
    	    //轮询连接事件
    		selector.select(); 
    		//遍历连接事件
    		Iterator<SelectionKey> it=selector.selectedKeys().iterator();
    	    while(it.hasNext()){
    	    	SelectionKey key=it.next();
    	    	//以防重复处理
    	    	it.remove();
    	    	if(key.isAcceptable()){
    	    		//获取服务器通道
    	    		ServerSocketChannel serverSocketChannel=(ServerSocketChannel)key.channel();
    	    		//获取和客户端连接的通道
    	    		SocketChannel channel=serverSocketChannel.accept();
    	    		//设置成非阻塞
    	    		channel.configureBlocking(false);
    	    		//启动一个服务线程
    	    		System.out.println("开启一个服务线程");
    	    		Runnable service=new TCPservice(channel);
    	    	    new Thread(service).start();
    	    	}
    	    }
    	 }
     }
     
     public static void main(String[] args) throws IOException {
		NIOServer nioServer=new NIOServer();
		nioServer.initServer(9768);
		nioServer.listen();
	}
}

客户端代码:

代码语言:javascript
复制
package com.test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NIOClient {

	private Selector connectSelector;
	
	public void init(String ip,int port) throws IOException{
		//获取SocketChannel
		SocketChannel socketChannel=SocketChannel.open();
		//设置为非阻塞
		socketChannel.configureBlocking(false);
		//获取连接选择器
		connectSelector=Selector.open();
		//连接
		socketChannel.connect(new InetSocketAddress(ip, port));
		//注册连接事件
		socketChannel.register(connectSelector, SelectionKey.OP_CONNECT);
	}
	
	public void listen() throws IOException{
		while(true){
			connectSelector.select();
			Iterator<SelectionKey> it=connectSelector.selectedKeys().iterator();
			while(it.hasNext()){
				SelectionKey key=it.next();
				it.remove();
				if(key.isConnectable()){
					SocketChannel channal=(SocketChannel)key.channel();
					if(channal.isConnectionPending()){
						channal.finishConnect();
					}
					channal.configureBlocking(false);
					channal.write(ByteBuffer.wrap("Hello Server".getBytes()));
					channal.register(connectSelector, SelectionKey.OP_READ);
				}else if(key.isReadable()){
					// 服务器可读取消息:得到事件发生的Socket通道  
			        SocketChannel channel = (SocketChannel) key.channel();  
			        // 创建读取的缓冲区  
			        ByteBuffer buffer = ByteBuffer.allocate(10);  
			        channel.read(buffer);  
			        byte[] data = buffer.array();  
			        String msg = new String(data).trim();  
			        System.out.println("客户端收到信息:  "+msg);  
			        ByteBuffer outBuffer = ByteBuffer.wrap("client yes".getBytes());  
			        channel.write(outBuffer);// 将消息回送给客户端  
				}
			}
		}
	}
	public static void main(String[] args) throws IOException { 
        NIOClient nioClient=new NIOClient();
	    nioClient.init("127.0.0.1", 9768);
	    nioClient.listen();
	}

}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015-07-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云服务器
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档