Серия анализа исходного кода Netty (1) Обзор Netty

Java Netty
Серия анализа исходного кода Netty (1) Обзор Netty

Это пятый день моего участия в августовском испытании обновлений, подробности о мероприятии:Испытание августовского обновления

предисловие

оNettyВ последнее время я посмотрел много связанных видео и книг, и я многому научился.Я надеюсь поделиться с вами тем, что я знаю, усердно работать и расти вместе. Раньше мы былиJava IO,BIO,NIO,AIOБыл проведен анализ, и ссылки на соответствующие статьи выглядят следующим образом:

Углубленный анализ Java IO (1) Обзор

Углубленный анализ Java IO (2) BIO

Углубленный анализ Java IO (3) NIO

Углубленный анализ Java IO (4) AIO

В этой статье мы начнемNettyДля более глубокого анализа давайте сначала разберемсяJAVA NIO,AIOнеадекватности.

Боль Java Native API

несмотря на то чтоJAVA NIOа такжеJAVA AIOПлатформа обеспечивает поддержку мультиплексирования ввода-вывода/асинхронного ввода-вывода, но не обеспечивает хорошей инкапсуляции «информационного формата» верхнего уровня. Внедрить настоящее веб-приложение с помощью этих API непросто.

JAVA NIOа такжеJAVA AIOОн не обеспечивает обработку отключения и повторного подключения, сетевой флэш-памяти, чтения и записи половинных пакетов, кэширования сбоев, перегрузки сети и ненормального потока кода и т. д., которые требуют от разработчиков выполнения соответствующей работы.

AIOНа практике нетNIOлучше.AIOСуществуют разные реализации на разных платформах, и в системе Windows используется технология асинхронного ввода-вывода:IOCP;Поскольку в Linux нет такой технологии асинхронного ввода-вывода, она используетepollМоделируйте асинхронный ввод-вывод. Так что производительность AIO под Linux не идеальна. AIO также не поддерживает UDP.

Подводя итог, можно сказать, что в реальных крупномасштабных интернет-проектах собственный API Java широко не используется и заменяется сторонним фреймворком Java, которыйNetty.

Преимущества Nettyty

Предоставлено НеттиАсинхронная среда веб-приложений, управляемая событиями, и инструменты для быстрой разработки высокопроизводительных и надежных веб-серверов и клиентских программ.

Неблокирующий ввод-вывод

Нетти основана наJava NIOПлатформа веб-приложений, реализованная на основе API, которую можно использовать для быстрой и простой разработки веб-приложений, таких как серверные и клиентские программы. Netty значительно упрощает процесс разработки сетевых программ, таких как разработка сервисов Socket для TCP и UDP.

Благодаря API на основе NIO Netty может предоставлять неблокирующиеI/Oоперации, что значительно повышает производительность. В то же время Netty внутренне инкапсулирует сложность Java NIO API и обеспечивает обработку пула потоков, что делает разработку приложений NIO чрезвычайно простой.

богатый протокол

Netty предоставляет простой и удобный в использовании API, но это не означает, что приложение будет сложно поддерживать и иметь низкую производительность. Netty — это хорошо спроектированная платформа, которая во многом опирается на реализацию многих протоколов, таких как FTP, SMTP, HTTP и многих традиционных двоичных и текстовых протоколов.

Netty поддерживает многофункциональные сетевые протоколы, такие какTCP,UDP,HTTP,HTTP/2,WebSocket,SSL/TLSи т. д. Эти протоколы реализованы «из коробки», поэтому разработчики Netty могут добиться простоты разработки, высокой производительности и стабильности без потери гибкости.

Асинхронный и управляемый событиями

Netty — это асинхронная управляемая событиями платформа, в которой реализованы всеI/Oоперации асинхронны, всеI/OВызов вернется немедленно, нет гарантии, что вызов будет успешным или нет, но вызов вернетсяChannelFuture. Netty будет проходитьChannelFutureУведомляет об успешности вызова, сбое или его отмене.

В то же время Netty управляется событиями, и вызывающая сторона не может получить результат немедленно, но через механизм мониторинга событий пользователь может легко получить его активно или получить через механизм уведомления.I/Oрезультат операции.

когдаFutureКогда объект только что создан, он находится в незавершенном состоянии, и вызывающая сторона может передать возвращенноеChannelFutureЧтобы получить статус выполнения операции, а затем зарегистрировать функцию прослушивателя для выполнения завершенной операции, общие операции следующие:

  • пройти черезisDoneспособ определить, завершена ли текущая операция.
  • пройти черезisSuccessметод, чтобы определить, была ли текущая завершенная операция успешной.
  • пройти черезgetCauseметод, чтобы получить причину, по которой завершенная текущая операция не удалась.
  • пройти черезisCancelledметод, чтобы определить, была ли отменена текущая завершенная операция.
  • пройти черезaddListenerметод для регистрации прослушивателя после завершения операции (isDoneметод возвращает завершенный), указанный слушатель будет уведомлен; еслиfutureКогда объект завершен, он уведомляет указанного слушателя.

Например, в следующем коде привязка порта является асинхронной операцией.При обработке операции привязки будет вызвана соответствующая логика обработки прослушивателя.

serverBootstrap.bind(port).addListener(future -> {
    if(future.isSuccess()){
        System.out.println("端口绑定成功!");
    }else {
        System.out.println("端口绑定失败!");
    }
});

по сравнению с традиционной блокировкойI/O, преимущество асинхронной обработки Netty заключается в том, что она не вызывает блокировку потока, и потокI/OВо время работы могут выполняться другие программы, которые будут более стабильными и будут иметь более высокую пропускную способность в ситуациях с высокой степенью параллелизма.

Хорошо продуманный API

Netty с самого начала предоставила пользователям лучший API и дизайн реализации.

Например, при небольшом количестве пользователей может быть выбран традиционный API блокировки, ведь использовать API блокировки будет проще, чем Java NIO. Однако проблемы возникают, когда объем трафика растет в геометрической прогрессии и серверу необходимо обрабатывать тысячи одновременных клиентских подключений. В этом случае можно попробовать Java NIO, но сложный программный интерфейс NIO Selector отнимает много времени и в конечном итоге препятствует быстрой разработке.

Нетти предоставляетchannelУнифицированный асинхронныйI/OИнтерфейс программирования, который абстрагирует все операции двухточечной связи. То есть, если приложение основано на определенной транспортной реализации Netty, то это же приложение может работать и на другой транспортной реализации Netty.ChannelОбщие субинтерфейсы:

image-20210804105936809

Богатый буфер для достижения

Netty использует собственный API кеша вместо Java NIO.ByteBufferдля представления непрерывной последовательности байтов. а такжеByteBufferПо сравнению с этим методом он имеет очевидные преимущества.

Netty использует новый тип буфераByteBuf, и предназначен для решения с нуляByteBufferПроблема, но и для удовлетворения ежедневных потребностей разработки веб-приложений типа буфера.

Netty обладает следующими важными особенностями:

  • Позволяет использовать пользовательские типы буферов.
  • Прозрачная реализация нулевого копирования встроена в составной тип буфера.
  • Стандартные типы динамических буферов с такими функциями, какStringBufferТа же емкость динамической буферизации.
  • больше не нужно звонитьflip()метод.
  • обычно имеет соотношениеByteBufferБолее быстрое время отклика.

Эффективная передача по сети

Собственная сериализация Java в основном имеет следующие недостатки:

  • Не может скрещивать языки.

  • Поток кода слишком велик после сериализации.

  • Производительность после сериализации слишком низкая.

В отрасли существует множество фреймворков для решения вышеуказанных проблем, таких какGoogle Protobuf,JBoss Marshalling,Facebook ThriftЖдать. Для этих фреймворков Netty предоставляет соответствующие пакеты для интеграции этих фреймворков в приложения. В то же время сама Netty также предоставляет множество инструментов кодеков для разработчиков. Разработчики могут разрабатывать эффективные приложения для передачи по сети на основе Netty, такие как высокопроизводительное промежуточное ПО для сообщений.Apache RocketMQ, высокопроизводительная среда RPCApache DubboЖдать.

Концепции ядра Netty

Netty功能特性图

Как видно из вышеуказанной архитектуры диаграммы, Netty в основном состоит из трех основных компонентов:

  • основные компоненты
  • Служба передачи
  • протокол

основные компоненты

Основные компоненты включают в себя: модель событий, байтовые буферы и коммуникационные API.

модель события

Netty основана на асинхронном управлении событиями, структура отражает всеI/OОперации асинхронные, вызывающий абонент не получает результаты немедленно, но по механизму слушателя событий пользователи могут легко получить или получить активный механизм уведомленияI/Oрезультат операции.

Netty классифицирует все события в соответствии с их отношением к входящему или исходящему потоку данных.

События, которые могут быть инициированы входящими данными или связанными изменениями состояния, включают следующее:

  • Соединение было активировано или соединение деактивировано.
  • данные прочитаны.
  • Пользовательские события.
  • Событие ошибки.

Исходящее событие — это результат действия, которое будет инициировано в будущем, включая следующие действия:

  • Откройте или закройте соединение с удаленным узлом.
  • Записать или сбросить данные в сокет.

Каждое событие может быть отправлено наChannelHandlerМетод, реализованный пользователем в классе.

байтовый буфер

Нетти использует различие междуJava ByteBufferновый тип буфераByteBuf,ByteBufПредоставляет богатые возможности.

Коммуникационный API

Коммуникационные API Netty абстрагируются отChannel, с унифицированным асинхроннымI/OИнтерфейс программирования для выполнения всех операций связи «точка-точка».

Служба передачи

В Netty уже есть несколько встроенных транспортных сервисов. Поскольку не все их транспорты поддерживают все протоколы, необходимо выбрать транспорт, совместимый с протоколом, используемым приложением. Ниже приведены все транспорты, предоставляемые Netty.

NIO

io.netty.channel.socket.nioПакеты используются для поддержки NIO. Реализация пакета ниже должна использоватьjava.nio.channelsПакеты служат базой (подход на основе селекторов).

epoll

io.netty.channel.epollПакет для поддержки epoll и неблокирующего ввода-вывода, управляемого JNI.

Следует отметить, что этоepollТранспорт поддерживается только в Linux.epollВ то же время он предоставляет множество функций, таких как: SO_REUSEPORT и т. д., что быстрее, чем передача NIO, и полностью не блокируется.

OIO

io.netty.channel.socket.oioПакеты используются для поддержки использованияjava.netпакетная блокировкаI/O.

местный

io.netty.channel.localПакеты используются для поддержки локальных транспортов, которые обмениваются данными через каналы внутри виртуальной машины.

в линию

io.netty.channel.embeddedПакеты передаются как встроенные, что позволяет использоватьChannelHandlerбез необходимости в истинном сетевом транспорте.

Поддержка протокола

Netty поддерживает многофункциональные сетевые протоколы, такие какTCP,UDP,HTTP,HTTP/2,WebSocket,SSL/TLSи т. д. Эти протоколы реализованы «из коробки», поэтому разработчики Netty могут добиться простоты разработки, высокой производительности и стабильности без потери гибкости.

Нетти простое приложение

Введение зависимостей Maven

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.49.Final</version>
</dependency>

Обработчик конвейера на стороне сервера

public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    //读取数据实际(这里我们可以读取客户端发送的消息)
    /*
    1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址
    2. Object msg: 就是客户端发送的数据 默认Object
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("server ctx =" + ctx);
        Channel channel = ctx.channel();
        //将 msg 转成一个 ByteBuf
        //ByteBuf 是 Netty 提供的,不是 NIO 的 ByteBuffer.
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("客户端发送消息是:" + buf.toString(CharsetUtil.UTF_8));
        System.out.println("客户端地址:" + channel.remoteAddress());
    }


    //数据读取完毕
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //writeAndFlush 是 write + flush
        //将数据写入到缓存,并刷新
        //一般讲,我们对这个发送的数据进行编码
        ctx.writeAndFlush(Unpooled.copiedBuffer("公司最近账户没啥钱,再等几天吧!", CharsetUtil.UTF_8));
    }

    //处理异常, 一般是需要关闭通道
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

NettyServerHandlerунаследовано отChannelInboundHandlerAdapter, этот класс реализуетChannelInboundHandlerинтерфейс.ChannelInboundHandlerПредоставляет множество методов интерфейса для обработки событий.

покрыто здесьchannelRead()Метод обработчика событий. Этот метод вызывается при получении сообщения всякий раз, когда от клиента поступают новые данные.

channelReadComplete()Метод обработки события вызывается при чтении данных путем вызоваChannelHandlerContextизwriteAndFlush()метод, который записывает сообщение в канал и, наконец, отправляет его клиенту.

exceptionCaught()Метод обработки событий заключается в том, что когдаThrowableобъект будет называться.

Основная программа сервера

public class NettyServer {

    public static void main(String[] args) throws Exception {
        //创建BossGroup 和 WorkerGroup
        //说明
        //1. 创建两个线程组 bossGroup 和 workerGroup
        //2. bossGroup 只是处理连接请求 , 真正的和客户端业务处理,会交给 workerGroup完成
        //3. 两个都是无限循环
        //4. bossGroup 和 workerGroup 含有的子线程(NioEventLoop)的个数
        //   默认实际 cpu核数 * 2
        //
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(); //8
        try {
            //创建服务器端的启动对象,配置参数
            ServerBootstrap bootstrap = new ServerBootstrap();
            //使用链式编程来进行设置
            bootstrap.group(bossGroup, workerGroup) //设置两个线程组
                    .channel(NioServerSocketChannel.class) //bossGroup使用NioSocketChannel 作为服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列得到连接个数 option主要是针对boss线程组,
                    .childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接状态 child主要是针对worker线程组
                    .childHandler(new ChannelInitializer<SocketChannel>() {//workerGroup使用 SocketChannel创建一个通道初始化对象																														(匿名对象)
                        //给pipeline 设置处理器
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //可以使用一个集合管理 SocketChannel, 再推送消息时,可以将业务加入到各个channel 对应的 NIOEventLoop 的 									taskQueue 或者 scheduleTaskQueue
                            ch.pipeline().addLast(new NettyServerHandler());
                        }
                    }); // 给我们的workerGroup 的 EventLoop 对应的管道设置处理器

            System.out.println(".....服务器 is ready...");
            //绑定一个端口并且同步, 生成了一个 ChannelFuture 对象
            //启动服务器(并绑定端口)
            ChannelFuture cf = bootstrap.bind(7788).sync();
            //给cf 注册监听器,监控我们关心的事件
            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (cf.isSuccess()) {
                        System.out.println("服务已启动,端口号为7788...");
                    } else {
                        System.out.println("服务启动失败...");
                    }
                }
            });
            //对关闭通道进行监听
            cf.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

NioEventLoopGroupиспользуется для обработкиI/OМногопоточный цикл событий для операций. Netty предоставляет множество различныхEventLoopGroupреализация для обработки различных видов транспорта.

В приведенном выше серверном приложении есть дваNioEventLoopGroupиспользовал. Первый называетсяbossGroup, используемый для приема входящих подключений. второй называетсяworkerGroup, используемый для обработки соединений, которые уже были получены, один разbossGroupПосле получения соединения информация о соединении будет зарегистрирована вworkerGroupначальство.

ServerBootstrapЯвляется загрузочным классом для сервисов NIO. можно использовать непосредственно в этом сервисеChannel.

  • groupМетод настройкиEventLoopGroup.
  • пройти черезChannelметод, вы можете указать вновь подключенный входящийChannelТипNioServerSocketChannelДобрый.
  • childHandlerиспользуется для указанияChannelHandler, то есть реализованный ранееNettyServerHandler.
  • в состоянии пройтиoptionустановить указанныйChannelреализоватьNioServerSocketChannelпараметры конфигурации.
  • childOptionОсновные настройкиSocketChannelсынChannelОпции.
  • bindИспользуется для привязки порта для запуска службы.

Обработчик клиентских каналов

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    //当通道就绪就会触发该方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("client ctx =" + ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("老板,工资什么时候发给我啊?", CharsetUtil.UTF_8));
    }

    //当通道有读取事件时,会触发
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("服务器回复的消息:" + buf.toString(CharsetUtil.UTF_8));
        System.out.println("服务器的地址: "+ ctx.channel().remoteAddress());
    }

    //处理异常, 一般是需要关闭通道
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

channelReadМетод преобразует полученное сообщение в строку для удобной печати на консоли.

channelReadТип полученного сообщенияByteBuf,ByteBufПредоставляются удобные методы для преобразования в строки.

Основная программа клиента

public class NettyClient {

    public static void main(String[] args) throws Exception {
        //客户端需要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //创建客户端启动对象
            //注意客户端使用的不是 ServerBootstrap 而是 Bootstrap
            Bootstrap bootstrap = new Bootstrap();
            //设置相关参数
            bootstrap.group(group) //设置线程组
                    .channel(NioSocketChannel.class) // 设置客户端通道的实现类(反射)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new NettyClientHandler()); //加入自己的处理器
                        }
                    });
            System.out.println("客户端 ok..");
            //启动客户端去连接服务器端
            //关于 ChannelFuture 要分析,涉及到netty的异步模型
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 7788).sync();
            //给关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}

Клиенту нужен только одинNioEventLoopGroupВот и все.

тестовый забег

Запускать серверы отдельноNettyServerи клиентNettyClientпрограмма

Вывод консоли сервера:

.....服务器 is ready...
服务已启动,端口号为7788...
server ctx =ChannelHandlerContext(NettyServerHandler#0, [id: 0xa1b2233c, L:/127.0.0.1:7788 - R:/127.0.0.1:63239])
客户端发送消息是:老板,工资什么时候发给我啊?
客户端地址:/127.0.0.1:63239

Вывод клиентской консоли:

客户端 ok..
client ctx =ChannelHandlerContext(NettyClientHandler#0, [id: 0x21d6f98e, L:/127.0.0.1:63239 - R:/127.0.0.1:7788])
服务器回复的消息:公司最近账户没啥钱,再等几天吧!
服务器的地址: /127.0.0.1:7788

На данный момент завершены простой сервер и клиент на базе Netty.

Компоненты Netty

Здесь мы сначала кратко разберемся в функциях следующих компонентов, оставим изображение, а позже подробно расскажем о каждом компоненте.

Channel

учитесь с намиJava NIO ChannelТочно так же Netty Channel сделал на этой основе очень абстрактную инкапсуляцию, в основном используемую для базовых операций данных сетевого ввода-вывода, таких какbind (),connect (),read (),write ()Ждать.

EventLoop

В течение всего жизненного цикла сетевого соединения обработка всех происходящих событий в основномEventLoopиметь дело с

ChannelFuture

В Netty операции ввода-вывода в основном выполняются асинхронно.Когда операция происходит, нам нужен способ узнать результат выполнения операции в будущем моменте времени.ChannelFutrueсерединаaddListener ()способ регистрации слушателейChannelFutureListener, прослушиватель может активно уведомлять нас о завершении операции.

ChannelHandler

channelHandlerВ основном используется для обработки бизнес-логики в приложении.Через него обрабатываются входящие и исходящие данные в сети.При возникновении событияchannelHandlerбудет запущен для выполнения.

ChannelPipeline

ChannelPipelineПредоставляет контейнер для определения потоков обработки при входе и выходе данных. могуPipelineРассматриваемый как сборочная линия, исходный исходный материал (поток байтов) поступает, проходит обработку и, наконец, выводится.

Bootstrap

В основном используется для настройки информации о запуске программы Netty сервера или клиента.

ByteBuf

байтовый контейнер данных, который обеспечивает отношенияJava NIO ByteBufferлучший API.

Суммировать

В этой статье в основном объясняются предыстория, характеристики, основные компоненты Netty и способы быстрого запуска первого приложения Netty.

Позже мы проанализируемNetty架构设计,Channel,ChannelHandler, байтовый буферByteBuf,线程模型,编解码,引导程序знания и тд.

конец

Я кодер, которого бьют, и я пытаюсь двигаться вперед. Если статья была вам полезна, не забудьте поставить лайк и подписаться, спасибо!