架构师养成记--18.NIO
有人叫new IO 我这里就叫Non-block IO
经典概念:
Buffer(缓冲区):之前直接通过流,现在提供一个buffer存放数据。
Channel:管道,包括ServerSocketChannel和SocketChannel
Selecor(选择器、多路复用器):SocketChannel注册到选择器上,轮询selector上的所有socketChannel ,根据通道的状态来执行相关操作。通道有链接状态、阻塞状态、可读状态、可写状态(Connect、Accept、Read、Write)。
Buffer:
和原来的IO的一个重要区别,面向流到面向缓冲区的转变。ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer
pos位置、limit长度、cap容量
朝pos装一个元素,pos增加1,get(Index)时pos时不发生变化的。所以在每批put后要flit()一下。put(value)和get()都会让pos发生递增。
IntBuffer.wrap(arr)可以将Int数组转换成IntBuffer,wrap方法影响limit;
put(arr)不影响limit;
buffer1.position(1);
buffer1.duplicate()复制;
Buffer1.remaining()可读长度;
Channel:
通道是双向的,SocketChannel、ServerSocketChannel,Selector不断轮询通道的状态,一旦有通道数据准备好了,selector就把这个通道拿出来(通过key可以拿出来)。
selector:
特别强调selector,理论上讲selector可以负责超大数据量的Channel,没有限制多少个客户端,因为只有一个线程在轮询Channel,也用epoll代替传统的select。
但是还是同步的,因为读写操作还是通过java代码实现的。
客户端:
1 import java.io.IOException; 2 import java.net.InetSocketAddress; 3 import java.nio.ByteBuffer; 4 import java.nio.channels.SelectionKey; 5 import java.nio.channels.Selector; 6 import java.nio.channels.ServerSocketChannel; 7 import java.nio.channels.SocketChannel; 8 import java.util.Iterator; 9 10 public class Server implements Runnable{ 11 //1 多路复用器(管理所有的通道) 12 private Selector seletor; 13 //2 建立缓冲区 14 private ByteBuffer readBuf = ByteBuffer.allocate(1024); 15 //3 16 private ByteBuffer writeBuf = ByteBuffer.allocate(1024); 17 public Server(int port){ 18 try { 19 //1 打开路复用器 20 this.seletor = Selector.open(); 21 //2 打开服务器通道 22 ServerSocketChannel ssc = ServerSocketChannel.open(); 23 //3 设置服务器通道为非阻塞模式 24 ssc.configureBlocking(false); 25 //4 绑定地址 26 ssc.bind(new InetSocketAddress(port)); 27 //5 把服务器通道注册到多路复用器上,并且监听阻塞事件 28 ssc.register(this.seletor, SelectionKey.OP_ACCEPT); 29 30 System.out.println("Server start, port :" + port); 31 32 } catch (IOException e) { 33 e.printStackTrace(); 34 } 35 } 36 37 @Override 38 public void run() { 39 while(true){ 40 try { 41 //1 必须要让多路复用器开始监听 42 this.seletor.select(); 43 //2 返回多路复用器已经选择的结果集 44 Iterator<SelectionKey> keys = this.seletor.selectedKeys().iterator(); 45 //3 进行遍历 46 while(keys.hasNext()){ 47 //4 获取一个选择的元素 48 SelectionKey key = keys.next(); 49 //5 直接从容器中移除就可以了 50 keys.remove(); 51 //6 如果是有效的 52 if(key.isValid()){ 53 //7 如果为阻塞状态 54 if(key.isAcceptable()){ 55 this.accept(key); 56 } 57 //8 如果为可读状态 58 if(key.isReadable()){ 59 this.read(key); 60 } 61 //9 写数据 62 if(key.isWritable()){ 63 //this.write(key); //ssc 64 } 65 } 66 67 } 68 } catch (IOException e) { 69 e.printStackTrace(); 70 } 71 } 72 } 73 74 private void write(SelectionKey key){ 75 //ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); 76 //ssc.register(this.seletor, SelectionKey.OP_WRITE); 77 } 78 79 private void read(SelectionKey key) { 80 try { 81 //1 清空缓冲区旧的数据 82 this.readBuf.clear(); 83 //2 获取之前注册的socket通道对象 84 SocketChannel sc = (SocketChannel) key.channel(); 85 //3 读取数据 86 int count = sc.read(this.readBuf); 87 //4 如果没有数据 88 if(count == -1){ 89 key.channel().close(); 90 key.cancel(); 91 return; 92 } 93 //5 有数据则进行读取 读取之前需要进行复位方法(把position 和limit进行复位) 94 this.readBuf.flip(); 95 //6 根据缓冲区的数据长度创建相应大小的byte数组,接收缓冲区的数据 96 byte[] bytes = new byte[this.readBuf.remaining()]; 97 //7 接收缓冲区数据 98 this.readBuf.get(bytes); 99 //8 打印结果 100 String body = new String(bytes).trim(); 101 System.out.println("Server : " + body); 102 103 // 9..可以写回给客户端数据 104 105 } catch (IOException e) { 106 e.printStackTrace(); 107 } 108 109 } 110 111 private void accept(SelectionKey key) { 112 try { 113 //1 获取服务通道 114 ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); 115 //2 执行阻塞方法 116 SocketChannel sc = ssc.accept(); 117 //3 设置阻塞模式 118 sc.configureBlocking(false); 119 //4 注册到多路复用器上,并设置读取标识 120 sc.register(this.seletor, SelectionKey.OP_READ); 121 } catch (IOException e) { 122 e.printStackTrace(); 123 } 124 } 125 126 public static void main(String[] args) { 127 128 new Thread(new Server(8765)).start();; 129 } 130 131 132 }
客户端:
1 import java.io.IOException; 2 import java.net.InetSocketAddress; 3 import java.nio.ByteBuffer; 4 import java.nio.channels.SocketChannel; 5 6 public class Client { 7 8 //需要一个Selector 9 public static void main(String[] args) { 10 11 //创建连接的地址 12 InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8765); 13 14 //声明连接通道 15 SocketChannel sc = null; 16 17 //建立缓冲区 18 ByteBuffer buf = ByteBuffer.allocate(1024); 19 20 try { 21 //打开通道 22 sc = SocketChannel.open(); 23 //进行连接 24 sc.connect(address); 25 26 while(true){ 27 //定义一个字节数组,然后使用系统录入功能: 28 byte[] bytes = new byte[1024]; 29 System.in.read(bytes); 30 31 //把数据放到缓冲区中 32 buf.put(bytes); 33 //对缓冲区进行复位 34 buf.flip(); 35 //写出数据 36 sc.write(buf); 37 //清空缓冲区数据 38 buf.clear(); 39 } 40 } catch (IOException e) { 41 e.printStackTrace(); 42 } finally { 43 if(sc != null){ 44 try { 45 sc.close(); 46 } catch (IOException e) { 47 e.printStackTrace(); 48 } 49 } 50 } 51 52 } 53 54 }