Nettyクライアントのコード例:
public Test connect(int port, String host, final String nickName) {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
}
});
//发起同步连接操作
ChannelFuture channelFuture = bootstrap
.connect(host, port)
.sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//关闭,释放线程资源
group.shutdownGracefully();
}
return this;
}
public static void main(String[] args) {
new Test().connect(8080, "localhost", "Tom");
}
チャネルにNioSocketChannel.classを設定すると、AbstractBootstrapの次のメソッドが呼び出されます。
public B channel(Class<? extends C> channelClass) {
return channelFactory(new ReflectiveChannelFactory<C>(
ObjectUtil.checkNotNull(channelClass, "channelClass")
));
}
ReflectiveChannelFactoryは、唯一のメソッド、つまりnewChannel()メソッドを提供するChannelFactoryインターフェイスを実装します。ChannelFactoryは、その名前が示すように、Channelを作成するためのファクトリクラスです。ReflectiveChannelFactoryの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.BootstrapのChannelFactory実装クラスはReflectiveChannelFactoryです。
2. channel()メソッドによって作成されるチャネルの特定のタイプはNioSocketChannelです。
チャネルのインスタンス化のプロセスは、実際にはChannelFactoryのnewChannel()メソッドを呼び出すことであり、インスタンス化される特定のタイプのチャネルは、Bootstrapの初期化時に渡されるchannel()メソッドのパラメーターに関連しています。したがって、クライアントBootstrapの場合、作成されるChannelインスタンスはNioSocketChannelです。
クライアントチャネルの初期化
チャネルのタイプを設定する方法はすでに知っており、チャネルはChannelFactoryのnewChannel()メソッドによってインスタンス化されることを学びました。したがって、ChannelFactoryのnewChannel()メソッドはどこで呼び出されますか?追跡を続けると、その呼び出しチェーンは次のようになります。 :
AbstractBootstrapのinitAndRegister()で、ChannelFactory()のnewChannel()が呼び出され、NioSocketChannelのインスタンスが作成されます。ソースコードは次のとおりです。
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
//调用的是channelFactory接口的实现类ReflectiveChannelFactory中的newChannel
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;
}
newChannel()メソッドで、リフレクションメカニズムを使用してクラスオブジェクトのnewInstance()メソッドを呼び出し、新しいChannelインスタンスを作成します。これは、NioSocketChannelのデフォルトコンストラクターを呼び出すのと同じです。NioSocketChannelのデフォルトのコンストラクタコードは次のとおりです。
public NioSocketChannel() {
this(DEFAULT_SELECTOR_PROVIDER);
}
public NioSocketChannel(SelectorProvider provider) {
this(newSocket(provider));
}
private static SocketChannel 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 SocketChannel.open() otherwise.
*
* See <a href="https://github.com/netty/netty/issues/2308">#2308</a>.
*/
return provider.openSocketChannel();
} catch (IOException e) {
throw new ChannelException("Failed to open a socket.", e);
}
}
ここのコードはより重要です。このコンストラクターでは、新しいJava NIO SocketChannelを開くためにnewSocket()が呼び出されていることがわかります。
次に、親クラスであるAbstractNioByteChannelのコンストラクターが呼び出されます。
public NioSocketChannel(SocketChannel socket) {
this(null, socket);
}
public NioSocketChannel(Channel parent, SocketChannel socket) {
super(parent, socket);
config = new NioSocketChannelConfig(this, socket.socket());
}
また、着信パラメータの親はnullです。chは、newSocket()を呼び出して作成されたJava NIO SocketChannelオブジェクトであるため、新しく作成されます。
NioSocketChannelオブジェクトの親は一時的にnullです。
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
super(parent, ch, SelectionKey.OP_READ);
}
次に、親クラスAbstractNioChannelのコンストラクターを引き続き呼び出し、実際のパラメーターreadInterestOp = SelectionKey.OP_READを渡します。
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
this.readInterestOp = readInterestOp;
try {
ch.configureBlocking(false);
} catch (IOException e) {
try {
ch.close();
} catch (IOException e2) {
logger.warn(
"Failed to close a partially initialized socket.", e2);
}
throw new ChannelException("Failed to enter non-blocking mode.", e);
}
}
次に、親クラスAbstractChannelのコンストラクターを呼び出し続けます。
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
この時点で、NioSocketChannelの初期化が完了し、NioSocketChannelの初期化によって行われた作業を簡単に要約できます。
1. NioSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER)を呼び出して、新しいJavaNIOSocketChannelを開きます。
2. AbstractChannel(チャネルの親)で初期化する必要がある属性:
id:各チャネルには一意のIDがあります。
親:属性はnullに設定されます。
unsafe:newUnsafe()を介して安全でないオブジェクトをインスタンス化します。そのタイプはAbstractNioByteChannel.NioByteUnsafe内部クラスです。
パイプライン:新しいDefaultChannelPipeline(this)の新しく作成されたインスタンスです。
3. AbstractNioChannelのプロパティ:
ch:Java SocketChannelに割り当てられます。これは、NioSocketChannelのnewSocket()メソッドによって返されるJava NIOSocketChannelです。
readInterestOp:SelectionKey.OP_READに割り当てられています
ch:非ブロッキングとして構成されています。つまり、ch.configureBlocking(false)を呼び出します。
4. NioSocketChannelのプロパティ:
config = new NioSocketChannelConfig(this、socket.socket())
安全でないフィールドの初期化
NioSocketChannelをインスタンス化するプロセスで、親クラスAbstractChannelのコンストラクターで呼び出されることを簡単に説明しました。
newUnsafe()を使用して、安全でないインスタンスを取得します。では、安全でないものはどのように初期化されますか?その機能は何ですか?
実際、安全でないことは特に重要です。これはJavaの最下部のソケットの操作をカプセル化するため、実際にはNettyの上位層とJavaの最下位層の間の重要なブリッジです。それでは、安全でないインターフェースによって提供されるメソッドを見てみましょう。
interface Unsafe {
RecvByteBufAllocator.Handle recvBufAllocHandle();
SocketAddress localAddress();
SocketAddress remoteAddress();
void register(EventLoop eventLoop, ChannelPromise promise);
void bind(SocketAddress localAddress, ChannelPromise promise);
void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);
void disconnect(ChannelPromise promise);
void close(ChannelPromise promise);
void closeForcibly();
void deregister(ChannelPromise promise);
void beginRead();
void write(Object msg, ChannelPromise promise);
void flush();
ChannelPromise voidPromise();
ChannelOutboundBuffer outboundBuffer();
}
ソースコードから、これらのメソッドが実際には基盤となるJavaソケットの操作に対応していることがわかります。AbstractChannelの構築メソッドに戻ると、ここではnewUnsafe()が呼び出されて新しい安全でないオブジェクトが取得され、newUnsafe()メソッドがNioSocketChannelでオーバーライドされます。コードを見てください
@Override
protected AbstractNioUnsafe newUnsafe() {
return new NioSocketChannelUnsafe();
}
NioSocketChannelのnewUnsafe()メソッドは、NioSocketChannelUnsafeのインスタンスを返します。ここから、インスタンス化されたNioSocketChannelのunsafeフィールドが実際にはNioSocketChannelUnsafeのインスタンスであることを確認できます。
パイプラインの初期化
上記では、NioSocketChannelの一般的な初期化プロセスを分析しましたが、重要な部分、つまり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のコンストラクターはチャネルを渡す必要があり、このチャネルは実際にはインスタンス化したNioSocketChannelであり、DefaultChannelPipelineはこのNioSocketChannelオブジェクトをチャネルフィールドに格納します。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();
}
EventLoopの初期化
最初のChatClientユーザーコードに戻って、最初にNioEventLoopGroupオブジェクトをインスタンス化したので、そのコンストラクターからEventLoopの初期化プロセスをトレースします。
NioEventLoopにはいくつかのオーバーロードされたコンストラクターがありますが、内容に大きな違いはありません。最終的に、それらはすべて親クラスMultithreadEventLoopGroupのコンストラクターと呼ばれます。
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
興味深い点の1つは、nThreadsで渡すスレッドの数が0の場合、Nettyがデフォルトのスレッド数を設定することです。
DEFAULT_EVENT_LOOP_THREADS、およびデフォルトのスレッド数はどのように決定されますか?
実際、これは非常に単純です。静的コードブロックでは、DEFAULT_EVENT_LOOP_THREADSの値が最初に決定されます。
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
"io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
if (logger.isDebugEnabled()) {
logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
}
}
Nettyは、最初にシステムプロパティから「io.netty.eventLoopThreads」の値を取得します。設定しない場合は、デフォルト値であるプロセッサコアの数* 2に戻ります。MultithreadEventLoopGroupに戻ると、コンストラクターは引き続き親クラスMultithreadEventExecutorGroupのコンストラクターを呼び出します。
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
if (nThreads <= 0) {
throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
}
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
//*
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
//上面是异常处理代码
//*
children[i] = newChild(executor, args);
success = true;
} catch (Exception e) {
// TODO: Think about if this is a good exception type
throw new IllegalStateException("failed to create a child event loop", e);
} finally {
if (!success) {
for (int j = 0; j < i; j ++) {
children[j].shutdownGracefully();
}
for (int j = 0; j < i; j ++) {
EventExecutor e = children[j];
try {
while (!e.isTerminated()) {
e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
} catch (InterruptedException interrupted) {
// Let the caller handle the interruption.
Thread.currentThread().interrupt();
break;
}
}
}
}
}
//*
chooser = chooserFactory.newChooser(children);
//包装处理代码
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
};
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
@Override
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
}
private static final class GenericEventExecutorChooser implements EventExecutorChooser {
// Use a 'long' counter to avoid non-round-robin behaviour at the 32-bit overflow boundary.
// The 64-bit long solves this by placing the overflow so far into the future, that no system
// will encounter this in practice.
private final AtomicLong idx = new AtomicLong();
private final EventExecutor[] executors;
GenericEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[(int) Math.abs(idx.getAndIncrement() % executors.length)];
}
}
上記のコードロジックの主な意味は次のとおりです。つまり、nThreadsが2の累乗の場合は、PowerOfTwoEventExecutorChooserを使用し、それ以外の場合はGenericEventExecutorChooserを使用します。これらのチューザーは両方ともnext()メソッドをオーバーライドします。next()メソッドの主な機能は、次の図に示すように、配列インデックスを周期的にシフトすることです。
この操作のロジックは実際には非常に単純です。つまり、インデックスがインクリメントされ、配列の長さがモジュロになるたびに:idx.getAndIncrement()%executors.length。しかし、非常に単純な配列インデックス操作でさえ、Nettyはそれを最適化するのに役立ちました。コンピュータの下部にあるため、&と%の方が効率的です。
さて、MultithreadEventExecutorGroupの処理ロジックを分析することで非常に明確になっているはずなので、簡単に要約すると次のようになります。
1.サイズnThreadsのSingleThreadEventExecutor配列を作成します。
2. nThreadsのサイズに応じて異なるチューザーを作成します。つまり、nThreadsが2の累乗である場合は、次を使用します。
PowerOfTwoEventExecutorChooser、それ以外の場合はGenericEventExecutorChooserを使用します。どのChooserを使用しても、それらの機能は同じです。つまり、適切なEventExecutorインスタンスが子配列から選択されます。
3. newChhild()メソッドを呼び出して、子配列を初期化します。
上記のコードによると、次のこともわかります。MultithreadEventExecutorGroupはEventExecutor配列を内部的に維持し、NettyのEventLoopGroup実装メカニズムは実際にはMultithreadEventExecutorGroup上に構築されています。NettyがEventLoopを必要とするときはいつでも、next()メソッドを呼び出して利用可能なEventLoopを取得します。上記のコードの最後の部分はnewChild()メソッドです。これは、EventLoopオブジェクトをインスタンス化することをタスクとする抽象メソッドです。そのコードを追跡しましょう。それを発見することができます。このメソッドはNioEventLoopGroupクラスに実装されており、その内容は非常に単純です。
@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
EventLoopTaskQueueFactory queueFactory = args.length == 4 ? (EventLoopTaskQueueFactory) args[3] : null;
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2], queueFactory);
}
実際、ロジックは非常に単純です。NioEventLoopオブジェクトをインスタンス化してから、NioEventLoopオブジェクトを返します。最後に、EventLoopGroup全体の初期化プロセスを要約します。
1. EventLoopGroup(実際にはMultithreadEventExecutorGroup)は、EventExecutorの子のタイプの配列を内部的に維持します。
そのサイズはnThreadsで、スレッドプールを構成します。
2. NioEventLoopGroupをインスタンス化するときにスレッドプールサイズを指定すると、nThreadsが指定された値になり、その逆も同様です。
プロセッサコアの数* 2。
3. newChild()抽象メソッドがMultithreadEventExecutorGroupで呼び出され、子配列が初期化されます。
4.抽象メソッドnewChild()はNioEventLoopGroupに実装され、NioEventLoopインスタンスを返します。
5. NioEventLoopプロパティの割り当て:
プロバイダー:NioEventLoopGroupコンストラクターのSelectorProvider.provider()を介してSelectorProviderを取得します。
セレクター:NioEventLoopコンストラクターで、provider.openSelector()メソッドを呼び出してセレクターオブジェクトを取得します。
セレクターに登録されているチャンネル
前の分析で、チャネルはBootstrapのinitAndRegister()で初期化されると述べましたが、このメソッドは初期化されたChanneをNioEventLoopのセレクターに登録します。次に、チャンネル登録のプロセスを分析しましょう。
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()。register()メソッドが呼び出され、チャネルがセレクターに登録されます。フォローアップを続けると、
コールチェーンは次のとおりです。
AbstractChannel $ AbstractUnsafe.register()メソッドでは正確に何が行われますか?
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
ObjectUtil.checkNotNull(eventLoop, "eventLoop");
if (isRegistered()) {
promise.setFailure(new IllegalStateException("registered to an event loop already"));
return;
}
if (!isCompatible(eventLoop)) {
promise.setFailure(
new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
return;
}
AbstractChannel.this.eventLoop = eventLoop;
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
logger.warn(
"Force-closing a channel whose registration task was not accepted by an event loop: {}",
AbstractChannel.this, t);
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
}
まず、eventLoopをChannelのeventLoopプロパティに割り当てます。これにより、このeventLoopオブジェクトが実際に存在することがわかります。
MultithreadEventLoopGroupのnext()メソッドによって取得されます。以前の分析によると、next()メソッドによって返されるeventLoopオブジェクトはNioEventLoopのインスタンスであると判断できます。次に、register()メソッドはregister0()メソッドを呼び出します。
private void register0(ChannelPromise promise) {
try {
// check if the channel is still open as it could be closed in the mean time when the register
// call was outside of the eventLoop
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = neverRegistered;
doRegister();
neverRegistered = false;
registered = true;
// Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
// user may already fire events through the pipeline in the ChannelFutureListener.
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
pipeline.fireChannelRegistered();
// Only fire a channelActive if the channel has never been registered. This prevents firing
// multiple channel actives if the channel is deregistered and re-registered.
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
// This channel was registered before and autoRead() is set. This means we need to begin read
// again so that we process inbound data.
//
// See https://github.com/netty/netty/issues/4805
beginRead();
}
}
} catch (Throwable t) {
// Close the channel directly to avoid FD leak.
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
register0()メソッドは、AbstractNioChannelのdoRegister()メソッドを呼び出します
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
// Force the Selector to select now as the "canceled" SelectionKey may still be
// cached and not removed because no Select.select(..) operation was called yet.
eventLoop().selectNow();
selected = true;
} else {
// We forced a select operation on the selector before but the SelectionKey is still cached
// for whatever reason. JDK bug ?
throw e;
}
}
}
}
javaChannel()メソッドが以前に知られていることを確認してください。これはJava NIOSocketChannelオブジェクトを返します。ここではI
このSocketChannelをeventLoopに関連付けられたセレクターに登録しました。
チャネルの登録プロセスを要約してみましょう。
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セレクターに登録し、現在のチャネルを添付ファイルとしてSocketChannelに関連付けます。 。
一般に、チャネル登録プロセスによって行われる作業は、チャネルを対応するEventLoopに関連付けることであるため、これはNettyにも反映されます。
各チャネルは特定のEventLoopに関連付けられており、このチャネルのすべてのIO操作はこのEventLoopで実行されます。チャネルとEventLoopが関連付けられている場合、基盤となるJava NIOのSocketChannelオブジェクトのregister()は引き続き呼び出されます。メソッド、基礎となるJavaNIOのSocketChannelを指定されたセレクターに登録します。これらの2つのステップにより、Netty toChannelの登録プロセスが完了します。
ハンドラーを追加するプロセス
Nettyの強力で柔軟な機能の1つは、Pipelineに基づくカスタムハンドラメカニズムです。これに基づいて、さまざまなハンドラーを自由に組み合わせて、プラグインの追加などのビジネスロジックを完成させることができます。たとえば、HTTPデータを処理する必要がある場合は、パイプラインの前にHTTPエンコードとデコードのハンドラーを追加してから、独自のビジネスロジックハンドラーを追加して、ネットワーク上のデータフローがパイプラインを通過するのと同じになるようにすることができます。ハンドラーを通過し、エンコードとデコードを実行して、最終的にカスタムハンドラーに到着します。
そういえば、友達の中には間違いなく好奇心が強い人もいるでしょう。このパイプラインメカニズムはとても強力なので、どのように実装されているのでしょうか。ここでは詳しく説明しません。このセクションでは、最初に簡単な経験から始めます。カスタムハンドラーをChannelPipelineに追加する方法とタイミング。まず、次のユーザーコードスニペットを確認します。
ChannelInitializer<SocketChannel> initializer = new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//先添加websocket相关的编解码器和协议处理器
pipeline.addLast("msgDecoder", new ProtocolDecoder());// 解码器
pipeline.addLast("msgEncoder", new ProtocolEncoder());// 编码器
pipeline.addLast("idleStateHandler", new IdleStateHandler(serverConfig.getAllIdleSecond(), 0, 0));// 定时器,秒
pipeline.addLast("handler", new WebsocketRouterHandler());// 消息总处理器WebsocketRouterHandler
}
};
このコードスニペットは、ハンドラーの追加機能を実装します。Bootstrapのhandler()メソッドがChannelHandlerを受け取り、渡すパラメーターは、抽象クラスChannelInitializerから派生した匿名クラスであり、もちろんChannelHandlerインターフェイスも実装していることがわかります。ChannelInitializerクラスの謎を見てみましょう。
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelInitializer.class);
// We use a Set as a ChannelInitializer is usually shared between all Channels in a Bootstrap /
// ServerBootstrap. This way we can reduce the memory usage compared to use Attributes.
private final Set<ChannelHandlerContext> initMap = Collections.newSetFromMap(
new ConcurrentHashMap<ChannelHandlerContext, Boolean>());
/**
* This method will be called once the {@link Channel} was registered. After the method returns this instance
* will be removed from the {@link ChannelPipeline} of the {@link Channel}.
*
* @param ch the {@link Channel} which was registered.
* @throws Exception is thrown if an error occurs. In that case it will be handled by
* {@link #exceptionCaught(ChannelHandlerContext, Throwable)} which will by default close
* the {@link Channel}.
*/
protected abstract void initChannel(C ch) throws Exception;
@Override
@SuppressWarnings("unchecked")
public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
// Normally this method will never be called as handlerAdded(...) should call initChannel(...) and remove
// the handler.
if (initChannel(ctx)) {
// we called initChannel(...) so we need to call now pipeline.fireChannelRegistered() to ensure we not
// miss an event.
ctx.pipeline().fireChannelRegistered();
// We are done with init the Channel, removing all the state for the Channel now.
removeState(ctx);
} else {
// Called initChannel(...) before which is the expected behavior, so just forward the event.
ctx.fireChannelRegistered();
}
}
/**
* Handle the {@link Throwable} by logging and closing the {@link Channel}. Sub-classes may override this.
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (logger.isWarnEnabled()) {
logger.warn("Failed to initialize a channel. Closing: " + ctx.channel(), cause);
}
ctx.close();
}
/**
* {@inheritDoc} If override this method ensure you call super!
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isRegistered()) {
// This should always be true with our current DefaultChannelPipeline implementation.
// The good thing about calling initChannel(...) in handlerAdded(...) is that there will be no ordering
// surprises if a ChannelInitializer will add another ChannelInitializer. This is as all handlers
// will be added in the expected order.
if (initChannel(ctx)) {
// We are done with init the Channel, removing the initializer now.
removeState(ctx);
}
}
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
initMap.remove(ctx);
}
@SuppressWarnings("unchecked")
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
if (initMap.add(ctx)) { // Guard against re-entrance.
try {
initChannel((C) ctx.channel());
} catch (Throwable cause) {
// Explicitly call exceptionCaught(...) as we removed the handler before calling initChannel(...).
// We do so to prevent multiple calls to initChannel(...).
exceptionCaught(ctx, cause);
} finally {
ChannelPipeline pipeline = ctx.pipeline();
if (pipeline.context(this) != null) {
pipeline.remove(this);
}
}
return true;
}
return false;
}
private void removeState(final ChannelHandlerContext ctx) {
// The removal may happen in an async fashion if the EventExecutor we use does something funky.
if (ctx.isRemoved()) {
initMap.remove(ctx);
} else {
ctx.executor().execute(new Runnable() {
@Override
public void run() {
initMap.remove(ctx);
}
});
}
}
}
ChannelInitializerは抽象クラスであり、抽象メソッドinitChannel()があります。私たちが見た匿名クラスはこのメソッドを実装しています。
そして、このメソッドにカスタムハンドラーを追加します。では、initChannel()はどこで呼び出されますか?実際、それはChannelInitializerのchannelRegistered()メソッドにあります。
次に、channelRegistered()メソッドに注目します。上記のソースコードから、channelRegistered()メソッドで、initChannel()メソッドが呼び出され、カスタムハンドラーがChannelPipelineに追加されてから、ctx.pipeline()。remove(this)メソッドが呼び出されることがわかります。 ChannelPipelineから削除されました。上記の分析プロセスを次の図に示します。
当初、ChannelPipelineには、head、tail、および追加したChannelInitializerの3つのハンドラーしかありませんでした。
次に、initChannel()メソッドが呼び出された後、カスタムハンドラーが追加されます。