Анализ исходного кода Netty - АВТОЧИТАТЬ

задняя часть исходный код Netty
Анализ исходного кода Netty - АВТОЧИТАТЬ

Анализ исходного кода Netty - АВТОЧИТАТЬ

предисловие

Это установлено вChannelАтрибут на главном управляющем канале автоматически считывается. Вероятно, для многих новичков все этоChannelАвтоматическое чтение сбивает с толку.В этой статье в основном рассматривается, как работает это автоматическое чтение и когда отключить автоматическое чтение (автоматическое чтение включено по умолчанию).

код выше

Это можно проследить до нашей операции чтения.В качестве примера возьмем сервер.Сначала на сервере есть сервер с именем Boss.Reacotrрезьба, постоянное вращениеACCEPTсобытие, если оно повернуто, создать событиеChannelи зарегистрируйтесьREADсобытие, поместите этоChannelприсвоено вызываемомуWorkerизReactorнить. Этой логики не скажешь, подробное содержание видно раньше:Босс и работник.

назначен наWorkerизChannel, в режиме NIO естьNioSocketChannel, давайте посмотрим на его операцию чтения:

// sth...
try {
do {
byteBuf = allocHandle.allocate(allocator);
//真正的把数据读取到ByteBuf里
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
if (close) {
readPending = false;
}
break;
}

allocHandle.incMessagesRead(1);
readPending = false;
// 触发管道的读操作,处理数据
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
// 读操作是否结束
} while (allocHandle.continueReading());

allocHandle.readComplete();
// 这里触发读完成操作
pipeline.fireChannelReadComplete();

if (close) {
closeOnRead(pipeline);
}
}
// sth...

Мы рассмотрели большинство шагов ранее, вот посмотрите на этоpipeline.fireChannelReadComplete. здесь будет изHeadContextУзел начинает проходить назад, см. нижеHeadContext#channelReadComplete:

ctx.fireChannelReadComplete();
readIfIsAutoRead();

private void readIfIsAutoRead() {
if (channel.config().isAutoRead()) {
channel.read();
}
}

посмотри это здесьreadIfIsAutoRead, похоже, происходит автоматическое чтение похожих слов. Конкретное содержание метода также очень просто, еслиChannelКогда установлено автоматическое чтение, выполнитьchannel.read(). Об этом методе мы также говорили ранее и, наконец, перешли кtail, то изtailПерейти кhead, затем выполнитеunsafe.beginRead(), что в конечном итоге дает этоNioSocketChannelЗарегистрироватьREADмероприятие.

После регистрации этот Канал может оставаться вWorker ReactorВ потоке продолжайте выполнять операцию чтения.

Обратите внимание, что конвейер может продолжить чтение после операции чтения, даже если наша тема -AUTOREAD, я не буду вдаваться в подробности, как настроить автоматическое чтение. Тогда мы можем сделать вывод о роли этого автоматического чтения в этот момент:Следует ли продолжать чтение после первой операции чтения. Я должен обратить на это внимание, если этоNioSocketChannelзарегистрирован в первый разWorkerOn, даже если канал настроен на неавтоматическое чтение, операция чтения будет выполнена, другими словами,Каждый канал сделает хотя бы одно чтение(Если клиенту вообще нечего писать на сервер, то конечно не читает).

Роли и сценарии использования

Поговорив о принципе, поговорим о том, когда отключать это автоматическое считывание, а когда снова включать.

Это на самом деле для управления потоком. Например, на нашем сервере есть пул потоков с фиксированным размером 500 потоков. В это время у нас может быть много клиентских подключений, и пул потоков сразу переполнен, в это время мы можем отключить автоматическое чтение некоторых пайпов.

Приведенный выше сценарий может быть не очень подходящим, я могу установить конвейер в流控Handler. этоHandlerКаждый раз при чтении данных мы смотрим на размер пула потоков, если он превышает определенное значение, мы закрываем автоматическое чтение этого пайпа:channel.config().setAutoRead(false). Затем продолжайте проходить. Когда чтение конвейера закончится, оно не начнет следующее чтение, поэтому, конечно, не будет занимать наш пул потоков.

Здесь мы обращаем внимание на проблему, мы неChannelчитать данные в нем, не означает этоChannelзакрыто.我们可以用一个定时任务检测我们的线程池,如果低于某个值,我们调用Channelизchannel.config().setAutoRead(true)включить автоматическое чтение.

Здесь у вас может возникнуть вопрос, как мы уже говорили, по телефонуreadIfIsAutoRead(ИлиfireChannelReadComplete) находится в конце чтения, но мы поставили запрет на автоматическое чтение раньше, поэтому естественно его никто не выполнитctx.fireChannelReadComplete, теперь ставлюChannelБесполезно включать автоматическое чтение, потому что никто не может его запуститьreadIfIsAutoReadдай этоChannelРегистрация читается автоматически.

Вот еще гонять код, смотриDefaultChannelConfig#setAutoRead:

boolean oldAutoRead = AUTOREAD_UPDATER.getAndSet(this, autoRead ? 1 : 0) == 1;
if (autoRead && !oldAutoRead) {
channel.read();
} else if (!autoRead && oldAutoRead) {
autoReadCleared();
}
return this;

Здесь мы видим, что если для автоматического чтения конвейера установлено значение true, он будет активно вызывать один раз.channel.read()Для регистрации на событие READ.

Уведомление

Но есть одна вещь, о которой следует помнить при использовании автоматического чтения.

Если автоматическое чтение отключено, одноранговый узел отправляетFINКогда прикладной уровень получателя не знает об этом. Одним из следствий этого является то, что одноранговый узел отправляетFIN, то ядро ​​преобразует этоsocketсостояние становитсяCLOSE_WAIT. Но поскольку прикладной уровень не может его воспринять, прикладной уровень не вызываетclose. ТакойsocketдолгосрочныйCLOSE_WAITусловие. Особенно для некоторых приложений, использующих пул соединений, если вы возвращаете соединение в пул соединений, вы должны помнить, что автоматическое чтение должно быть включено. В противном случае будет большое количество подключений вCLOSE_WAITусловие.

Грубо говоря, если сервер отключил автоматическое чтение, но после его отключенияsocketПолучил команду закрытия от клиента, в это время прикладной уровень неChannelЕсли вы читаете данные с сервера, вы, естественно, не знаете, что клиент запросил его закрыть. Здесь обратите особое внимание.