Netty初探

NIO 的类库和 API 繁杂, 使用麻烦: 需要熟练掌握Selector、 ServerSocketChannel、 SocketChannel、 ByteBuffer等。
开发工作量和难度都非常大: 例如客户端面临断线重连、 网络闪断、心跳处理、半包读写、 网络拥塞和异常流的处理等等。
Netty 对 JDK 自带的 NIO 的 API 进行了良好的封装,解决了上述问题。且Netty拥有高性能、 吞吐量更高,延迟更低,减少资源消耗,最小化不必要的内存复制等优点。
Netty 现在都在用的是4.x,5.x版本已经废弃,Netty 4.x 需要JDK 6以上版本支持

Netty的使用场景:

1)互联网行业:

在分布式系统中,各个节点之间需要远程服务调用,高性能的 RPC 框架必不可少,Netty 作为异步高性能的通信框架,往往作为基础通信组件被这些 RPC 框架使用。典型的应用有:阿里分布式服务框架 Dubbo 的 RPC 框架使用 Dubbo 协议进行节点间通信,Dubbo 协议默认使用 Netty 作为基础通信组件,用于实现。各进程节点之间的内部通信。Rocketmq底层也是用的Netty作为基础通信组件。

2)游戏行业:

无论是手游服务端还是大型的网络游戏,Java 语言得到了越来越广泛的应用。Netty 作为高性能的基础通信组件,它本身提供了 TCP/UDP 和 HTTP 协议栈。

3)大数据领域:

经典的 Hadoop 的高性能通信和序列化组件 Avro 的 RPC 框架,默认采用 Netty 进行跨界点通信,它的 Netty Service 基于 Netty 框架二次封装实现。
netty相关开源项目:https://netty.io/wiki/related-projects.html

Netty通讯示例

Netty的maven依赖:

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

服务端代码:

  1. public class NettyServer {
  2. public static void main(String[] args) throws Exception {
  3. //创建两个线程组bossGroup和workerGroup, 含有的子线程NioEventLoop的个数默认为cpu核数的两倍
  4. // bossGroup只是处理连接请求 ,真正的和客户端业务处理,会交给workerGroup完成
  5. EventLoopGroup bossGroup = new NioEventLoopGroup(1);
  6. EventLoopGroup workerGroup = new NioEventLoopGroup();
  7. try {
  8. //创建服务器端的启动对象
  9. ServerBootstrap bootstrap = new ServerBootstrap();
  10. //使用链式编程来配置参数
  11. bootstrap.group(bossGroup, workerGroup) //设置两个线程组
  12. .channel(NioServerSocketChannel.class) //使用NioServerSocketChannel作为服务器的通道实现
  13. // 初始化服务器连接队列大小,服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接。
  14. // 多个客户端同时来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理
  15. .option(ChannelOption.SO_BACKLOG, 1024)
  16. .childHandler(new ChannelInitializer<SocketChannel>() {//创建通道初始化对象,设置初始化参数
  17. @Override
  18. protected void initChannel(SocketChannel ch) throws Exception {
  19. //对workerGroup的SocketChannel设置处理器
  20. ch.pipeline().addLast(new NettyServerHandler());
  21. }
  22. });
  23. System.out.println("netty server start。。");
  24. //绑定一个端口并且同步, 生成了一个ChannelFuture异步对象,通过isDone()等方法可以判断异步事件的执行情况
  25. //启动服务器(并绑定端口),bind是异步操作,sync方法是等待异步操作执行完毕
  26. ChannelFuture cf = bootstrap.bind(9000).sync();
  27. //给cf注册监听器,监听我们关心的事件
  28. /*cf.addListener(new ChannelFutureListener() {
  29. @Override
  30. public void operationComplete(ChannelFuture future) throws Exception {
  31. if (cf.isSuccess()) {
  32. System.out.println("监听端口9000成功");
  33. } else {
  34. System.out.println("监听端口9000失败");
  35. }
  36. }
  37. });*/
  38. //对通道关闭进行监听,closeFuture是异步操作,监听通道关闭
  39. // 通过sync方法同步等待通道关闭处理完毕,这里会阻塞等待通道关闭完成
  40. cf.channel().closeFuture().sync();
  41. } finally {
  42. bossGroup.shutdownGracefully();
  43. workerGroup.shutdownGracefully();
  44. }
  45. }
  46. }
  47. /**
  48. * 自定义Handler需要继承netty规定好的某个HandlerAdapter(规范)
  49. */
  50. public class NettyServerHandler extends ChannelInboundHandlerAdapter {
  51. /**
  52. * 读取客户端发送的数据
  53. *
  54. * @param ctx 上下文对象, 含有通道channel,管道pipeline
  55. * @param msg 就是客户端发送的数据
  56. * @throws Exception
  57. */
  58. @Override
  59. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  60. System.out.println("服务器读取线程 " + Thread.currentThread().getName());
  61. //Channel channel = ctx.channel();
  62. //ChannelPipeline pipeline = ctx.pipeline(); //本质是一个双向链接, 出站入站
  63. //将 msg 转成一个 ByteBuf,类似NIO 的 ByteBuffer
  64. ByteBuf buf = (ByteBuf) msg;
  65. System.out.println("客户端发送消息是:" + buf.toString(CharsetUtil.UTF_8));
  66. }
  67. /**
  68. * 数据读取完毕处理方法
  69. *
  70. * @param ctx
  71. * @throws Exception
  72. */
  73. @Override
  74. public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
  75. ByteBuf buf = Unpooled.copiedBuffer("HelloClient", CharsetUtil.UTF_8);
  76. ctx.writeAndFlush(buf);
  77. }
  78. /**
  79. * 处理异常, 一般是需要关闭通道
  80. *
  81. * @param ctx
  82. * @param cause
  83. * @throws Exception
  84. */
  85. @Override
  86. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  87. ctx.close();
  88. }
  89. }

客户端代码:

  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) // 使用 NioSocketChannel 作为客户端的通道实现
  12. .handler(new ChannelInitializer<SocketChannel>() {
  13. @Override
  14. protected void initChannel(SocketChannel channel) throws Exception {
  15. //加入处理器
  16. channel.pipeline().addLast(new NettyClientHandler());
  17. }
  18. });
  19. System.out.println("netty client start");
  20. //启动客户端去连接服务器端
  21. ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9000).sync();
  22. //对关闭通道进行监听
  23. channelFuture.channel().closeFuture().sync();
  24. } finally {
  25. group.shutdownGracefully();
  26. }
  27. }
  28. }
  29. public class NettyClientHandler extends ChannelInboundHandlerAdapter {
  30. /**
  31. * 当客户端连接服务器完成就会触发该方法
  32. *
  33. * @param ctx
  34. * @throws Exception
  35. */
  36. @Override
  37. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  38. ByteBuf buf = Unpooled.copiedBuffer("HelloServer", CharsetUtil.UTF_8);
  39. ctx.writeAndFlush(buf);
  40. }
  41. //当通道有读取事件时会触发,即服务端发送数据给客户端
  42. @Override
  43. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  44. ByteBuf buf = (ByteBuf) msg;
  45. System.out.println("收到服务端的消息:" + buf.toString(CharsetUtil.UTF_8));
  46. System.out.println("服务端的地址: " + ctx.channel().remoteAddress());
  47. }
  48. @Override
  49. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  50. cause.printStackTrace();
  51. ctx.close();
  52. }
  53. }

看完代码,我们发现Netty框架的目标就是让你的业务逻辑从网络基础应用编码中分离出来,让你可以专注业务的开发,而不需写一大堆类似NIO的网络处理操作。