Анализ исходного кода 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
зарегистрирован в первый разWorker
On, даже если канал настроен на неавтоматическое чтение, операция чтения будет выполнена, другими словами,Каждый канал сделает хотя бы одно чтение(Если клиенту вообще нечего писать на сервер, то конечно не читает).
Роли и сценарии использования
Поговорив о принципе, поговорим о том, когда отключать это автоматическое считывание, а когда снова включать.
Это на самом деле для управления потоком. Например, на нашем сервере есть пул потоков с фиксированным размером 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
Если вы читаете данные с сервера, вы, естественно, не знаете, что клиент запросил его закрыть. Здесь обратите особое внимание.