Reactor模式称为叫反应堆或则反应器。在网络编程 - BIO中,咱们了解到,大量的线程休眠致使资源浪费,在BIO中,经过Reactor来优化,下面举个简单的例子:
有个高级游戏乐园,里面有5个不一样游戏的玩法,玩家进来的时候,有一对一的工做人员给玩家讲解每一个玩法。
传统BIO模式是这样的:
A玩家进来,工做人员1接待A,工做人员1讲解完第一个游戏玩法,而后A玩家开始玩,此时,工做人员1就在等待A玩家玩完(好比br.readLine())。玩家玩第二个游戏的时候,工做人员1开始讲解第二个游戏玩法,而后A玩家继续玩,一直玩到第五个游戏。
若是还有玩家进来呢?那派出第二个工做人员,第三个工做人员。。。。。。
咱们能够看到,工做人员在玩家玩的时候,他是处于休息空闲的状态,并且玩家愈来愈多的时候,工做人员就须要愈来愈多。
Reactor模式:
工做人员1给玩家A讲解完了,他就去处理其余事情,而玩家开始玩游戏,当玩家准备玩下一个游戏的时候,他就呼叫工做人员,工做人员就过来说解下一个游戏玩法。原本一个工做人员只能服务一个玩家,可是经过这个模式,他能够同时服务多个玩家,在玩家A玩游戏的时候,他能够为其余玩家提供讲解服务。
在Reactor模式中,应用程序并不会调用某个方法直至完成,而是逆置了事件处理流程,具体事件处理程序向反应器注册一个事件处理器,等到事件来了,具体事件处理程序再处理相关事件。java
Buffer在NIO中,本质是一块内存,用于和NIO通道进行交互。咱们能够把数据从通道读取中出来,写入到Buffer,也能够把Buffer的数据读到出来,写到通道中。
在NIO中,java定义了IntBuffer、FloatBuffer、ByteBuffer等,咱们比较经常使用的是ByteBuffer。编程
Buffer有几个重要的属性:position、limit、capacity。segmentfault
下面经过一个简单的例子深刻了解一下这几个属性。数组
public static void main(String[] args) { ByteBuffer buffer = ByteBuffer.allocate(8); System.out.println("init:" + buffer); buffer.put((byte) 'a'); System.out.println("put-a:" + buffer); buffer.put((byte) 'b'); System.out.println("put-b:" + buffer); buffer.put((byte) 'c'); System.out.println("put-c:" + buffer); // 切换到读模式 buffer.flip(); System.out.println("flip:" + buffer); buffer.get(); System.out.println("get-a:" + buffer); buffer.get(); System.out.println("get-b:" + buffer); buffer.get(); }
输出结果以下:
初始化时,pos指向0,capacity和limit都等于指定大小8。
put-a时,pos+1,等于1,capacity和limit不变。
put-b时,pos+1,等于2,capacity和limit不变。
put-c时,pos+1,等于3,capacity和limit不变。
flip后,pos把值赋值给limit,并重置为0,capacity不变。此时,pos等于0,limit等于3,capacity等于8。
get-a时,pos+1,等于1,capacity和limit不变。
get-b时,pos+1,等于1,capacity和limit不变。网络
allocate方法,在上面例子中,咱们看到了ByteBuffer.allocate(8)的使用。优化
public static ByteBuffer allocate(int capacity) { if (capacity < 0) throw new IllegalArgumentException(); // lim也传capacity,因此两个刚开始是相等的 return new HeapByteBuffer(capacity, capacity); } HeapByteBuffer(int cap, int lim) { // 这边pos赋值为0,字节长度为cap super(-1, 0, lim, cap, new byte[cap], 0); } ByteBuffer(int mark, int pos, int lim, int cap, byte[] hb, int offset) { super(mark, pos, lim, cap); this.hb = hb; this.offset = offset; }
wrap方法,跟allocate方法均可以初始化buffer,不一样的是能够指定pos和limit,以及指定字节数组的初始值。this
public static ByteBuffer wrap(byte[] array) { return wrap(array, 0, array.length); } public static ByteBuffer wrap(byte[] array, int offset, int length) { try { // 传递字节数组,pos,偏移量length,用于计算limit return new HeapByteBuffer(array, offset, length); } catch (IllegalArgumentException x) { throw new IndexOutOfBoundsException(); } }
除了上面例子演示的,put(byte),还有如下这些。
从源码中看pos会加1的缘由:spa
public abstract ByteBuffer put(byte b); public ByteBuffer put(byte x) { hb[ix(nextPutIndex())] = x; return this; } final int nextPutIndex() { // package-private if (position >= limit) throw new BufferOverflowException(); // 这边加1 return position++; }
也能够把通道中的数据写入到buffer:操作系统
// 这边用read指的是把通道的数据读取出来,再写入buffer,read返回的是写入buffer的数据大小。 channel.read(buf)
从源码中也能够看出,把pos的值赋值给limit,并重置为0。线程
public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
除了上面例子演示的,get,还有如下这些
从源码中看pos会加1的缘由:
public abstract byte get(); public byte get() { return hb[ix(nextGetIndex())]; } final int nextGetIndex() { // package-private if (position >= limit) throw new BufferUnderflowException(); return position++; }
也能够把buffer的数据写入到通道中:
// 把buffer的数据读取出来,写入到channel中 channel.write(buf)
调用mark的时候,会把pos的值给mark,调用reset的时候,会把mark的值给pos。在实际过程当中,咱们在读操做的时候,先调用mark方法标记位置,好比此时为4,当咱们读到7的时候,再调用reset方法,此时又从新从4开始读。
public final Buffer mark() { mark = position; return this; } public final Buffer reset() { int m = mark; if (m < 0) throw new InvalidMarkException(); position = m; return this; }
从源码能够看出,rewind把pos置为0,因此就是从头开始读写。
clear方法,把pos置0,并重置limit为capacity,这个时候进行写的时候,就是从第一个位置开始写,若是原先有数据,就是要被覆盖,至关于清空了整个内存。
compact与clear不同的是,他会把pos和limit之间的数据,移到前面去,并设置pos的值,写的时候,会重新的位置开始写。好比pos为2,limit为4,他会把2-4之间的值移到0,再把pos设置为2,这样没读的数据,就不会被覆盖而消失消失。
public final Buffer rewind() { position = 0; mark = -1; return this; } public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; } public abstract ByteBuffer compact(); public ByteBuffer compact() { System.arraycopy(hb, ix(position()), hb, ix(0), remaining()); position(remaining()); limit(capacity()); discardMark(); return this; } public final int remaining() { return limit - position; }
Channel,通道,操做系统和应用程序之间的数据交互,就是经过通道来的。
选择器,把Channel和须要的事件注册到Selector上面,让Selector进行监听。这些事件包括如下几种:
// 读 public static final int OP_READ = 1 << 0; // 写 public static final int OP_WRITE = 1 << 2; // 请求链接 public static final int OP_CONNECT = 1 << 3; // 接收链接 public static final int OP_ACCEPT = 1 << 4;
当须要监听多个事件时,好比OP_ACCEPT和OP_CONNECT能够这样写SelectionKey.OP_ACCEPT | SelectionKey.OP_CONNECT
。