NIOベースクラス
始める前に、あなたは、Java NIOの次のクラスを理解する必要があります
- セレクタ
- SelectableChannel
- SelectionKey
あなたは見えるかもしれJAVA NIOの基本的なクラスを
登録プロセスサーバーを起動
そして、サーバの前面には、登録プロセスを開始するには、
public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
..
AbstractChannel.this.eventLoop = eventLoop;
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
//注册的时候会往taskQueue里放入一个注册的线程
eventLoop.execute(new Runnable() {
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
...
}
}
}
private void register0(ChannelPromise promise) {
doRegister();
}
}
登録はしているServerSocketChannel
ように登録selector
レーン
//为什么这里是0而是SelectionKey.OP_ACCEPT?
//在AbstractNioChannel.doBeginRead里会改,这个bind里再分析
protected void doRegister() throws Exception {
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
}
実際には、以前の分析が追加されて登録されていた内部。NioEventLoop
taskQueue
見てNioEventLoop
上記のこのタスクの登録を実行する方法である()内で実行します。
二つの方法のNioEventLoopのrunメソッドは、実際には、実装
-
processSelectedKeys()を実行するI / Oタスクを等、受け入れ接続し、読み取り、書き込みなどすなわち準備とSelectionKeyイベント、
-
runAllTasks()を実行する非IOタスクはタスクタスクキュー、例えばレジスタ0、bind0タスクに加え
次のコード注釈を考えてみましょう
protected void run() {
for (;;) {
try {
switch
//计算出每次循环要干什么事
(selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
//处理I/O操作
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
default:
// fallthrough
}
cancelledKeys = 0;
needsToSelectAgain = false;
//一次处理包括了processSelectedKeys()+runAllTasks() ioRatio是指processSelectedKeys()即处理IO事件的占据的比例, 默认是50,
final int ioRatio = this.ioRatio;
//如果是100,那就是处理完所有的I/O 再处理完所有的非I/O
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
runAllTasks();
}
} else {
//如果不是100,那就是I/O处理了时间为t1,非I/O处理的时间为t1*(100 - ioRatio) / ioRatio,
//当然默认情况下I/O处理了时间为t1,则非I/O处理的时间也只能为t1
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
// Always handle shutdown even if the loop processing threw an exception.
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
フレーズの分析
selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())
selectStrategy是DefaultSelectStrategy
final class DefaultSelectStrategy implements SelectStrategy {
static final SelectStrategy INSTANCE = new DefaultSelectStrategy();
private DefaultSelectStrategy() { }
//hasTasks为false,那表示taskQueue里没任务,即没非IO任务,那就执行IO任务.
//那为什么hasTasks为true要selectSupplier.get()?这个在后面会解答
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}
次のようにDefaultSelectStrategyが定義されています
private final IntSupplier selectNowSupplier = new IntSupplier() {
@Override
public int get() throws Exception {
return selectNow();
}
};
int selectNow() throws IOException {
try {
//这方法不会阻塞会立即返回
//返回的值是指有多少个channel ready for handle
return selector.selectNow();
} finally {
// restore wakup state if needed
if (wakenUp.get()) {
selector.wakeup();
}
}
}
ロジックは何のミッションが存在しないタスクキューで一緒に見ている、私は何の操作コレクションはありません任意のセレクタで確認しに行ってきました。)最初addTask(以降)(hasTasksように、登録サーバーチャネルに追加チャネルセレクタが登録されていない。真であるので、selector.selectNow()
リターンが0です。この場合、それはに直接移動します
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
非I / Oを処理するために属するレジスタは、つまり、私たちはしますrunAllTasks
に対処します。
次runAllTasks()のコメントを考えてみましょう
//取消息然后再`safeExecute(task)`直接运行。运行完channel就注册到了selector上了。
protected boolean runAllTasks(long timeoutNanos) {
fetchFromScheduledTaskQueue();
Runnable task = pollTask();
if (task == null) {
afterRunningAllTasks();
return false;
}
final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
long runTasks = 0;
long lastExecutionTime;
for (;;) {
//跟进去就是task.run(),注意不是start()
safeExecute(task);
...
}
afterRunningAllTasks();
this.lastExecutionTime = lastExecutionTime;
return true;
}
runAllTasksは、実行ServerSocketChannel
それはに登録されているselector
こと。
上述runAllTasks()は、非I / Oタスクの処理であるとして、それは、次であるprocessSelectedKeys()
I / Oロジック次コード処理
private void processSelectedKeys() {
//debug 发现selectedKeys非空,那这个是怎么来的?
if (selectedKeys != null) {
processSelectedKeysOptimized(selectedKeys.flip());
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
}
SelectedKeys最初の質問は、割り当てが行くところこれは、空ではないでしょうか?
JDKを得る際にSelector
selectedKeysに置き換えたときの参照で定義されてSelectorImpl selectedKeys
とpublicSelectedKeys
、見れば
JAVA NIOの基本的なクラスをセレクタで、あなたはセレクタがそのうちの1つのセットの3種類を、維持知っているselectedKeys
選択の実装では、 ()またはselectNow()または登録証明書を操作することになるとき(長い)を選択し(SelectionKey
)コレクションにselectedKeys
は、このフィールド。
次のようにあるいはコードであります
private Selector openSelector() {
final Selector selector;
try {
selector = provider.openSelector();
} catch (IOException e) {
throw new ChannelException("failed to open a new selector", e);
}
if (DISABLE_KEYSET_OPTIMIZATION) {
return selector;
}
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
return Class.forName(
"sun.nio.ch.SelectorImpl",
false,
PlatformDependent.getSystemClassLoader());
} catch (ClassNotFoundException e) {
return e;
} catch (SecurityException e) {
return e;
}
}
});
if (!(maybeSelectorImplClass instanceof Class) ||
// ensure the current selector implementation is what we can instrument.
!((Class<?>) maybeSelectorImplClass).isAssignableFrom(selector.getClass())) {
if (maybeSelectorImplClass instanceof Exception) {
...
}
return selector;
}
final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;
Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
//要替换的字段为selectedKeys
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
//要替换的字段为publicSelectedKeys
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
selectedKeysField.setAccessible(true);
publicSelectedKeysField.setAccessible(true);
//执行替换 selectedKeysField.set(selector, selectedKeySet);
//执行替换 publicSelectedKeysField.set(selector, selectedKeySet);
return null;
} catch (NoSuchFieldException e) {
...
}
}
});
if (maybeException instanceof Exception) {
...
} else {
selectedKeys = selectedKeySet;
logger.trace("instrumented a special java.util.Set into: {}", selector);
}
return selector;
}
public abstract class SelectorImpl extends AbstractSelector {
protected Set<SelectionKey> selectedKeys = new HashSet();
private Set<SelectionKey> publicSelectedKeys;
}
これも説明した理由上記質問hasTasks = trueの場合、なぜ直接固定値に、例えばhasTasks ? 0 : SelectStrategy.SELECT;
、むしろselectSupplier.getに(下)。
selectSupplier.getは()にこの点をNioEventLoop.selectedKeysますのでSelectorImpl.selectedKeys
。
元selectedKeysはHashSetので、交換後に次のクラス、またAbstractSet継承し、それが直接交換することができます。ただ、リファレンスのように、なぜフリップ()一時的に理解されていないがあるのはなぜなぜ?二つの配列keysA、keysAを行う?置き換えます
このクラスは非常に簡単です、addメソッドを書き換えます
final class SelectedSelectionKeySet extends AbstractSet<SelectionKey> {
private SelectionKey[] keysA;
private int keysASize;
private SelectionKey[] keysB;
private int keysBSize;
private boolean isA = true;
SelectedSelectionKeySet() {
keysA = new SelectionKey[1024];
keysB = keysA.clone();
}
@Override
public boolean add(SelectionKey o) {
if (o == null) {
return false;
}
if (isA) {
int size = keysASize;
keysA[size ++] = o;
keysASize = size;
if (size == keysA.length) {
doubleCapacityA();
}
} else {
int size = keysBSize;
keysB[size ++] = o;
keysBSize = size;
if (size == keysB.length) {
doubleCapacityB();
}
}
return true;
}
private void doubleCapacityA() {
SelectionKey[] newKeysA = new SelectionKey[keysA.length << 1];
System.arraycopy(keysA, 0, newKeysA, 0, keysASize);
keysA = newKeysA;
}
private void doubleCapacityB() {
SelectionKey[] newKeysB = new SelectionKey[keysB.length << 1];
System.arraycopy(keysB, 0, newKeysB, 0, keysBSize);
keysB = newKeysB;
}
SelectionKey[] flip() {
if (isA) {
isA = false;
keysA[keysASize] = null;
keysBSize = 0;
return keysA;
} else {
isA = true;
keysB[keysBSize] = null;
keysASize = 0;
return keysB;
}
}
@Override
public int size() {
if (isA) {
return keysASize;
} else {
return keysBSize;
}
}
}
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}
私はなぜselectedKeysが空を知って、どこに投稿します。このコードは、processSelectedKeysOptimized(selectedKeys.flip())に行くだろう。
private void processSelectedKeys() {
if (selectedKeys != null) {
processSelectedKeysOptimized(selectedKeys.flip());
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
}
分析processSelectedKeysOptimized
private void processSelectedKeysOptimized(SelectionKey[] selectedKeys) {
//死循环,当没任务的时候跳出
for (int i = 0;; i ++) {
final SelectionKey k = selectedKeys[i];
if (k == null) {
break;
}
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);
}
...
}
}
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) {
...
return;
}
if (eventLoop != this || eventLoop == null) {
return;
}
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) {
unsafe.read();
if (!ch.isOpen()) {
// Connection already closed - no need to handle write.
return;
}
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
スレッド・スタック解析
スレッドスタックからソースコードの上記の分析は、より明確になります
telnet 127.0.0.1 8888の後に、コードを呼び出すスレッドスタック以下。
"boss-1-1@1468" prio=5 tid=0xd nid=NA runnable
java.lang.Thread.State: RUNNABLE
at io.netty.bootstrap.ServerBootstrap$ServerBootstrapAcceptor.channelRead(ServerBootstrap.java:254)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:373)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:351)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1334)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:373)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:926)
at io.netty.channel.nio.AbstractNioMessageChannel$NioMessageUnsafe.read(AbstractNioMessageChannel.java:93)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:651)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:574)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:488)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:450)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:873)
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:144)
at java.lang.Thread.run(Thread.java:748)
下に行わ構文解析
1。
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:450)
実行方法は、死のサイクルである
2。
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:651)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:574)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:488)
(後)runTaskは()NioEventLoop.selectedKeysにselector.selectNowに記入します
3。
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1334)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:373)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:926)
at io.netty.channel.nio.AbstractNioMessageChannel$NioMessageUnsafe.read(AbstractNioMessageChannel.java:93)
これは、パイプライン、HeadContext.channelReadの実装からHeadContextを見つけるために、パイプラインでNioServerChannelを見つけることです、それは以下の呼び出し
public ChannelHandlerContext fireChannelRead(final Object msg) {
invokeChannelRead(findContextInbound(), msg);
return this;
}
private AbstractChannelHandlerContext findContextInbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.next;
} while (!ctx.inbound);
return ctx;
}
実際には、二重に次のコンテキストでリストをリンクされ、
at io.netty.bootstrap.ServerBootstrap$ServerBootstrapAcceptor.channelRead(ServerBootstrap.java:254)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:373)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:351)
次のコンテキストがServerBootstrapAcceptorを見つけることです、これはServerBootstrapAcceptorは内部パイプラインに追加どこにあるのでしょうか?
//初始化channel里
void init(Channel channel) throws Exception {
....
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
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(
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
この例では、我々は3つのしかコンテキストを持って、次のように
どこ設定値を理解しています。
当然のことながら、私たちはServerBootstrapAcceptor.channelReadを何方法を分析するために行くだろう
1.ブレークポイントServerBootstrapAcceptor 237行の
子供がio.netty.channel.socket.nio.NioSocketChannelのある
child.pipeline()addLast(childHandler);.
ChildHandler = $ rechard.learn.netty.demo.welcome.WelcomeServer。1はWelcomeServerで定義されています
2.ブレークポイント254 ServerBootstrapAcceptor
childGroupその労働者EventLoopGroup
childGroup.register(子)へ
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
スレッド内のレジスタワーカーeventLoopGroup後にイベントループを開始します
変更点観測スレッド
より良い観測のために、次のように2 EventLoopGroupでwelcomeServerを読みます
EventLoopGroup bossGroup = new NioEventLoopGroup(0,new DefaultThreadFactory("boss"));
EventLoopGroup workerGroup = new NioEventLoopGroup(0,new DefaultThreadFactory("worker"));
完全なサーバの起動には、一つだけボス-1-1がある
のtelnet 127.0.0.1 8888の後、あなたは1つの以上のワーカー-3-1を参照することができ
、その後、CMDは、telnet 127.0.0.1 8888開き
、次のように網状スレッドモデルを
概要
中()メソッドを実行するための最初の実行
- デフォルトのswichに来る0を返さなければなりませんhasTask()が真であるが、selector.selectNow()ので、タスクキューがあり、サーバータスクを登録しました
- I / Oイベント、すなわち処理方法processSelectedKeys()を実行し、非I / O処理のイベントを実行する、すなわちrurunAllTasks(長いtimeoutNanos)
- runAllTasks(ロングtimeoutNanos)タスクキューセレクタに登録されたタスクを登録する作業に取りに行くために。そのようなのServerSocketChannelは、セレクタに登録します。
- telnet 127.0.0.1 8888のSocketChannel接続が存在するであろう、セレクタ中)(selectNowでこのSelectKeyに取得します
- 登録接続を処理するServerBootstrapAcceptor.channelReadにおけるパイプラインのNioServerSocketChannel
- 登録プロセスは、これは同じであるchildGroupに登録されたSocketChannel、登録プロセスとNioServerSocketChannelあります。
バインドに左
bindメソッド
bindメソッドは、比較的簡単です
通常のNIOサーバーの登録コードは次のようです
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()
Selector selector = Selector.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
しかし、実際には網状であります
private ChannelFuture doBind(final SocketAddress localAddress) {
//1.先注册channel到selector上
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
//2.如果注册成功了
if (regFuture.isDone()) {
ChannelPromise promise = channel.newPromise();
//3.再绑定到localAddress
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
...
}
}
doBind0このメソッドは、チャネルがをlocalAddressにバインドされ、比較的簡単です。この方法は、直接結合されていないことに注意してくださいが、非I / Oなどのタスクは、タスクキュー年に加えます。
これは、呼び出し元のスレッドスタックを次の
次のコード
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
それはスタートが0に設定されている理由を説明したが、後に事実となり、SelectionKey.OP_ACCEPTを設定します。
//为什么这里是0而是SelectionKey.OP_ACCEPT?
//在AbstractNioChannel.doBeginRead里会改,这个bind里再分析
protected void doRegister() throws Exception {
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
}