文章目录
开篇词
我希望大家能够带着问题来学习本篇文章,在这里我给大家提出两个问题。第一个Netty是在哪里检测到有新连接接入的。第二个新连接是怎样注册到Nioeventlooop线程当中的。
netty新连接接入处理逻辑:
检测新连接接入,是通过serverchannnel绑定的selector轮询到accept事件,基于jdk的channel创建出netty的NioSocketChannel。接着netty给客户端channel分配一个Nioeventloop,并且把channel注册到该Nioeventloop对应的selector上,然后我们就可以向这个channel对应的selector注册读事件。
源码分析
NioEventloop.java
// 处理新连接接入的逻辑:
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
final EventLoop eventLoop;
try {
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
// If the channel implementation throws an exception because there is no event loop, we ignore this
// because we are only trying to determine if ch is registered to this event loop and thus has authority
// to close ch.
return;
}
// Only close ch if ch is still registerd to this EventLoop. ch could have deregistered from the event loop
// and thus the SelectionKey could be cancelled as part of the deregistration process, but the channel is
// still healthy and should not be closed.
// See https://github.com/netty/netty/issues/5125
if (eventLoop != this || eventLoop == null) {
return;
}
// close the channel if the key is not valid anymore
unsafe.close(unsafe.voidPromise());
return;
}
try {
int readyOps = k.readyOps();
// We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
// the NIO JDK channel implementation may throw a NotYetConnectedException.
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
// See https://github.com/netty/netty/issues/924
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
// Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
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();
}
// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
// to a spin loop
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
这里是重点,进入此方法会执行到这个逻辑,判断是否有新连接过来,如果发现有op_accept
事件,就会执行下面的unsafe.read();
unsafe.read();
if (!ch.isOpen()) {
// Connection already closed - no need to handle write.
return;
}
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
接下来进入unsafe.read() 方法:
AbstractNioMessageChannel.java
内部类NioMessageUnsafe
@Override
public void read() {
// 判断当前是否在server的Nioeventloop中,否则报错
assert eventLoop().inEventLoop();
// server端的config
final ChannelConfig config = config();
// server端的pipeline
final ChannelPipeline pipeline = pipeline();
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.reset(config);
boolean closed = false;
Throwable exception = null;
try {
try {
do {
这里的doReadMessages是重点,这里会创建jdk底层的channel,
然后封装成netty的niochannel,将读到的连接放在readBuf这个临时容器中。
下面我们来跟进这个doReadMessages(readBuf)
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
allocHandle.incMessagesRead(localRead);
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
}
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false;
pipeline.fireChannelRead(readBuf.get(i));
}
readBuf.clear();
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
if (exception != null) {
closed = closeOnReadError(exception);
pipeline.fireExceptionCaught(exception);
}
if (closed) {
inputShutdown = true;
if (isOpen()) {
close(voidPromise());
}
}
}
NioServerSocketChannel.java
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
// 创建jdk底层的channel
SocketChannel ch = javaChannel().accept();
try {
if (ch != null) {
// 进行封装成NioSocketChannel,然后添加到临时容器当中,同时构造方法中还传递了服务端channel
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
logger.warn("Failed to create a new channel from an accepted socket.", t);
try {
ch.close();
} catch (Throwable t2) {
logger.warn("Failed to close a socket.", t2);
}
}
return 0;
}
所以在这里我们回答下第一个问题,是在哪里检测到新连接的,在Nioeventloop的processSelectedKey()处理io事件,当发现是accept事件后,就会调用unsafe.read()方法去创建niosocketchannel,这个channel是对jdk底层channel的封装。
NioSocketChannel的创建
NioSocketChannel构造方法
public NioSocketChannel(Channel parent, SocketChannel socket) {
// 代用父类AbstractNioByteChannel的构造方法
super(parent, socket);
// 创建config配置类,在里面会设置tcpnodelay为true,即小的数据包也会发出去,以降低延迟。
config = new NioSocketChannelConfig(this, socket.socket());
}
AbstractNioByteChannel.java
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
// SelectionKey.OP_READ表示后续将此channel绑定到selector上时,表示我对读事件
// 感兴趣,如果发生读事件的话,请交给我
super(parent, ch, SelectionKey.OP_READ);
}
继续跟进去
AbstractNioChannel.java
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
// 保存感兴趣事件标识
this.readInterestOp = readInterestOp;
try {
// 设置io模式为非阻塞模式
ch.configureBlocking(false);
} catch (IOException e) {
try {
ch.close();
} catch (IOException e2) {
if (logger.isWarnEnabled()) {
logger.warn(
"Failed to close a partially initialized socket.", e2);
}
}
throw new ChannelException("Failed to enter non-blocking mode.", e);
}
}
继续跟进supr(parent)方法
AbstractChannel.java
protected AbstractChannel(Channel parent) {
// 创建这三个基本的组件
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
重点 channel的分类
1:NioServerSocketChannel
2:NioSocketChannel
3:Unsafe:实现每种channel底层的协议
下面是channel的层级关系图
channel接口:定义网络读写最顶层的框架
AbstractChannel:对Channel的实现,所有channel功能抽象都可以这个抽象类来实现
可以看到,usafe id,pipeline,eventloop都是在这个类中进行抽象保存的
private final Channel parent;
private final ChannelId id;
private final Unsafe unsafe;
private final DefaultChannelPipeline pipeline;
private final VoidChannelPromise unsafeVoidPromise = new VoidChannelPromise(this, false);
private final CloseFuture closeFuture = new CloseFuture(this);
private volatile SocketAddress localAddress;
private volatile SocketAddress remoteAddress;
private volatile EventLoop eventLoop;
private volatile boolean registered;
/** Cache for the string representation of this channel */
private boolean strValActive;
AbstractNiochannel:负责nio相关部分,主要使用selector的方式来进行读写事件的监听。
不管是服务端channel还是客户端channel,注册到selector上时都会有一个selectionkey,就是保存在这个类的成员变量里面
SelectableChannel ch保存了服务端channel或者客户端channel底层的jdk channel
private final SelectableChannel ch;
这是一个读或accept事件
protected final int readInterestOp;
volatile SelectionKey selectionKey;
boolean readPending;
客户端channel:AbstractNioByteChannel,NioSocketchannel,NioByteUnsafe(是AbstractNioByteChannel)的一个成员变量
服务端channel:AbstractNioMessageChannel,NioServerSocketchannel,NioMessageUnsafe(是AbstractNioMessageChannel的一个成员变量)
而AbstractNioByteChannel与AbstractNioMessageChannel都继承AbstractNiochannel,说明都是通过selector方式去轮询io事件的,只不过关心事件不同。如下所示
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
super(parent, ch, SelectionKey.OP_READ);
}
unsafe:实现每种channel的读写抽象,对于读客户端channel的读是读取数据,而服务端channel的读是读一个连接。
服务端的读:
客户端的读:读的是io数据
新连接建立如何分配Eventloop和注册selector
这里有很重要的一点:在创建服务端的channel时,如果有新连接过来,那么就会调用unsafe.read()事件。接下来这段代码有两个很重要的逻辑,接下来进行分析
private final class NioMessageUnsafe extends AbstractNioUnsafe {
private final List<Object> readBuf = new ArrayList<Object>();
@Override
public void read() {
assert eventLoop().inEventLoop();
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.reset(config);
boolean closed = false;
Throwable exception = null;
try {
try {
do {
//将读到的连接放入临时容器readbuf中
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
allocHandle.incMessagesRead(localRead);
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
}
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false;
// 调用pipeline从head节点向下传播连接,这里地fireChannelread方法因为传递的是
// 连接,所以跟进去最后调用channelread()方法接口,最终会调用到
// ServerBootstrap类的内部类ServerBootstrapAcceptor的channelread方法
// 注意ServerBootstrapAcceptor在ServerBootstrap.init()方法中创建
//下面具体分析channelread()方法实现
pipeline.fireChannelRead(readBuf.get(i));
}
readBuf.clear();
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
if (exception != null) {
closed = closeOnReadError(exception);
pipeline.fireExceptionCaught(exception);
}
if (closed) {
inputShutdown = true;
if (isOpen()) {
close(voidPromise());
}
}
} finally {
// Check if there is a readPending which was not processed yet.
// This could be for two reasons:
// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
//
// See https://github.com/netty/netty/issues/2254
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
}
}
@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
child.pipeline().addLast(childHandler);
for (Entry<ChannelOption<?>, Object> e: childOptions) {
try {
if (!child.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {
logger.warn("Unknown channel option: " + e);
}
} catch (Throwable t) {
logger.warn("Failed to set a channel option: " + child, t);
}
}
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);
}
}
在这里主要做了三件事情 (childhandler与options与attrs都是我们用户自定义的代码)
到这里我们先梳理一下逻辑:
首先启动Nioeventloop,然后执行其中的processSelectedKey方法,在其中判断是否有新连接接入事件,若有则调用unsave.read(),在其中创建新连接,然后调用pipeline.firechannelread(readBuf.get(i)),因为是一个连接,所以最终会调用到ServerBootstrap.ServerBootstrapAcceptor的channelread方法。所做的事情就是上图所示要做的。
接下来我们就来聊一聊Nioeventloop。
NioSocketChannel读事件的注册