Netty源码分析----注册监听事件
(*文章基于Netty4.1.22版本)
上篇服务启动的文章讲了3个步骤
- 创建Channel并设置非阻塞
- Channel绑定地址
- Channel注册Selector
但是其实,NIO还有一步是注册感兴趣的事件,在上一篇文章中,只是将感兴趣的事件存放到一个变量中,而没有进行注册,这里,我们看下注册的流程。
一开始我也没找到到底哪里进行注册的,后来先通过AbstractNioChannel的readInterestOp变量找到使用到的地方,发现doBeginRead方法用到了
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
看来这里是注册感兴趣事件的地方,再看下哪里使用了这个方法。
先看下AbstractChannel#register0方法的一部分代码
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
当channel已经打开且连接的时候,会有两个分支,第一个分支流程调用pipeline的fireChannelActive方法,首先会调用HeadContext的channelActive方法(具体原因看pipeline分析的文章),第二个是调用beginRead方法,先看下HeadContext的channelActive方法
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
readIfIsAutoRead();
}
private void readIfIsAutoRead() {
if (channel.config().isAutoRead()) {
channel.read();
}
}
如果autoRead打开了,那么会在连接打开和建立的时候默认调用Channel的read方法,然后最终会调用到AbstractUnsafe的beginRead方法(具体原因看pipeline分析的文章),刚说了另外一个分支流程的beginRead也是调用AbstractUnsafe的beginRead方法,看下其实现
public final void beginRead() {
//....
doBeginRead();
//....
}
doBeginRead就是上面说的注册感兴趣事件的地方。
总结一下:
在channelActive事件发生的时候,如果开启了autoRead,那么会自动去注册一个感兴趣的事件。
另外再channelReadComplete也是如此
注册OP_ACCEPT事件
初始化NioServerSocketChannel的代码如下
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
会将OP_ACCEPT设置到AbstractNioChannel的readInterestOp属性中,然后是怎么触发beginRead方法的呢?上面讲了触发pipeline的那几个方法的时候会调用beginRead,那么找一下哪里调用了,发现有两个地方,第一个是AbstractUnsafe类的register0方法中
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
这里会调用,但是在这个阶段isActive会为true吗?isActive方法如下
javaChannel().socket().isBound()
当socket绑定了端口之后,isBound会返回true,而bind的阶段,是在register之后的,此时并不符合要求,所以isActive会返回false,那么再看下AbstractUnsafe类的bind方法
//....
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
在doBind方法之后又调用了fireChannelActive方法,由于doBind已经调用过了,那么if的条件满足,这时候再注册OP_ACCEPT事件
注册OP_READ事件
NioServerSocketChannel是ServerSocketChannel的封装,而NioSocketChannel是SocketChannel的封装,OP_ACCEPT方法是在构造方法中设置的,同理,看一下构造方法
public NioSocketChannel(Channel parent, SocketChannel socket) {
super(parent, socket);
config = new NioSocketChannelConfig(this, socket.socket());
}
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
super(parent, ch, SelectionKey.OP_READ);
}
在NioSocketChannel父类的构造方法中设置了事件,接下来要看下是哪里开始调用了channelActive方法。
boss线程接收到请求后,会将Channel注册到worker线程中,这个注册过程和之前讲的注册一致,那么这种情况就会调用到abstractUnsafe的register0方法:
public boolean isActive() {
SocketChannel ch = javaChannel();
return ch.isOpen() && ch.isConnected();
}