再来看看他是如何执行io任务和普通任务的,如何区分事件的呢,来看下面这段源码
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
这里定义了一个变量ioRatio我们假设他是80,然后进入else获取了当前时间记录一下然后执行io任务,运行到普通任务之前计算一下执行io任务用了多长的时间,这里假如运行io任务的时间是8s,
ioTime * (100 - ioRatio) / ioRatio就等于8s*(100-80)/80也就是2s,所以执行普通任务的时间为两秒钟,如果时间一到就会把它放入任务队列等到下次再来执行,所以ioRatio也就是一个控制执行普通任务时间的变量,这个变量也不是越大越好可以看到如果是100,那么就会走if会把io任务和普通任务执行完才结束,那假如普通任务执行时间很长,这个线程也就会一直阻塞在这里,那么下个io任务来了也就不会执行。
好了,下面再来看看他是如何划分事件的,进入processSelectedKeys();方法,他会一直调用到
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
try {
int readyOps = k.readyOps();
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
ch.unsafe().forceFlush();
}
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
这就是在这个方法里面进行的事件判断,这里可以看到read和accept事件是一起的,下面来试试看他会不会运行到这里,写一个客户端如下
public class TestClient {
public static void main(String[] args) throws InterruptedException {
new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler());
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.writeAndFlush("hello".getBytes(StandardCharsets.UTF_8));
}
});
}
})
.connect(new InetSocketAddress("localhost",9999)).sync();
}
}
然后在unsafe.read();上打一个断点,启动服务器和客户端会发现果然运行到了这里,并且readyOps等于16也就是accept事件,然后进入这个方法里面,我只显示关键代码
private final List<Object> readBuf = new ArrayList<Object>();
public void read() {
try {
try {
do {
int localRead = doReadMessages(readBuf);
}
}
for (int i = 0; i < size; i ++) {
readPending = false;
pipeline.fireChannelRead(readBuf.get(i));
}
}
他会在这个方法里面创建一个SocketChannel
protected int doReadMessages(List<Object> buf) throws Exception {
SocketChannel ch = SocketUtils.accept(javaChannel());
try {
if (ch != null) {
buf.add(new NioSocketChannel(this, ch));
return 1;
}
}
}
public static SocketChannel accept(final ServerSocketChannel serverSocketChannel) throws IOException {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<SocketChannel>() {
@Override
public SocketChannel run() throws IOException {
return serverSocketChannel.accept();
}
});
} catch (PrivilegedActionException e) {
throw (IOException) e.getCause();
}
}
在这里就会把连接建立完毕,然后在NioSocketChannel构造方法中把创出来的SocketChannel 赋值给他
然后就会运行到pipeline.fireChannelRead(readBuf.get(i));这是不是有点熟悉和前面的代码,在这个里面就会把连接事件交给我们的NioServerSocketChannel中的pipeline中的处理器来处理,而他中间有三个处理器,head、acceptor、tail,这三个,其实主要就是要交给acceptor处理器,下面我们来看看,中间调用链比较长,所以我直接切换到重点
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
child.pipeline().addLast(childHandler);
setChannelOptions(child, childOptions, logger);
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
try {
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});
} catch (Throwable t) {
forceClose(child, t);
}
}
这里Msg就是我们传进来的刚刚创建好的NioSocketChannel,前面就是给他设置参数,一直到childGroup.register(child),给这个NioSocketChannel上要设置一些东西比如selector来关注这个Channel将来要发生的事件,是不是发现和我们之前NioServerSocketChannel注册是差不多的
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
}
}
在这里会完成线程的切换操作,大家都知道netty的服务器初始化的时候会有两个NioEventLoopGroup,一个是专门负责连接的,一个是负责连接后进行Io操作的,所以在这里也就会让专人连接的把执行权转交给专门负责后续操作的线程,eventLoop.inEventLoop()这就是判断当前线程是不是负责后续NioEventLoopGroup中的线程,很明显不是,所以进入下面提交一个任务给其他线程执行。
下面进入register0(promise);方法
private void register0(ChannelPromise promise) {
try {
doRegister();
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
pipeline.fireChannelRegistered();
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
}
找到当中的doRegister();方法
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
}
}
}
这里也就是给当前的NioSocketChannel绑定一个selector来监听事件,但是注意此时还没有关注的任何事件
再来看pipeline.invokeHandlerAddedIfNeeded();这个就是会激活我们的NioSocketChannel上的处理器,也就是会回调到我们自己添加的LoggingHandler()处理器上
下面他就会进入pipeline.fireChannelActive();方法,,给我们的selector来关注事件
下面进入这个方法中,他会经过head、loggin、tail这三个处理器,最终在io.netty.channel.nio.AbstractNioChannel 上设置的
protected void doBeginRead() throws Exception {
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
大家可以看到此时我们的关注事件为0所以他会进入然后关注read事件,到这也就完成了连接,之后客户端也就会通过NioSocketChannel来向我们的服务器发送数据
能力有限,不足之处还请大家指出,希望能让大家对netty有一点提升