端口绑定,大部分是在AbstractChannel的内部类AbstractUnsafe的bind()这个方法里面完成的。主要完成了两件事情:
1、doBind()最终调用到javaChannel.bind()调用jdk底层绑定端口。
2、pipeline的channelActive传播事件,最终会调用到HeadContext.readIfIsAutoRead()(这个方法的作用是把原来注册到selector上面的事件重新绑定为accept事件,这样子有新连接进来netty就会接受这个时间并交给netty处理)。
在AbstractBoostrap的doBind()这个方法的initAndRegister()调用后面,有一个doBind0(),一层一层进去,到达AbstractChannelHeadContext的这个方法:
private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
if (invokeHandler()) {
try {
((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} else {
bind(localAddress, promise);
}
}
点击HeadContext的实现,再进去一层就能看到AbstractUnsafe的bind的实现:
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
assertEventLoop();
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
// See: https://github.com/netty/netty/issues/576
if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
localAddress instanceof InetSocketAddress &&
!((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
!PlatformDependent.isWindows() && !PlatformDependent.isRoot()) {
// Warn a user about the fact that a non-root user can't receive a
// broadcast packet on *nix if the socket is bound on non-wildcard address.
logger.warn(
"A non-root user can't receive a broadcast packet if the socket " +
"is not bound to a wildcard address; binding to a non-wildcard " +
"address (" + localAddress + ") anyway as requested.");
}
boolean wasActive = isActive();
try {
doBind(localAddress);
} catch (Throwable t) {
safeSetFailure(promise, t);
closeIfClosed();
return;
}
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
safeSetSuccess(promise);
}
看NioServerSocketChannel的doBInd的实现:
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
就是调用jdk底层实现端口绑定了。
观察到AbstractUnsafe的doBind后这一段:
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
wasActive实在doBind()之前赋值,这段代码的意思是:如果doBInd之前isActive为false,之后isActive()为true,那么就传播事件。也就是,之前没有传播过,后面绑定端口完成并且成功之后,就开始传播。(正常情况下在doBind之前isActive为false)
一样的道理,我们层层进入fireChannelActive最后到达HeadContext的channelAcitve方法(因为传播事件是在pipeline里面完成,pipeline的第一个节点是HeadContext):
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
readIfIsAutoRead();
}
这里又做了两件事:
1、传播事件
2、把accept事件注册上去。
在传播事件这件事情上,我们一层一层点进去,在AbstractChannelContextHandler的这个方法里面:
private void invokeChannelActive() {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelActive(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelActive();
}
}
channelActive这个方法,找我们用户代码自己实现的channelActive,就能看到这个:
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("channelActive");
}
是不是很神奇!
在把accept事件注册上去这里,点击进去readIfIsAutoRead()这个方法,还是实现HeadContext的read方法,AbstractNioChannel的doBegainRead()方法:
@Override
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);
}
}
首先我们看final int interestOps = selectionKey.interestOps();这段代码出来什么?
在我们把jdk底层的channel注册到selector上的时候,我们并不关心什么事件,之前的文章讲过,所以这里的interestOps是0。
我们看看readInterestOp从哪里来。在这里我们首先明确的是,这里的read并不是指“读事件”,而是“读到的事件”,这个“读到的事件”有可能是读事件、写事件、accept事件。其实这个readInterestOp是从NioServerSocketChannel这个类的构造函数来的,之前我们可能有点印象,在创建NioServerSocketChannel的时候:
/**
* Create a new instance using the given {@link ServerSocketChannel}.
*/
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
也就是这个readInterestOp就是SelectionKey.OP_ACCEPT。
那么 (interestOps & readInterestOp) == 0这段代码是什么意思呢?先这么理解,如果之前没有注册readInterestOp这样事件,就为false。结合下面这一段:interestOps | readInterestOp,这里指前面已经有注册一些事件,在那些事件的基础上,把readInterestOp(在这里是accept事件)这个事件也加上去。结合整个下面一段代码:
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
如果之前没有注册过readInterestOp这样的事件,就把readInteresOp这样的事件注册进去(添加进去),并且之前的事件不受影响。
绑定成功之后就要告诉selector它需要关心accept事件,selector下次有这个事件就会交给netty处理。
我们再次来理解一下doBegainRead 的read,read就是可读了,读什么,在这里就是读accept事件,也就是新连接。
回顾一下netty服务端启动的几个过程:创建channel(创建jdk底层channel)、init初始化(最主要是添加连接器serverBootstrapAcceptor)、register(把第一步jdk底层创建channel注册到selector上面,并且把这个NioServerSocketChannel作为attachment添加进去),doBind(绑定端口,实现监听accept事件)