BIO/NIO的区别:
1.没有数据缓冲区,只有一个流,I/O性能存在问题;(NIO增加了数据缓冲区)2.没有C/C++的Channel(双管道,同时写入写出,或者一个写入,同时另一个写出)概念,只有输入和输出流(单管道),(NIO改善成了Channel)3.BIO因为是同步的,会导致通信被长时间阻塞(同步非阻塞,异步非阻塞)4.多路复用机制 Selector
BIO通信模型,为什么会很多人诟病:1.4版本之前
Client发送请求到Server,Server中的Acceptor分发给线程进行链路处理,线程输出流返回数据给Client,
当这种1on1的任务不断增多时,会造成线程溢出服务器宕机。
1.5以后出现了NIO(包含1.5)。
NIO核心组成部分:
1.缓冲区buffer对象(NIO做出的重大改变,增大了IO吞吐性能)读写需要转换读写模式 flip();position代表位置变化
size
position
limit (limit<=size)
传统的IO面向流操作都没有提供Buffer,而NIO是面向Buffer写到Channel
Channel
而且可以同时读写,速度快效率高,使用ServerSocketChannel 服务类配合高速通道Channel()
Selector
多路复用注册器,通过register(select SelectionKey....)注册到selector上
不断的轮询每个客户端注册到上面的Channel,只要客户端发生读写,就会被Selector轮询到,
通过SelectionKey可以获取这些就绪通道的集合,进行IO操作
通过Select select() 将上述四种状态准备好的通道 返回int值
JDK使用了epoll()取代了传统的select实现,没有最大句柄的1024/2048的限制,
只需要一个线程负责Selector轮询,就可以完成成千上万的客户端接入。
这篇文章对select、poll、epoll的实现做了详细总结,不再赘述。
做了一个简单的EchoDemo,代码如下
package nio; 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.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; public class NIOServer { private static ServerSocketChannel server; private static int port=8080; //信息写入buffer ByteBuffer receiveBuffer=ByteBuffer.allocate(1024); //信息写出buffer ByteBuffer sendBuffer=ByteBuffer.allocate(1024); //多路复用器(注册器) private static Selector selector; //维护一个实际上是一个事件标签的集合 Map<SelectionKey, String> sessionMsg=new HashMap<SelectionKey, String>(); public NIOServer(int port) throws IOException{ this.port=port; server =ServerSocketChannel.open(); server.socket().bind(new InetSocketAddress(port)); server.configureBlocking(false); selector= Selector.open(); server.register(selector, SelectionKey.OP_ACCEPT); System.out.println("NIO Service has started," + "and been listening the port:"+this.port); } //监听请求方法模块 public void listener() throws IOException{ while(true){ //到这里就是通过selector来看有没有注册事件 int events=selector.select(); if(events<=0){ //继续轮询 //NIO的内部机制就是不断轮询注册到selector的多个channel continue; } //有事件 Set<SelectionKey> keys=selector.selectedKeys(); Iterator<SelectionKey> iterator=keys.iterator(); while(iterator.hasNext()){ //处理客户端事件 process(iterator.next()); //移除集合里面的事件 iterator.remove(); } } } //详细的处理客户端事件 private void process(SelectionKey key) { SocketChannel clients=null; try { if(key.isValid() && key.isAcceptable()){ clients=server.accept(); clients.configureBlocking(false); clients.register(selector, SelectionKey.OP_READ); //读取客户端消息 }else if(key.isValid() && key.isReadable()){ //读到缓冲区 receiveBuffer.clear(); clients=(SocketChannel)key.channel(); int len=clients.read(receiveBuffer); if(len>0){ String msg=new String (receiveBuffer.array(),0,len); sessionMsg.put(key, msg); System.out.println("收到从客户端发送的消息:"+msg); //告诉我们的复用器,下次可以写数据 clients.register(selector,SelectionKey.OP_WRITE); } }else if(key.isValid() && key.isWritable()){ if(!sessionMsg.containsKey(key)){ return; } //服务会客户端消息 clients=(SocketChannel)key.channel(); sendBuffer.clear(); sendBuffer.put(new String(sessionMsg.get(key)+" request has already processed").getBytes()); //这个实际上设置读取位 sendBuffer.flip(); clients.write(sendBuffer); clients.register(selector, SelectionKey.OP_READ); } } catch (IOException e) { //读取key的事件的时候 遇到客户端异常下线 不会引起异常 try { key.cancel(); clients.socket().close(); clients.close(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } e.printStackTrace(); } } public static void main(String[] args){ try { new NIOServer(port).listener(); } catch (IOException e) { e.printStackTrace(); } } }
package nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.util.Iterator; import java.util.Scanner; import java.util.Set; public class NIOClient { SocketChannel client=null; InetSocketAddress serverAddress=new InetSocketAddress("localhost", 8080); Selector selector=null; ByteBuffer receiveBuffer=ByteBuffer.allocate(1024); ByteBuffer sendBuffer=ByteBuffer.allocate(1024); public NIOClient() throws IOException{ //修路 client=SocketChannel.open(); client.configureBlocking(false); client.connect(serverAddress); selector=Selector.open(); //向selector注册链接事件 client.register(selector, SelectionKey.OP_CONNECT); } public void session() throws IOException{ if(client.isConnectionPending()){ client.finishConnect(); client.register(selector, SelectionKey.OP_WRITE); System.out.println("Already connected to the server,please write some message..."); } Scanner scan=new Scanner(System.in); while (scan.hasNextLine()) { String name=scan.nextLine(); if("".equals(name)){ continue; } if("finish".equals(name)){ System.exit(0); } process(name); } } private void process(String name) throws IOException{ boolean waitHelp=true; Iterator<SelectionKey> iterator =null; Set<SelectionKey> keys=null; // try{ try { while(waitHelp){ //如果没有继续轮询,和read()类似 int readys=selector.select(); if(readys == 0){ continue; } //得到SelectionKey并迭代一下 keys=selector.selectedKeys(); iterator=keys.iterator(); while(iterator.hasNext()){ SelectionKey key=iterator.next(); //首先要判断是否有效和可写,代表Client想Server发送信息 if(key.isValid() && key.isWritable()){ sendBuffer.clear(); sendBuffer.put(name.getBytes()); // 在一系列通道读取或放置 操作之后,调用此方法为一系列通道写入或相对获取 操作做好准备。例如: jdk1.6 sendBuffer.flip(); client.write(sendBuffer); client.register(selector, SelectionKey.OP_READ); }else if(key.isValid() && key.isReadable()){ receiveBuffer.clear(); int len=client.read(receiveBuffer); if(len>0){ receiveBuffer.flip(); System.out.println("服务器反馈的消息:"+new String(receiveBuffer.array(),0,len)); client.register(selector, SelectionKey.OP_WRITE); waitHelp=false; } } //检查完之后,client走开 iterator.remove(); } } } catch(IOException e){ ((SelectionKey)keys).cancel(); client.socket().close(); client.close(); return; } } /** * ByteBuffer 转换 String * @param buffer * @return */ public static String getString(ByteBuffer buffer) { Charset charset = null; CharsetDecoder decoder = null; CharBuffer charBuffer = null; try { charset = Charset.forName("UTF-8"); decoder = charset.newDecoder(); // charBuffer = decoder.decode(buffer);//用这个的话,只能输出来一次结果,第二次显示为空 charBuffer = decoder.decode(buffer.asReadOnlyBuffer()); return charBuffer.toString(); } catch (Exception ex) { ex.printStackTrace(); return ""; } } public static void main(String[] args) throws IOException { new NIOClient().session(); } }