前言

关于Netty的学习,最近看了不少有关视频和书籍,也收获不少,希望把我知道的分享给你们,一起加油,一起成长。前面我们对 Java IOBIONIOAIO进行了分析,相关文章链接如下:

深入分析 Java IO (一)概述

深入分析 Java IO (二)BIO

深入分析 Java IO (三)NIO

深入分析 Java IO (四)AIO

本篇文章我们就开始对 Netty来进行深入分析,首先我们来了解一下 JAVA NIOAIO的不足之处。

Java原生API之痛

虽然JAVA NIOJAVA AIO框架提供了多路复用IO/异步IO的支持,但是并没有提供上层“信息格式”的良好封装。用这些API实现一款真正的网络应用则并非易事。

JAVA NIOJAVA AIO并没有提供断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流等的处理,这些都需要开发者自己来补齐相关的工作。

AIO在实践中,并没有比NIO更好。AIO在不同的平台有不同的实现,windows系统下使用的是一种异步IO技术:IOCP;Linux下由于没有这种异步 IO 技术,所以使用的是epoll 对异步 IO 进行模拟。所以 AIO 在 Linux 下的性能并不理想。AIO 也没有提供对 UDP 的支持。

综上,在实际的大型互联网项目中,Java 原生的 API 应用并不广泛,取而代之的是一款第三方Java 框架,这就是Netty

Netty的优势

Netty 提供 异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。

非阻塞 I/O

Netty 是基于 Java NIO API 实现的网络应用框架,使用它可以快速简单的开发网络应用程序,如服务器和客户端程序。Netty 大大简化了网络程序开发的过程,如 TCP 和 UDP 的 Socket 服务的开发。

由于是基于 NIO 的 API,因此,Netty 可以提供非阻塞的 I/O操作,极大的提升了性能。同时,Netty 内部封装了 Java NIO API 的复杂性,并提供了线程池的处理,使得开发 NIO 的应用变得极其简单。

丰富的协议

Netty 提供了简单、易用的 API ,但这并不意味着应用程序会有难维护和性能低的问题。Netty 是一个精心设计的框架,它从许多协议的实现中吸收了很多的经验,如 FTP 、SMTP、 HTTP、许多二进制和基于文本的传统协议。

Netty 支持丰富的网络协议,如TCPUDPHTTPHTTP/2WebSocketSSL/TLS等,这些协议实现开箱即用,因此,Netty 开发者能够在不失灵活的前提下来实现开发的简易性、高性能和稳定性。

异步和事件驱动

Netty 是异步事件驱动的框架,该框架体现为所有的I/O操作都是异步的,所有的I/O调用会立即返回,并不保证调用成功与否,但是调用会返回ChannelFuture。Netty 会通过 ChannelFuture通知调用是成功了还是失败了,抑或是取消了。

同时,Netty 是基于事件驱动的,调用者并不能立即获得结果,而是通过事件监听机制,用户可以方便地主动获取或者通过通知机制获得I/O操作的结果。

Future对象刚刚创建时,处于非完成状态,调用者可以通过返回的ChannelFuture来获取操作执行的状态,再通过注册监听函数来执行完成后的操作,常见有如下操作:

  • 通过isDone方法来判断当前操作是否完成。
  • 通过isSuccess方法来判断已完成的当前操作是否成功。
  • 通过getCause方法来获取已完成的当前操作失败的原因。
  • 通过isCancelled方法来判断已完成的当前操作是否被取消。
  • 通过addListener方法来注册监听器,当操作已完成(isDone方法返回完成),将会通知指定的监听器;如果future对象已完成,则理解通知指定的监听器。

例如:下面的代码中绑定端口是异步操作,当绑定操作处理完,将会调用相应的监听器处理逻辑。

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

相比传统的阻塞 I/O,Netty 异步处理的好处是不会造成线程阻塞,线程在 I/O操作期间可以执行其他的程序,在高并发情形下会更稳定并拥有更高的吞吐量。

精心设计的API

Netty 从开始就为用户提供了体验最好的API及实现设计。

例如,在用户数较小的时候可能会选择传统的阻塞API,毕竟与 Java NIO 相比使用阻塞 API 将会更加容易一些。然而,当业务量呈指数增长并且服务器需要同时处理成千上万的客户连接,便会遇到问题。这种情况下可能会尝试使用 Java NIO,但是复杂的 NIO Selector 编程接口又会耗费大量的时间并最终会阻碍快速开发。

Netty 提供了一个叫作 channel的统一的异步I/O编程接口,这个编程接口抽象了所有点对点的通信操作。也就是说,如果应用是基于Netty 的某一种传输实现,那么同样的,应用也可以运行在 Netty 的另一种传输实现上。Channel常见的子接口有:

丰富的缓冲实现

Netty 使用自建的缓存 API,而不是使用 Java NIO 的 ByteBuffer 来表示一个连续的字节序列。与 ByteBuffer 相比,这种方式拥有明显的优势。

Netty 使用新的缓冲类型 ByteBuf ,并且被设计为可从底层解决 ByteBuffer 问题,同时还满足日常网络应用开发需要的缓冲类型。

Netty 重要有以下特性:

  • 允许使用自定义的缓冲类型。
  • 复合缓冲类型中内置透明的零拷贝实现。
  • 开箱即用动态缓冲类型,具有像 StringBuffer 一样的动态缓冲能力。
  • 不再需要调用flip()方法。
  • 正常情况下具有比ByteBuffer更快的响应速度。

高效的网络传输

Java 原生的序列化主要存在以下几个弊端:

  • 无法跨语言。

  • 序列化后码流太大。

  • 序列化后性能太低。

业界有非常多的框架用于解决上述问题,如 Google ProtobufJBoss MarshallingFacebook Thrift等。针对这些框架,Netty 都提供了相应的包将这些框架集成到应用中。同时,Netty 本身也提供了众多的编解码工具,方便开发者使用。开发者可以基于 Netty 来开发高效的网络传输应用,例如:高性能的消息中间件 Apache RocketMQ、高性能RPC框架Apache Dubbo等。

Netty 核心概念

从上述的架构图可以看出,Netty 主要由三大块组成:

  • 核心组件
  • 传输服务
  • 协议

核心组件

核心组件包括:事件模型、字节缓冲区和通信API

事件模型

Netty 是基于异步事件驱动的,该框架体现为所有的I/O操作都是异步的,调用者并不能立即获得结果,而是通过事件监听机制,用户可以方便地主动获取或者通过通知机制获得I/O操作的结果。

Netty 将所有的事件按照它们与入站或出站数据流的相关性进行了分类。

可能由入站数据或者相关的状态更改而触发的事件包括以下几项:

  • 连接已被激活或者连接失活。
  • 数据读取。
  • 用户事件。
  • 错误事件。

出站事件是未来将会触发的某个动作的操作结果,包括以下动作:

  • 打开或者关闭到远程节点的连接。
  • 将数据写到或者冲刷到套接字。

每个事件都可以被分发到ChannelHandler类中的某个用户实现的方法。

字节缓冲区

Netty 使用了区别于Java ByteBuffer 的新的缓冲类型ByteBuf,ByteBuf提供了丰富的特性。

通信API

Netty 的通信API都被抽象到Channel里,以统一的异步I/O编程接口来满足所有点对点的通信操作。

传输服务

Netty 内置了一些开箱即用的传输服务。因为并不是它们所有的传输都支持每一种协议,所以必须选择一个和应用程序所使用的协议相兼容的传输。以下是Netty提供的所有的传输。

NIO

io.netty.channel.socket.nio包用于支持NIO。该包下面的实现是使用java.nio.channels包作为基础(基于选择器的方式)。

epoll

io.netty.channel.epoll包用于支持由 JNI 驱动的 epoll 和 非阻塞 IO。

需要注意的是,这个epoll传输只能在 Linux 上获得支持。epoll同时提供多种特性,如:SO_REUSEPORT 等,比 NIO传输更快,而且是完全非阻塞的。

OIO

io.netty.channel.socket.oio包用于支持使用java.net包作为基础的阻塞I/O

本地

io.netty.channel.local包用于支持在 VM 内部通过管道进行通信的本地传输。

内嵌

io.netty.channel.embedded包作为内嵌传输,允许使用ChannelHandler而又不需要一个真正的基于网络的传输。

协议支持

Netty 支持丰富的网络协议,如TCPUDPHTTPHTTP/2WebSocketSSL/TLS等,这些协议实现开箱即用,因此,Netty 开发者能够在不失灵活的前提下来实现开发的简易性、高性能和稳定性。

Netty简单应用

引入Maven依赖

  1. <dependency>
  2. <groupId>io.netty</groupId>
  3. <artifactId>netty-all</artifactId>
  4. <version>4.1.49.Final</version>
  5. </dependency>

服务端的管道处理器

  1. public class NettyServerHandler extends ChannelInboundHandlerAdapter {
  2. //读取数据实际(这里我们可以读取客户端发送的消息)
  3. /*
  4. 1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址
  5. 2. Object msg: 就是客户端发送的数据 默认Object
  6. */
  7. @Override
  8. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  9. System.out.println("server ctx =" + ctx);
  10. Channel channel = ctx.channel();
  11. //将 msg 转成一个 ByteBuf
  12. //ByteBuf 是 Netty 提供的,不是 NIO 的 ByteBuffer.
  13. ByteBuf buf = (ByteBuf) msg;
  14. System.out.println("客户端发送消息是:" + buf.toString(CharsetUtil.UTF_8));
  15. System.out.println("客户端地址:" + channel.remoteAddress());
  16. }
  17. //数据读取完毕
  18. @Override
  19. public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
  20. //writeAndFlush 是 write + flush
  21. //将数据写入到缓存,并刷新
  22. //一般讲,我们对这个发送的数据进行编码
  23. ctx.writeAndFlush(Unpooled.copiedBuffer("公司最近账户没啥钱,再等几天吧!", CharsetUtil.UTF_8));
  24. }
  25. //处理异常, 一般是需要关闭通道
  26. @Override
  27. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  28. ctx.close();
  29. }
  30. }

NettyServerHandler继承自ChannelInboundHandlerAdapter,这个类实现了ChannelInboundHandler接口。ChannelInboundHandler提供了许多事件处理的接口方法。

这里覆盖了channelRead()事件处理方法。每当从客户端收到新的数据时,这个方法会在收到消息时被调用。

channelReadComplete()事件处理方法是数据读取完毕时被调用,通过调用ChannelHandlerContextwriteAndFlush()方法,把消息写入管道,并最终发送给客户端。

exceptionCaught()事件处理方法是,当出现Throwable对象时才会被调用。

服务端主程序

  1. public class NettyServer {
  2. public static void main(String[] args) throws Exception {
  3. //创建BossGroup 和 WorkerGroup
  4. //说明
  5. //1. 创建两个线程组 bossGroup 和 workerGroup
  6. //2. bossGroup 只是处理连接请求 , 真正的和客户端业务处理,会交给 workerGroup完成
  7. //3. 两个都是无限循环
  8. //4. bossGroup 和 workerGroup 含有的子线程(NioEventLoop)的个数
  9. // 默认实际 cpu核数 * 2
  10. //
  11. EventLoopGroup bossGroup = new NioEventLoopGroup(1);
  12. EventLoopGroup workerGroup = new NioEventLoopGroup(); //8
  13. try {
  14. //创建服务器端的启动对象,配置参数
  15. ServerBootstrap bootstrap = new ServerBootstrap();
  16. //使用链式编程来进行设置
  17. bootstrap.group(bossGroup, workerGroup) //设置两个线程组
  18. .channel(NioServerSocketChannel.class) //bossGroup使用NioSocketChannel 作为服务器的通道实现
  19. .option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列得到连接个数 option主要是针对boss线程组,
  20. .childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接状态 child主要是针对worker线程组
  21. .childHandler(new ChannelInitializer<SocketChannel>() {//workerGroup使用 SocketChannel创建一个通道初始化对象 (匿名对象)
  22. //给pipeline 设置处理器
  23. @Override
  24. protected void initChannel(SocketChannel ch) throws Exception {
  25. //可以使用一个集合管理 SocketChannel, 再推送消息时,可以将业务加入到各个channel 对应的 NIOEventLoop 的 taskQueue 或者 scheduleTaskQueue
  26. ch.pipeline().addLast(new NettyServerHandler());
  27. }
  28. }); // 给我们的workerGroup 的 EventLoop 对应的管道设置处理器
  29. System.out.println(".....服务器 is ready...");
  30. //绑定一个端口并且同步, 生成了一个 ChannelFuture 对象
  31. //启动服务器(并绑定端口)
  32. ChannelFuture cf = bootstrap.bind(7788).sync();
  33. //给cf 注册监听器,监控我们关心的事件
  34. cf.addListener(new ChannelFutureListener() {
  35. @Override
  36. public void operationComplete(ChannelFuture future) throws Exception {
  37. if (cf.isSuccess()) {
  38. System.out.println("服务已启动,端口号为7788...");
  39. } else {
  40. System.out.println("服务启动失败...");
  41. }
  42. }
  43. });
  44. //对关闭通道进行监听
  45. cf.channel().closeFuture().sync();
  46. } finally {
  47. bossGroup.shutdownGracefully();
  48. workerGroup.shutdownGracefully();
  49. }
  50. }
  51. }

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用于绑定端口启动服务。

客户端管道处理器

  1. public class NettyClientHandler extends ChannelInboundHandlerAdapter {
  2. //当通道就绪就会触发该方法
  3. @Override
  4. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  5. System.out.println("client ctx =" + ctx);
  6. ctx.writeAndFlush(Unpooled.copiedBuffer("老板,工资什么时候发给我啊?", CharsetUtil.UTF_8));
  7. }
  8. //当通道有读取事件时,会触发
  9. @Override
  10. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  11. ByteBuf buf = (ByteBuf) msg;
  12. System.out.println("服务器回复的消息:" + buf.toString(CharsetUtil.UTF_8));
  13. System.out.println("服务器的地址: "+ ctx.channel().remoteAddress());
  14. }
  15. //处理异常, 一般是需要关闭通道
  16. @Override
  17. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  18. cause.printStackTrace();
  19. ctx.close();
  20. }
  21. }

channelRead方法中将接收到的消息转化为字符串,方便在控制台上打印出来。

channelRead接收到的消息类型为ByteBufByteBuf提供了转为字符串的方便方法。

客户端主程序

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

客户端只需要一个NioEventLoopGroup就可以了。

测试运行

分别启动服务器 NettyServer 和客户端 NettyClient程序

服务端控制台输出内容:

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

客户端控制台输出内容:

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

至此,一个简单的基于Netty开发的服务端和客户端就完成了。

总结

本篇文章主要讲解了 Netty 产生的背景、特点、核心组件及如何快速开启第一个 Netty 应用。

后面我们会分析Netty架构设计ChannelChannelHandler、字节缓冲区ByteBuf线程模型编解码引导程序等方面的知识。

结尾

我是一个正在被打击还在努力前进的码农。如果文章对你有帮助,记得点赞、关注哟,谢谢!

Netty 源码分析系列(一)Netty 概述的更多相关文章

  1. Netty 源码分析系列(二)Netty 架构设计

    前言 上一篇文章,我们对 Netty做了一个基本的概述,知道什么是Netty以及Netty的简单应用. Netty 源码分析系列(一)Netty 概述 本篇文章我们就来说说Netty的架构设计,解密高 ...

  2. netty源码分析系列文章

    netty源码分析系列文章 nettynetty源码阅读netty源码分析  想在年终之际将对netty研究的笔记记录下来,先看netty3,然后有时间了再写netty4的,希望对大家有所帮助,这个是 ...

  3. netty源码分析(十八)Netty底层架构系统总结与应用实践

    一个EventLoopGroup当中会包含一个或多个EventLoop. 一个EventLoop在它的整个生命周期当中都只会与唯一一个Thread进行绑定. 所有由EventLoop所处理的各种I/O ...

  4. Netty源码分析第2章(NioEventLoop)---->第1节: NioEventLoopGroup之创建线程执行器

    Netty源码分析第二章: NioEventLoop 概述: 通过上一章的学习, 我们了解了Server启动的大致流程, 有很多组件与模块并没有细讲, 从这个章开始, 我们开始详细剖析netty的各个 ...

  5. Netty源码分析第1章(Netty启动流程)---->第3节: 服务端channel初始化

    Netty源码分析第一章:Netty启动流程   第三节:服务端channel初始化 回顾上一小节的initAndRegister()方法: final ChannelFuture initAndRe ...

  6. Netty源码分析第1章(Netty启动流程)---->第4节: 注册多路复用

    Netty源码分析第一章:Netty启动流程   第四节:注册多路复用 回顾下以上的小节, 我们知道了channel的的创建和初始化过程, 那么channel是如何注册到selector中的呢?我们继 ...

  7. Netty源码分析第1章(Netty启动流程)---->第5节: 绑定端口

    Netty源码分析第一章:Netty启动步骤 第五节:绑定端口 上一小节我们学习了channel注册在selector的步骤, 仅仅做了注册但并没有监听事件, 事件是如何监听的呢? 我们继续跟第一小节 ...

  8. Netty源码分析(前言, 概述及目录)

    Netty源码分析(完整版) 前言 前段时间公司准备改造redis的客户端, 原生的客户端是阻塞式链接, 并且链接池初始化的链接数并不高, 高并发场景会有获取不到连接的尴尬, 所以考虑了用netty长 ...

  9. Netty源码分析第5章(ByteBuf)---->第5节: directArena分配缓冲区概述

    Netty源码分析第五章: ByteBuf 第五节: directArena分配缓冲区概述 上一小节简单分析了PooledByteBufAllocator中, 线程局部缓存和arean的相关逻辑, 这 ...

随机推荐

  1. ES系列(七):多节点任务的分发与收集实现

    我们知道,当我们对es发起search请求或其他操作时,往往都是随机选择一个coordinator发起请求.而这请求,可能是该节点能处理,也可能是该节点不能处理的,也可能是需要多节点共同处理的,可以说 ...

  2. 旁路电容的PCB布局布线透彻详解(4)

    原文地址点击这里: 前面使用了较多的篇幅介绍旁路电容的工作原理及其选择依据,我们已经能够为电路系统中相应的数字集成芯片选择合适的旁路电容,在实际应用过程中,旁路电容的PCB布局布线也会影响到高频噪声旁 ...

  3. 用vue ui创建的项目怎么关闭eslint校验

    在Vue Cli的控制面板找到配置-ESLint configuration,然后关闭保存时检查就可以了

  4. JWT理论知识

    JWT学习文章: 第一篇:JWT原理 第二篇:JWT原理实现代码 简介 JWT全拼是JSON Web Tocken,是目前最流行的跨域身份认证解决方案,特别适合分布式系统,减少用户麻烦,保证账号安全, ...

  5. ES6学习笔记之字符串新增方法

    1.字符串的子串识别 传统上,Javascript 只有indexof 方法,用来确定一个字符串是否包含在另一个字符串中.如: //indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的 ...

  6. 关于asp.net中Repeater控件的一些应用

    在Asp.net中,我是比较喜欢用Repeater这个控件,刚刚遇到的一个问题,怎么实现单击 <asp:LinkButton>,通过后台的单击事件获取同一行数据中的其他数据(对象). 1, ...

  7. Error in render: "TypeError: Cannot read property '' of undefined"

    描述 在用Vue的时候出现了一个令人窒息的错误 报错显示 "avatar" 未定义,但在postman中测试返回的数据确实有"avatar",可是为什么未找到? ...

  8. CRM软件从哪些方面帮助企业更上一层楼

    CRM顾客智能管理系统可以将"以顾客为管理中心"的管理模式与高新科技方式紧密结合,协助公司搭建优良的客户关系管理,改进顾客的消費感受,进而提升顾客的满意率,为公司产生大量的盈利.据 ...

  9. Java的类加载器种类

    Java类加载器采用双亲委派模型: 1.启动类加载器:这个类加载器负责放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别 ...

  10. Linux守护进程列表/守护进程

      在linux或者unix操作系统中在系统引导的时候会开启很多服务,这些服务就叫做守护进程.为了增加灵活性,root可以选择系统开启的模式,这些模式叫做运行级别,每一种运行级别以一定的方式配置系统. ...