前言
本博客主要是介绍Netty在新连接接入后的相关处理
GitHub地址:https://github.com/RobertoHuang
新连接建立
新连接建立可以分为以下三个步骤
1.检测到有新的连接
2.将新的连接注册到Worker线程组
3.注册新连接的读事件
在Reactor线程模型详解博客中我们已经知道当服务端读取到IO事件(新连接接入事件)后,会调用processSelectedKey方法对事件进行处理,此处以新连接接入事件为例它最后会调用底层的unsafe进行read操作
public void read() {
assert eventLoop().inEventLoop();
final ChannelPipeline pipeline = pipeline();
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
do {
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
} while (allocHandle.continueReading());
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
pipeline.fireChannelRead(readBuf.get(i));
}
readBuf.clear();
pipeline.fireChannelReadComplete();
}
这里有两个主要的方法:
1.doReadMessages
2.pipeline.fireChannelRead
doReadMessages
protected int doReadMessages(List<Object> buf) throws Exception {
SocketChannel ch = javaChannel().accept();
try {
if (ch != null) {
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
// ...
}
return 0;
}
该方法主要作用是通过JDK底层的API获取到SocketChannel,然后包装成Netty自己的NioSocketChannel。NioSocketChannel与服务端启动时创建的NioServerSocketChannel最主要的区别在于它们关注的事件不同,NioSocketChannel的构造方法如下
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);
}
这里我们看到一个SelectionKey.OP_READ,说明这个Channel关心读事件而服务端的Channel关心ACCEPT事件。接下来调用父类AbstractNioChannel构造,后续过程与服务端启动流程一致此处不再赘述
pipeline.fireChannelRead
接着来看pipeline.fireChannelRead(readBuf.get(i))方法,关于Pipeline我们将在下一篇博客中详细介绍。我们知道客户端在启动的过程中会往Pipeline中添加一个ServerBootstrapAcceptor(连接处理器的东西),所以到这里服务端Channel对应的Pipeline的数据结构为:Hea⇋ServerBootstrapAcceptor⇋Tail,在调用pipeline.fireChannelRead时会依次触发这三个节点上的channelRead方法,接下来我们重点关注ServerBootstrapAcceptor的channelRead方法,代码如下
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);
}
}
首先获取我们之前实例化的NioSocketChannel,然后将我们设置的chlidHandler添加到NioSocketChannel对应的Pipeline中(这里的chlidHandler对应用户通过.childHandler()设置的Handler),代码执行到这里NioSocketChannel中Pipeline对应的数据结构为: head⇋ChannelInitializer⇋tail,接着设置NioSocketChannel对应的attr和option,然后进入到childGroup.register(child),这里的childGroup就是WorkerGroup,接下来我们进入NioEventLoopGroup的register方法
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}
这段代码和服务端启动的时候像BossGroup注册NioServerSocketChannel是类似的,通过next()方法获取到NioEventLoop然后将Channel注册到该NioEventLoop上(即将该Channel与NioEventLoop上的Selector进行绑定)。注册的逻辑最终是交给unsafe对象完成的,我们继续跟进unsafe的register方法,代码如下
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
//...
AbstractChannel.this.eventLoop = eventLoop;
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
//...
}
}
}
由于这里是在Boss线程中执行的IO操作所以不会是跟Worker线程是同一个线程,所以eventLoop.inEventLoop()返回false,最后会通过eventLoop.execute的方式去执行注册任务。在Reactor线程模型中我们讲到在调用execute的时候,如果是首次添加任务那这个NioEventLoop线程会被启动,所以从此Worker线程开始执行,接下来看下具体的注册逻辑
private void register0(ChannelPromise promise) {
try {
boolean firstRegistration = neverRegistered;
doRegister();
neverRegistered = false;
registered = true;
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
pipeline.fireChannelRegistered();
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
//...
}
}
和服务端启动过程一样,先是调用doRegister()执行真正的注册过程
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
return;
} catch (CancelledKeyException e) {
//...
}
}
}
该Channel绑定到NioEventLoop对应的Selector上去,后续该Channel的事件轮询、事件处理、异步Task执行都由此线程负责,绑定完Reactor线程之后调用pipeline.invokeHandlerAddedIfNeeded()代码如下
final void invokeHandlerAddedIfNeeded() {
assert channel.eventLoop().inEventLoop();
if (firstRegistration) {
firstRegistration = false;
callHandlerAddedForAllHandlers();
}
}
往下跟callHandlerAddedForAllHandlers方法
private void callHandlerAddedForAllHandlers() {
final PendingHandlerCallback pendingHandlerCallbackHead;
synchronized (this) {
assert !registered;
registered = true;
pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
this.pendingHandlerCallbackHead = null;
}
PendingHandlerCallback task = pendingHandlerCallbackHead;
while (task != null) {
task.execute();
task = task.next;
}
}
这里有个对象叫pendingHandlerCallbackHead,我们发现它是在callHandlerCallbackLater方法中被初始化的
private void callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added) {
assert !registered;
PendingHandlerCallback task = added ? new PendingHandlerAddedTask(ctx) : new PendingHandlerRemovedTask(ctx);
PendingHandlerCallback pending = pendingHandlerCallbackHead;
if (pending == null) {
pendingHandlerCallbackHead = task;
} else {
// Find the tail of the linked-list.
while (pending.next != null) {
pending = pending.next;
}
pending.next = task;
}
}
当我们在Channel注册到之前添加或删除Handler时,此时没有EventExecutor可执行HandlerAdd或HandlerRemove事件,所以Netty为此事件生成一个相应任务等注册完成后在调用执行任务。添加或删除任务可能有很多个,DefaultChannelPipeline使用一个链表存储,链表头部为先前的字段pendingHandlerCallbackHead
接下来我们继续分析task.execute方法, 它主要是完成NioSocketChannel对应的Pipeline的初始化
void execute() {
// ...
callHandlerAdded0(ctx);
// ...
}
通过上面对pendingHandlerCallbackHead的分析,这里肯定会调用ChannelInitializer的handlerAdded方法
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isRegistered()) {
initChannel(ctx);
}
}
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) {
try {
initChannel((C) ctx.channel());
} catch (Throwable cause) {
exceptionCaught(ctx, cause);
} finally {
remove(ctx);
}
return true;
}
return false;
}
ChannelInitializer的initChannel主要完成两个功能以下两个功能
首先调用initChannel((C) ctx.channel())进入用户自定义的代码完成Pipeline的初始化
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
}
})
然后在finally中调用remove方法将ChannelInitializer删除
private void remove(ChannelHandlerContext ctx) {
try {
ChannelPipeline pipeline = ctx.pipeline();
if (pipeline.context(this) != null) {
pipeline.remove(this);
}
} finally {
initMap.remove(ctx);
}
}
执行该方法前NioSocketChannel对应的Pipeline的数据结构为:head⇋ChannelInitializer⇋tail,执行该方法后ChannelInitializer被删除,NioSocketChannel对应的Pipeline的数据结构为:head⇋自定义的HandlerContext⇋tail
到目前为止我们完成了新连接的注册、pipeline的绑定,但是新连接注册的时候的感兴趣事件还是0还无法进行读写操作,新连接对读事件的绑定是在pipeline.fireChannelActive方法中完成的,它最后会调用到AbstractNioChannel的doBeginRead
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);
}
}
前面register0()方法的时候向selector注册的事件代码是0,而readInterestOp对应的事件代码是SelectionKey.OP_READ,所以本段代码的用处是将SelectionKey.OP_READ事件注册到Selector中去,fireChannelActive的执行逻辑在服务端启动过程中有详细描述,至此已完成客户端新连接接入的操作。下一篇博客将介绍Pipeline相关的源码解析