之前做的一个项目,先开始用的是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事件,感兴趣,而普通管道主要是对读写事件感兴趣。为了帮助大家更好的理解,下面是我写个服务器代码和客户端代码,大家可以参考,基本就是一个客户端连接,那么在服务器端就建立一个线程进行处理。
服务器代码:
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(); } }
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(); } }
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/sxiaobei/article/details/47006647