Selector
一个Selector可以管理多个channel,我们可以创建一个Selector,然后创建channel并将channel注册到Selector:
Selector Selector=Selector.open();
channel.configureBlocking(false);
SelectionKey key= channel.register(selector,SelectionKey,OP_READ); //返回值是一个SelectionKey
从Selector类的源码中可以看出它里面维护了已注册的键集合、已就绪的键集合
public abstract class SelectorImpl extends AbstractSelector {
protected Set<SelectionKey> selectedKeys = new HashSet(); //已就绪的键集合。每个channel和Selector都对应一个SelectionKey,所以一个Selector中有一个SelectionKey的集合。
protected HashSet<SelectionKey> keys = new HashSet(); //已注册的键集合
private Set<SelectionKey> publicKeys;
private Set<SelectionKey> publicSelectedKeys;
... ...
}
SelectionKey: 它维护了Selector和channel的注册关系。从源码中可以看出SelectionKey中封装了channel、selector、channel中注册的事件集合、channel已经准备好的事件:
public class SelectionKeyImpl extends AbstractSelectionKey {
final SelChImpl channel;
public final SelectorImpl selector;
private int index;
private volatile int interestOps; //channel中注册的事件集合。 注意这里虽然是一个int值,但是可以通过与操作确定四个事件的存在与否
private int readyOps; //channel已经准备好的事件
所以:选择器维护注册过的通道的集合,并且这种注册关系都被封装在SelectionKey当中,又因为一个Selector管理着多个channel,所以Selector中封装了SelectionKey的集合,包括已注册的和注册并已就绪的。
Selector的select()方法返回的是已就绪的channel数量,它会阻塞直至有通道就绪才会返回。所以服务器只需要一个线程通过轮询并调用select方法监听是否有事件发生。如果有的话,直接迭代selectedKeys进行处理就行了:
private void listener() throws Exception {
while (true) {
int n = selector.select();
if (n == 0) {
continue;
}
Iterator<SelectionKey> ite = selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = ite.next();
//a connection was accepted by a ServerSocketChannel.
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel = server.accept(); //等待客户端连接
registerChannel(selector, channel, SelectionKey.OP_READ); //连接成功后像channel注册读事件
remoteClientNum++;
System.out.println("online client num="+remoteClientNum);
replyClient(channel);
}
//a channel is ready for reading
if (key.isReadable()) {
readDataFromSocket(key);
}
ite.remove();//must
}
}
}
而在NIO中的操作就是调用channel的read和write方法,这两个方法将数据读写到Buffer。
private void replyClient(SocketChannel channel) throws IOException {
byteBuffer.clear();
byteBuffer.put("hello client!\r\n".getBytes());
byteBuffer.flip();
channel.write(byteBuffer);
}
Buffer
flip(): NIO的Buffer机制中可以通过该方法来进行Buffer的读模式与写模式的切换,相当于一个Buffer实现了IO中的InputStream和OutputStream的功能。
所以是将Buffer中的数据写到channel和从channel读取数据到Buffer两个操作。其实write(Buffer)操作最终是将Buffer缓冲区数据的数据写到内存中,然后再通过网络传输,所以这里就分了Buffer和DirectBuffer等不同的Buffer,像DirectBuffer本身就是分配的堆外内存,所以数据直接放进去就行了,而普通的Buffer是堆内存储的,所以就需要多一个从堆内拷贝到堆外内存的过程:
static int read(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
if (var1.isReadOnly()) {
throw new IllegalArgumentException("Read-only buffer");
} else if (var1 instanceof DirectBuffer) {
return readIntoNativeBuffer(var0, var1, var2, var4); //直接调用的native方法将数据放到堆外内存
} else {
ByteBuffer var5 = Util.getTemporaryDirectBuffer(var1.remaining()); //先分配堆外内存
int var7;
try {
int var6 = readIntoNativeBuffer(var0, var5, var2, var4); //再将Buffer数据拷贝过去
var5.flip();
if (var6 > 0) {
var1.put(var5);
}
var7 = var6;
} finally {
Util.offerFirstTemporaryDirectBuffer(var5);
}
return var7;
}
}
网友评论