NioServerSocketChannelの作成
サーバーコードをもう一度見ると、ServerBootstarapのchannel(NioServerSocketChannel.class)メソッドが呼び出され、渡されたパラメーターはNioServerSocketChannel.classオブジェクトです。このように、クライアントコードの同じプロセスに従って、NioServerSocketChannelのインスタンス化もReflectiveChannelFactoryファクトリクラスを介して行われ、ReflectiveChannelFactoryのclazzフィールドにNioServerSocketChannel.classの値が割り当てられていることを確認できます。したがって、ReflectiveChannelFactoryのnewChannel()メソッドが呼び出されると、 NioServerSocketChannelのインスタンスを取得できます。newChannel()メソッドのソースコードは次のとおりです。
public ReflectiveChannelFactory(Class<? extends T> clazz) {
ObjectUtil.checkNotNull(clazz, "clazz");
try {
this.constructor = clazz.getConstructor();
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
" does not have a public non-arg constructor", e);
}
}
@Override
public T newChannel() {
try {
//通过反射调用下面代码
return constructor.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
}
}
最後に、以下も要約します。
1.ServerBootstrapのChannelFactoryの実装クラスはReflectiveChannelFactoryです。
2.作成されたチャネルの特定のタイプはNioServerSocketChannelです。
Channelのインスタンス化プロセスは、実際にはChannelFactoryのnewChannel()メソッドを呼び出すことであり、インスタンス化されたChannelの具体的なタイプは、ServerBootstrapが初期化されるときにchannel()メソッドに渡される実際のパラメーターです。したがって、上記のコードケースのサーバー側ServerBootstrapはチャネルを作成します。インスタンスは、NioServerSocketChannelのインスタンスです。
サーバーチャネルの初期化
NioServerSocketChannelのインスタンス化プロセスを分析してみましょう。まず、NioServerSocketChannelのクラス階層図を見てください。
まず、NioServerSocketChannelのデフォルト構造をトレースしましょう。NioSocketChannelと同様に、コンストラクターはnewSocket()を呼び出してJavaNIOソケットを開きます。ただし、クライアントのnewSocket()メソッドはopenSocketChannel()を呼び出し、サーバーのnewSocket()はopenServerSocketChannel()を呼び出すことに注意してください。名前が示すように、1つはクライアント側のJava SocketChannelで、もう1つはサーバー側のJavaServerSocketChannelです。コードを見てください。
private static ServerSocketChannel newSocket(SelectorProvider provider) {
try {
/**
* Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
* {@link SelectorProvider#provider()} which is called by each ServerSocketChannel.open() otherwise.
*
* See <a href="https://github.com/netty/netty/issues/2308">#2308</a>.
*/
return provider.openServerSocketChannel();
} catch (IOException e) {
throw new ChannelException(
"Failed to open a server socket.", e);
}
}
private final ServerSocketChannelConfig config;
/**
* Create a new instance,通过反射创建,调用构造
*/
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
次に、オーバーロードされた構築メソッドが呼び出されます。
public NioServerSocketChannel(ServerSocketChannel channel){ super(null、channel、SelectionKey.OP_ACCEPT); config = new NioServerSocketChannelConfig(this、javaChannel()。socket()); }
この構築メソッドでは、親クラス構築メソッドを呼び出すときに渡されるパラメーターは、SelectionKey.OP_ACCEPTです。比較のために、クライアントのチャネルが初期化されるときに、渡されるパラメーターがSelectionKey.OP_READであることを確認しましょう。サービスの開始後、クライアントの接続要求をリッスンする必要があるため、ここではSelectionKey.OP_ACCEPTを設定します。これは、クライアントの接続要求に関心があることをセレクターに通知するためのものです。
次に、クライアントと比較して分析します。親クラスのコンストラクターであるNioServerSocketChannel-> AbstractNioMessageChannel-> AbstractNioChannel-> AbstractChannelが段階的に呼び出されます。同様に、AbstractChannelで安全でないパイプラインをインスタンス化します。
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
ただし、ここで、クライアント側の安全でないのはAbstractNioByteChannel#NioByteUnsafeのインスタンスであり、サーバー側の安全でないのはAbstractNioMessageChannel.AbstractNioUnsafeのインスタンスであることに注意してください。AbstractNioMessageChannelはnewUnsafe()メソッドを書き換えるため、そのソースコードは次のとおりです。
@Override
protected AbstractNioUnsafe newUnsafe() {
return new NioMessageUnsafe();
}
最後に、要約すると、NioServerSocketChannelのインスタンス化中の実行ロジックは次のとおりです。
1. NioServerSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER)メソッドを呼び出して、新しいJavaNIOを開きます
ServerSocketChannel
2. AbstractChannelが初期化され、属性が割り当てられます。
親:nullに設定
unsafe:newUnsafe()を介して安全でないオブジェクトをインスタンス化します。タイプはAbstractNioMessageChannel#AbstractNioUnsafeです。
パイプライン:DefaultChannelPipelineのインスタンスを作成します
3. AbstractNioChannelで割り当てられた属性:
ch:Java NIOのServerSocketChannelに割り当てられ、NioServerSocketChannelのnewSocket()メソッドを呼び出して取得します。
readInterestOp:デフォルトの割り当てはSelectionKey.OP_ACCEPTです。
chが非ブロッキングに設定されている場合は、ch.configureBlocking(false)メソッドを呼び出します。
4. NioServerSocketChannelで割り当てられた属性:
config = new NioServerSocketChannelConfig(this、javaChannel()。socket())
ChannelPipelineの初期化
上記では、NioServerSocketChannelの一般的な初期化プロセスを分析しましたが、重要な部分、つまりChannelPipelineの初期化を見逃していました。パイプラインのメモには、「各チャネルには独自のパイプラインがあり、新しいチャネルが作成されると自動的に作成されます」と記載されています。チャネルをインスタンス化するときは、ChannelPipelineをインスタンス化する必要があることがわかっています。また、AbstractChannelのコンストラクターで、パイプラインフィールドがDefaultChannelPipelineのインスタンスに初期化されていることがわかりました。それでは、DefaultChannelPipelineコンストラクターの機能を見てみましょう。
protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise = new VoidChannelPromise(channel, true);
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
DefaultChannelPipelineのコンストラクターはチャネルを渡す必要があり、このチャネルは実際にはインスタンス化したNioServerSocketChannelであり、DefaultChannelPipelineはこのNioServerSocketChannelオブジェクトをチャネルフィールドに格納します。DefaultChannelPipelineには、headとtailという2つの特別なフィールドもあります。これらの2つのフィールドは、二重にリンクされたリストのheadとtailです。実際、DefaultChannelPipelineでは、ノード要素としてAbstractChannelHandlerContextを持つ二重リンクリストが維持されます。このリンクリストは、Nettyによるパイプラインメカニズムの実装の鍵です。
リンクリストでは、ヘッドはChannelOutboundHandlerであり、テールはChannelInboundHandlerです。次に、HeadContextコンストラクターを見てください。
HeadContext(DefaultChannelPipeline pipeline) {
super(pipeline, null, HEAD_NAME, HeadContext.class);
unsafe = pipeline.channel().unsafe();
setAddComplete();
}
セレクターに登録されたサーバーチャネル
チャネルの登録プロセスを要約してみましょう。
1.まず、AbstractBootstrapのinitAndRegister()メソッドで、group()。register(channel)を呼び出します。
MultithreadEventLoopGroupのregister()メソッド。
2. MultithreadEventLoopGroupのregister()で、next()メソッドを呼び出して使用可能なSingleThreadEventLoopを取得してから、そのregister()メソッドを呼び出します。
3. SingleThreadEventLoopのregister()メソッドで、channel.unsafe()。register(this、promise)メソッドを呼び出して取得します。
チャネルのunsafe()の基礎となる操作オブジェクト、次にunsafeのregister()が呼び出されます。
4. AbstractUnsafeのregister()メソッドで、register0()メソッドを呼び出してChannelオブジェクトを登録します。
5. AbstractUnsafeのregister0()メソッドで、AbstractNioChannelのdoRegister()メソッドを呼び出します。
6. AbstractNioChannelのdoRegister()メソッドは、チャネルに対応するJava NIO SocketChannelをjavaChannel()。register(eventLoop()。selector、0、this)を介してeventLoopセレクターに登録し、現在のチャネルを添付ファイルとしてServerSocketChannelに関連付けます。 。
一般に、チャネル登録プロセスによって行われる作業は、チャネルを対応するEventLoopに関連付けることであるため、これはNettyにも反映されます。
各チャネルは特定のEventLoopに関連付けられ、このチャネルのすべてのIO操作はこのEventLoopで実行されます。チャネルとEventLoopが関連付けられると、基盤となるJava NIOのServerSocketChannelオブジェクトのregister()が引き続き呼び出されます。メソッド、基礎となるJavaNIOのServerSocketChannelを指定されたセレクターに登録します。これらの2つのステップにより、Netty toChannelの登録プロセスが完了します。
bossGroup与workerGroup
クライアント側では、EventLoopGroupオブジェクトを初期化し、サーバー側では、2つを設定しました。
EventLoopGroup、1つはbossGroupで、もう1つはworkerGroupです。では、これら2つのEventLoopGroupは何に使用されるのでしょうか?次に詳しく見ていきましょう。実際、bossGroupはサーバー側での受け入れにのみ使用されます。つまり、クライアントの新しい接続要求を処理するために使用されます。Nettyをレストランと比較することができます。BossGroupはロビーマネージャーのようなものです。顧客がレストランで食事をするとき、ロビーマネージャーは顧客に座ってお茶と水を提供するように案内します。ワーカーグループは実際に働くシェフであり、クライアント接続チャネルのIO操作を担当します。ロビーで顧客の受け入れが発生すると、顧客は休憩でき、バックキッチン(workerGroup)のシェフは準備に忙しくなります。食事は終わりました。bossGroupとworkerGroupの関係については、次の図を使用して示すことができます。前の章も分析しました。ここでは、それを統合します。
まず、サーバーのbossGroupは、クライアント接続があるかどうかを常に監視します。新しいクライアント接続が見つかると、bossGroupはこの接続のさまざまなリソースを初期化し、workerGroupからEventLoopを選択してこのクライアントにバインドします。接続を終了します。次に、ここで割り当てられたEventLoopで、次のサーバーとクライアントの対話プロセスがすべて完了します。口の中に証拠はありません。ソースコードで話しましょう。まず、ServerBootstrapが初期化されると、b.group(bossGroup、workerGroup)が呼び出されて2つのEventLoopGroupが設定されます。トレースインすると、次のように表示されます。
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
super.group(parentGroup);
if (this.childGroup != null) {
throw new IllegalStateException("childGroup set already");
}
this.childGroup = ObjectUtil.checkNotNull(childGroup, "childGroup");
return this;
}
bind()メソッドは、次の呼び出しチェーンをトリガーします。
AbstractBootstrap.bind()-> AbstractBootstrap.doBind()-> AbstractBootstrap.initAndRegister()
ソースコードからこれまでのところ、AbstractBootstrapのinitAndRegister()メソッドはすでに古くからの友人であることがわかりました。クライアントプログラムを分析するときに多くのことを扱ってきました。次に、このメソッドを確認しましょう。
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
} catch (Throwable t) {
if (channel != null) {
// channel can be null if newChannel crashed (eg SocketException("too many open files"))
channel.unsafe().closeForcibly();
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
}
//*
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
ここで、group()メソッドは前述のbossGroupを返します。ここでのチャネルは、実際にはNioServerSocketChannelのインスタンスであるため、group()。register(channel)はbossGroupとNioServerSocketChannelを関連付ける必要があると推測できます。では、workerGroupはNioServerSocketChannelと正確にどこに関連していますか?init(channel)メソッドを引き続き見ていきます。
@Override
void init(Channel channel) {
setChannelOptions(channel, newOptionsArray(), logger);
setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
}
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
実際、init()メソッドはServerBootstrapで書き直されています。上記のコードスニペットから、次のように追加されていることがわかります。
ChannelInitializer、およびこのChannelInitializerは、非常に重要なServerBootstrapAcceptorハンドラーを追加します。ハンドラーの追加と初期化のプロセスについては、詳細な分析のために次の章に任せます。それでは、ServerBootstrapAcceptorクラスに注目しましょう。channelRead()メソッドはServerBootstrapAcceptorで書き直され、そのメインコードは次のとおりです。
@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
child.pipeline().addLast(childHandler);
setChannelOptions(child, childOptions, logger);
setAttributes(child, childAttrs);
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);
}
}
ServerBootstrapAcceptorのchildGroupは、workerGroupオブジェクトであるこのオブジェクトを構築するために渡されるcurrentChildGroupです。ここでのチャネルはNioSocketChannelのインスタンスであるため、ここでのchildGroupのregister()メソッドは、workerGroup内の特定のEventLoopをNioSocketChannelに関連付けることです。その場合、問題はServerBootstrapAcceptorのchannelRead()メソッドがどこで呼び出されるかということです。実際、クライアントがサーバーに接続すると、NIOの基礎となるJavaのServerSocketChannelでSelectionKey.OP_ACCEPTイベントが準備され、その後、 NioServerSocketChannelのdoReadMessages()メソッドを呼び出します。
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
//创建java的socket
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
/**
* @param
* @return
* @description: 创建的netty的服务端的socket
* @author madongyu
* @date 2020/10/29 15:36
*/
@Override
protected AbstractNioUnsafe newUnsafe() {
return new NioMessageUnsafe();
}
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 {
//*
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());
}
}
} 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
protected int doReadMessages(List<Object> buf) throws Exception {
SocketChannel ch = SocketUtils.accept(javaChannel());
try {
if (ch != null) {
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;
}
doReadMessages()メソッドでは、クライアントの新しく接続されたSocketChannelオブジェクトがjavaChannel()。accept()メソッドを呼び出すことによって取得され、次にNioSocketChannelがインスタンス化され、NioServerSocketChannelオブジェクト(つまりこれ)が渡されます。作成したNioSocketChannelの親クラスChannelはNioServerSocketChannelのインスタンスであることがわかります。次に、NettyのChannelPipelineメカニズムを介して、読み取りイベントが各ハンドラーに段階的に送信され、前述のServerBootstrapAcceptorのchannelRead()メソッドがトリガーされます。
サーバーセレクタイベントのポーリング
サーバー側のServerBootStrapの起動コードに戻ると、bind()メソッドから開始されます。ServerBootStraptのbind()メソッドは、実際にはその親クラスAbstractBootstrapのbind()メソッドです。コードを見てください。
private static void doBind0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
// the pipeline in its channelRegistered() implementation.
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
doBind0()メソッドでは、EventLoopのexecute()メソッドが呼び出され、フォローアップを続けます。
SingleThreadEventExecutor
private void execute(Runnable task, boolean immediate) {
boolean inEventLoop = inEventLoop();
addTask(task);
if (!inEventLoop) {
startThread();
if (isShutdown()) {
boolean reject = false;
try {
if (removeTask(task)) {
reject = true;
}
} catch (UnsupportedOperationException e) {
// The task queue does not support removal so the best thing we can do is to just move on and
// hope we will be able to pick-up the task before its completely terminated.
// In worst case we will log on termination.
}
if (reject) {
reject();
}
}
}
if (!addTaskWakesUp && immediate) {
wakeup(inEventLoop);
}
}
execute()で、スレッドが作成され、EventLoopのロックフリーシリアルタスクキューに追加されます。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 {
//*
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
for (;;) {
int oldState = state;
if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
break;
}
}
// Check if confirmShutdown() was called at the end of the loop.
if (success && gracefulShutdownStartTime == 0) {
if (logger.isErrorEnabled()) {
logger.error("Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must " +
"be called before run() implementation terminates.");
}
}
try {
// Run all remaining tasks and shutdown hooks. At this point the event loop
// is in ST_SHUTTING_DOWN state still accepting tasks which is needed for
// graceful shutdown with quietPeriod.
for (;;) {
if (confirmShutdown()) {
break;
}
}
// Now we want to make sure no more tasks can be added from this point. This is
// achieved by switching the state. Any new tasks beyond this point will be rejected.
for (;;) {
int oldState = state;
if (oldState >= ST_SHUTDOWN || STATE_UPDATER.compareAndSet(
SingleThreadEventExecutor.this, oldState, ST_SHUTDOWN)) {
break;
}
}
// We have the final set of tasks in the queue now, no more can be added, run all remaining.
// No need to loop here, this is the final pass.
confirmShutdown();
} finally {
try {
cleanup();
} finally {
// Lets remove all FastThreadLocals for the Thread as we are about to terminate and notify
// the future. The user may block on the future and once it unblocks the JVM may terminate
// and start unloading classes.
// See https://github.com/netty/netty/issues/6596.
FastThreadLocal.removeAll();
STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
threadLock.countDown();
int numUserTasks = drainTasks();
if (numUserTasks > 0 && logger.isWarnEnabled()) {
logger.warn("An event executor terminated with " +
"non-empty task queue (" + numUserTasks + ')');
}
terminationFuture.setSuccess(null);
}
}
}
}
});
}
startThread()が最終的にSingleThreadEventExecutor.this.run()メソッドを呼び出すことがわかりました。これは、NioEventLoopオブジェクトです。
@Override
protected void run() {
int selectCnt = 0;
for (;;) {
try {
int strategy;
try {
strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
switch (strategy) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.BUSY_WAIT:
// fall-through to SELECT since the busy-wait is not supported with NIO
case SelectStrategy.SELECT:
long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
if (curDeadlineNanos == -1L) {
curDeadlineNanos = NONE; // nothing on the calendar
}
nextWakeupNanos.set(curDeadlineNanos);
try {
if (!hasTasks()) {
//*解决JDK空轮训的bug
strategy = select(curDeadlineNanos);
}
} finally {
// This update is just to help block unnecessary selector wakeups
// so use of lazySet is ok (no race condition)
nextWakeupNanos.lazySet(AWAKE);
}
// fall through
default:
}
} catch (IOException e) {
// If we receive an IOException here its because the Selector is messed up. Let's rebuild
// the selector and retry. https://github.com/netty/netty/issues/8566
rebuildSelector0();
selectCnt = 0;
handleLoopException(e);
continue;
}
selectCnt++;
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
boolean ranTasks;
if (ioRatio == 100) {
try {
if (strategy > 0) {
//针对不同的轮训事件进行处理
processSelectedKeys();
}
} finally {
// Ensure we always run tasks.
ranTasks = runAllTasks();
}
} else if (strategy > 0) {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
} else {
ranTasks = runAllTasks(0); // This will run the minimum number of tasks
}
if (ranTasks || strategy > 0) {
if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {
logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
selectCnt - 1, selector);
}
selectCnt = 0;
} else if (unexpectedSelectorWakeup(selectCnt)) { // Unexpected wakeup (unusual case)
selectCnt = 0;
}
} catch (CancelledKeyException e) {
// Harmless exception - log anyway
if (logger.isDebugEnabled()) {
logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
selector, e);
}
} catch (Error e) {
throw (Error) e;
} catch (Throwable t) {
handleLoopException(t);
} finally {
// Always handle shutdown even if the loop processing threw an exception.
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Error e) {
throw (Error) e;
} catch (Throwable t) {
handleLoopException(t);
}
}
}
}
ついに見慣れたコードを見ました。上記のコードは、主に無限ループを使用して、SelectionKeyを継続的にポーリングします。select()メソッドは主にJDKの空のポーリングバグを解決するために使用され、processSelectedKeys()はさまざまなポーリングイベントを処理するために使用されます。クライアントに書き込むデータがある場合、最終的にはAbstractNioMessageChannelのdoReadMessages()メソッドを呼び出します。結論として:
AbstractBootstrap.doBind0-> SingleThreadEventExecutor.execute-> SingleThreadEventExecutor.run->
NioEventLoop.run(不断轮训selectKeys)-> AbstractNioChannel.read-> AbstractNioMessageChannel.read-> NioServerSocketChannel.doReadMessages
1. NettyでのSelectorイベントのポーリングは、EventLoopのexecute()メソッドから開始されます。
2. EventLoopのexecute()メソッドでは、タスクごとに独立したスレッドが作成され、ロック解除されたシリアルタスクキューに保存されます。
3.スレッドタスクキューの各タスクは、実際にはNioEventLoopのrun()メソッドを呼び出します。
4. runメソッドでprocessSelectedKeys()を呼び出して、ポーリングイベントを処理します。
ハンドラーを追加するプロセス
サーバー側ハンドラーを追加するプロセスは、クライアント側のプロセスとは多少異なります。EventLoopGroupと同様に、2つのサーバー側ハンドラーがあります。1つはhandler()メソッドによって設定されるハンドラーで、もう1つはchildHandler()メソッドによって設定されるchildHandlerです。bossGroupとworkerGroupの以前の分析から、ここで大胆に推測できます。ハンドラーは受け入れプロセスに関連しています。つまり、ハンドラーはクライアントの新しい接続アクセス要求の処理を担当し、childHandlerはクライアント接続とのIO相互作用を担当します。それで、これは実際にそうですか?私たちは証明するためにコードを使い続けます。前の章では、ServerBootstrapがinit()メソッドを書き換え、ハンドラーもこのメソッドに追加されることを学びました。
@Override
void init(Channel channel) {
setChannelOptions(channel, newOptionsArray(), logger);
setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
}
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
上記のコードのinitChannel()メソッドでは、最初にhandler()メソッドを介してハンドラーを取得します。取得したハンドラーが空でない場合は、パイプラインに追加します。次に、ServerBootstrapAcceptorのインスタンスを追加します。では、ここでhandler()メソッドによって返されるオブジェクトはどれですか?実際には、ハンドラフィールドが返されます。このフィールドは、サーバー側の起動コードで設定したものです。
ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup、workerGroup)
このとき、パイプラインのハンドラーは次のとおりです。
元のクライアントコードの分析によると、チャネルがeventLoopにバインドされ(この場合、NioServerSocketChannelはbossGroupにバインドされます)、fireChannelRegisteredイベントがパイプラインでトリガーされ、ChannelInitializerのinitChannel()メソッドがトリガーされることを指定します。転送。したがって、バインディングが完了した後のこの時点でのパイプラインは次のようになります。
以前にbossGroupとworkerGroupを分析したとき、ServerBootstrapAcceptorのchannelRead()メソッドが新しく作成されたチャネルのハンドラーを設定し、それをeventLoopに登録することをすでに知っていました。
@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
child.pipeline().addLast(childHandler);
setChannelOptions(child, childOptions, logger);
setAttributes(child, childAttrs);
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は、サーバー側の起動コードで設定したハンドラーです。
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
}
}).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);
次の手順は基本的に明らかです。クライアントがチャネル登録に接続すると、ChannelInitializerのinitChannel()メソッドがトリガーされます。最後に、サーバーハンドラーとchildHandlerの違いと接続を要約しましょう。
1.サーバーNioServerSocketChannelのパイプラインに、ハンドラーとServerBootstrapAcceptorが追加されます。
2.新しいクライアント接続要求がある場合、ServerBootstrapAcceptorのchannelRead()メソッドを呼び出してこの接続を作成します
NioSocketChannelと、NioSocketChannelに対応するパイプラインにchildHandlerを追加し、このチャネルをにバインドします。
workerGroupのeventLoop内。
3.ハンドラーは受け入れフェーズで機能し、クライアントの接続要求を処理します。
4. childHandlerは、クライアント接続が確立された後に有効になり、クライアント接続のIOインタラクションを担当します。
最後に、写真を見て理解を深めてください。次の図は、サーバーの初期化から新しい接続アクセスへの変更プロセスを示しています。