当服务端启动后,那么久可以做客户端连接的事情了。客户端的示例代码在Netty服务端启动源码解析那篇文章中已经有展示了,那么这里在回顾一下,代码如下:
public class NettyClient {
public static void main(String[] args) throws Exception {
//客户端需要一个事件循环组
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建客户端启动对象
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group) //设置线程组
.channel(NioSocketChannel.class) // 使用NioSocketChannel作为客户端的通道实现
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//加入处理器
socketChannel.pipeline().addLast(new NettyClientHandler());
}
});
System.out.println("netty client start。。");
//启动客户端去连接服务器端
ChannelFuture cf = bootstrap.connect("127.0.0.1", 9000).sync();
//对通道关闭进行监听
cf.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
在上面示例代码中的bootstrap.connect("127.0.0.1", 9000).sync()这行代码的作用就是去连接服务器的请求,当客户端的连接请求过来时,那么久可以开始处理连接请求了。
连接请求概览
关于处理客户端连接请求的方法,在Netty服务端启动源码解析那篇文章中所展示的那张Netty Reactor模型图中是有提到的,这里我们就不再展示了,在服务器注册完连接请求事件后,便具备了处理请求事件的能力,那张Netty Reactor模型图中可以看出当有事件来时,便会调用select方法来进行处理,当处理完之后便会调用io.netty.channel.nio.NioEventLoop#processSelectedKeys方法,来将客户端的SocketChannel注册到Selector上。这里我们看一下构件连接的核心时序图,如下:
上图对构建连接的程序链路,进行了更加细粒度的拆分,接下来的源码分析的大体逻辑,便是按照上图进行分析。
(回顾)注册事件
在讲Netty服务启动源码解析的时候,在讲到创建Channel的时候,便是调用channelFactory.newChannel()方法进行创建的,而在这个方法中所调用的便是当时在Netty服务端示例源码中调用io.netty.bootstrap.AbstractBootstrap#channel方法是传入的NioServerSocketChannel这个Class的无参构造函数。这里我们就不回顾channelFactory.newChannel()方法是怎么实现的了,只看下NioServerSocketChannel类的无参构造函数的代码,其实在客户端的逻辑是一样的,客户端NioSocketChannel创建代码如下:
public NioSocketChannel() {
this(DEFAULT_SELECTOR_PROVIDER);
}
public NioSocketChannel(SelectorProvider provider) {
this(newSocket(provider));
}
public NioSocketChannel(SocketChannel socket) {
this(null, socket);
}
public NioSocketChannel(Channel parent, SocketChannel socket) {
// SelectionKey.OP_ACCEPT:连接事件
super(parent, socket);
config = new NioSocketChannelConfig(this, socket.socket());
}
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
super(parent, ch, SelectionKey.OP_READ);
}
当创建完channel后,就可以通过io.netty.channel.AbstractChannel.AbstractUnsafe#register(channel)来注册Channel了,因为初次启动eventLoop.inEventLoop()为false的原因,那么就是执行eventLoop.execute(...)方法了,这里便来回顾一下这段代码逻辑。代码如下:
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
if (eventLoop == null) {
throw new NullPointerException("eventLoop");
} else if (AbstractChannel.this.isRegistered()) {
promise.setFailure(new IllegalStateException("registered to an event loop already"));
} else if (!AbstractChannel.this.isCompatible(eventLoop)) {
promise.setFailure(new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
} else {
AbstractChannel.this.eventLoop = eventLoop;
// 第一次为false
if (eventLoop.inEventLoop()) {
this.register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
public void run() {
AbstractUnsafe.this.register0(promise);
}
});
} catch (Throwable var4) {
AbstractChannel.logger.warn("Force-closing a channel whose registration task was not accepted by an event loop: {}", AbstractChannel.this, var4);
this.closeForcibly();
AbstractChannel.this.closeFuture.setClosed();
this.safeSetFailure(promise, var4);
}
}
}
}
@Override
public void execute(Runnable task) {
// 省略非关键代码
boolean inEventLoop = inEventLoop();
// 添加任务
addTask(task);
if (!inEventLoop) {
// 启动线程
startThread();
// 省略非关键代码
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
从上面这段代码中可以得知,这段代码是提交注册任务的代码,当符合条件的时候,就会执行startThread方法,代码如下:
io.netty.util.concurrent.SingleThreadEventExecutor#startThread
private void startThread() {
if (state == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
boolean success = false;
try {
// 真正执行启动线程方法
doStartThread();
success = true;
} finally {
if (!success) {
STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
}
}
}
}
}
private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {
@Override
public void run() {
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
}
boolean success = false;
updateLastExecutionTime();
try {
// 执行 NioEventLoop
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
for (;;) {
// 省略非关键代码
}
if (success && gracefulShutdownStartTime == 0) {
// 省略非关键代码
}
try {
// 省略非关键代码
} finally {
try {
//关闭selector
cleanup();
} finally {
// 省略非关键代码
}
}
}
}
});
}
SingleThreadEventExecutor#doStartThread方法的实现很简单,只是调用了一下doStartThread方法,关于doStartThread方法要复杂一些了,但是其核心代码也只是执行 NioEventLoop的run方法,代码如下:
io.netty.channel.nio.NioEventLoop#run
@Override
//死循环监听、处理事件
protected void run() {
for (;;) {
try {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.BUSY_WAIT:
case SelectStrategy.SELECT:
// 执行selector.select
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
default:
}
} catch (IOException e) {
rebuildSelector0();
handleLoopException(e);
continue;
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
// 执行事件
processSelectedKeys();
} finally {
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
private void closeAll() {
selectAgain(); //这里的目标是为了去除canceled的key
Set<SelectionKey> keys = selector.keys();
Collection<AbstractNioChannel> channels = new ArrayList<AbstractNioChannel>(keys.size());
for (SelectionKey k: keys) {
Object a = k.attachment();
if (a instanceof AbstractNioChannel) {
channels.add((AbstractNioChannel) a);
} else {
k.cancel();
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
invokeChannelUnregistered(task, k, null);
}
}
for (AbstractNioChannel ch: channels) {
ch.unsafe().close(ch.unsafe().voidPromise());
}
}
NioEventLoop的run方法是个死循环,期主要目的是轮训这来执行事件,这个时候便是进入到连接的核心代码了,至于这个方法也没什么特别多需要解释的,接下来就开始分析主线代码了。
执行selector.select
NioEventLoop通过selector.select/selectNow()/select(timeoutMillis)方法执行发现连接接收事件OP_ACCEPT时,然后对其进行处理,selector.select方法(对NIO的封装)代码如下:
private void select(boolean oldWakenUp) throws IOException {
Selector selector = this.selector;
try {
int selectCnt = 0;
long currentTimeNanos = System.nanoTime();
// 按scheduled的task时间来计算select timeout时间。
long selectDeadLineNanos = currentTimeNanos + this.delayNanos(currentTimeNanos);
while(true) {
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
if (timeoutMillis <= 0L) { // 已经有定时task需要执行了,或者超过最长等待时间了
if (selectCnt == 0) {
// 非阻塞,没有数据返回0
selector.selectNow();
selectCnt = 1;
}
break;
}
if (this.hasTasks() && this.wakenUp.compareAndSet(false, true)) {
selector.selectNow();
selectCnt = 1;
break;
}
// 下面select阻塞中,别人唤醒也可以可以的
int selectedKeys = selector.select(timeoutMillis);
++selectCnt;
if (selectedKeys != 0 || oldWakenUp || this.wakenUp.get() || this.hasTasks() || this.hasScheduledTasks()) {
break;
}
if (Thread.interrupted()) {
// 省略非关键代码
selectCnt = 1;
break;
}
// 处理jdk空轮询bug和超时时的select方法的处理
long time = System.nanoTime();
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
selector = this.selectRebuildSelector(selectCnt);
selectCnt = 1;
break;
}
currentTimeNanos = time;
}
// 省略非关键代码
} catch (CancelledKeyException var13) {
// 省略非关键代码
}
}
processSelectedKeys方法
从前面的run方法的代码中可以看出来,和之前一直提到的Netty Reactor模型图所展示的是不是一致的,当执行完processSelectedKeys()方法后,就会执行runAllTasks()。这里processSelectedKeys()方法的执行具体如下:
io.netty.channel.nio.NioEventLoop#processSelectedKeys
private void processSelectedKeys() {
if (selectedKeys != null) {
//不用JDK的selector.selectedKeys(), 性能更好(1%-2%),垃圾回收更少
processSelectedKeysOptimized();
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
}
NioEventLoop#processSelectedKeys方法的代码很简单,就是根据selectedKeys是否为null来决定执行哪个方法,这里会执行processSelectedKeysOptimized()方法,代码如下:
io.netty.channel.nio.NioEventLoop#processSelectedKeysOptimized
private void processSelectedKeysOptimized() {
for (int i = 0; i < selectedKeys.size; ++i) {
final SelectionKey k = selectedKeys.keys[i];
selectedKeys.keys[i] = null;
// 呼应于channel的register中的this: 例如:selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
final Object a = k.attachment();
//
if (a instanceof AbstractNioChannel) {
processSelectedKey(k, (AbstractNioChannel) a);
} else {
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task);
}
if (needsToSelectAgain) {
selectedKeys.reset(i + 1);
selectAgain();
i = -1;
}
}
}
从前面所展示的连接构建核心时序图中,可以得知当轮训到OP_ACCEPT事件的时候,会调用NioUnsafe.read()方法,而NioUnsafe则是通过processSelectedKeysOptimized()方法中的k.attachment()来进行获取的。 k.attachment()方法所返回的是AbstractNioChannel,AbstractNioChannel.unsafe()方法返回的则是AbstractNioChannel.NioUnsafe类型。那我们就来看看processSelectedKey()方法的具体实现,如下:
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
// 获取 AbstractNioChannel.NioUnsafe
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
final EventLoop eventLoop;
try {
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
return;
}
if (eventLoop != this || eventLoop == null) {
return;
}
unsafe.close(unsafe.voidPromise());
return;
}
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) {
ch.unsafe().forceFlush();
}
//处理读请求(断开连接)或接入连接
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
执行NioMessageUnsafe#read 方法
从 processSelectedKey()方法中,看到当有OP_ACCEPT事件的时候,便会执行unsafe.read(),在unsafe.read()方法中,这时我们只需要关注doReadMessages(readBuf)即可,代码如下:
io.netty.channel.nio.AbstractNioMessageChannel.NioMessageUnsafe#read
@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 {
int localRead = doReadMessages(readBuf);
// 省略非关键代码
} 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();
// 省略非关键代码
} finally {
// 省略非关键代码
}
}
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
//接受新连接创建SocketChannel
SocketChannel ch = SocketUtils.accept(javaChannel());
try {
if (ch != null) {
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
// 省略非关键代码
}
return 0;
}
public NioSocketChannel(Channel parent, SocketChannel socket) {
// 调用 NioSocketChannel 父类方法
super(parent, socket);
config = new NioSocketChannelConfig(this, socket.socket());
}
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
super(parent, ch, SelectionKey.OP_READ);
}
// io.netty.util.internal.SocketUtils#accept
public static SocketChannel accept(final ServerSocketChannel serverSocketChannel) throws IOException {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<SocketChannel>() {
@Override
public SocketChannel run() throws IOException {
// 非阻塞模式下,没有连接请求时,返回null(对NIO的封装)
return serverSocketChannel.accept();
}
});
} catch (PrivilegedActionException e) {
throw (IOException) e.getCause();
}
}
doReadMessages方法中,会通过SocketUtils.accept(javaChannel())方法中的serverSocketChannel.accept()来创建SocketChannel对象,一但SocketChannel创建,就可以通过pipeline.fireChannelRead方法传播出去,在传播过程中,系统会调用处理程序流水线中的ServerBootstrapAcceptor对创建的SocketChannel来进行初始化,初始化方法为ServerBootstrapAcceptor#channelRead,那么就来看看这个方法在哪里调用的(AbstractNioMessageChannel.NioMessageUnsafe#read方法中),如下:
// io.netty.channel.DefaultChannelPipeline#fireChannelRead
public final ChannelPipeline fireChannelRead(Object msg) {
AbstractChannelHandlerContext.invokeChannelRead(this.head, msg);
return this;
}
// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRead(m);
} else {
executor.execute(new Runnable() {
public void run() {
next.invokeChannelRead(m);
}
});
}
}
// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(java.lang.Object)
private void invokeChannelRead(Object msg) {
if (this.invokeHandler()) {
try {
// channel read 执行逻辑
((ChannelInboundHandler)this.handler()).channelRead(this, msg);
} catch (Throwable var3) {
this.notifyHandlerException(var3);
}
} else {
this.fireChannelRead(msg);
}
}
上边的代码便是从.NioMessageUnsafe#read方法中开始调用 DefaultChannelPipeline#fireChannelRead 方法的一系列逻辑,代码很简单,这个就不赘述了,当调用到ServerBootstrapAcceptor#channelRead方法时,才是真正的核心逻辑,代码如下:
io.netty.bootstrap.ServerBootstrap.ServerBootstrapAcceptor#channelRead
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 获取 NioSocketChannel
final Channel child = (Channel)msg;
child.pipeline().addLast(new ChannelHandler[]{this.childHandler});
AbstractBootstrap.setChannelOptions(child, this.childOptions, ServerBootstrap.logger);
Entry[] var4 = this.childAttrs;
int var5 = var4.length;
for(int var6 = 0; var6 < var5; ++var6) {
Entry<AttributeKey<?>, Object> e = var4[var6];
child.attr((AttributeKey)e.getKey()).set(e.getValue());
}
try {
// 注册客户端的读事件
this.childGroup.register(child).addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
ServerBootstrap.ServerBootstrapAcceptor.forceClose(child, future.cause());
}
}
});
} catch (Throwable var8) {
forceClose(child, var8);
}
}
上面的初始化(注册)SocketChannel的过程和初始化ServerSocketChannel的过程是差不多的,最终都是从NioEventLoopGroup中(此时是Worker Group)中选择一个NioEventLoop。来注册“读”事件,但是此时的读事件是真正的OP_READ读取数据事件(也就是前面时序图中最后所提到的读事件),而不是OP_ACCEPT连接接收事件。