ToB企服应用市场:ToB评测及商务社交产业平台

标题: 认识netty的基本组件 [打印本页]

作者: 络腮胡菲菲    时间: 2024-7-30 20:32
标题: 认识netty的基本组件
Java NIO VS Netty

有了 Java NIO,而且 Netty 也是基于 Java NIO 实现,那么为什么不能直接用 Java NIO 来实现网络通讯模块呢? 接下来我就给各人表明一下缘故原由。
如果我们用 Java NIO 来开发网络通讯组件,势必会直接面临很多网络通讯的题目。比如,网络连接异常怎样处理、网络的闪断怎么处理、网络拥堵、拆包粘包等一大堆网络通讯的题目。同时还会面临性能优化的题目,比如成熟的中间件为了提升通讯性能,以及提升处理哀求量,会计划成 reactor 模式。
所以,直接用 Java NIO 做通讯模块,会有很多的生产情况的题目等待我们去处理,大部分经验并不是很资深的同砚是很难实现的。
但对比下来,Netty 开发通讯组件则有很多上风。
但是 Netty 也是有劣势的,Netty 为了更好地封装 Java NIO 创造了很多抽象的概念,这些抽象概念对于初学者来说难度并不小。
总体来说,Netty 相对于 Java NIO 确实更加完善和健壮,但是也难于理解。
为了让你更好地理解 Netty,下面我会带各人用 Netty 简单地实现一个有服务端和客户端的网络通讯 Demo。
Demo:Netty 入门步伐

在这个 Demo 步伐中,我会给各人详细表明步伐中每步的意义,让各人更快地入门 Nettty 开发。
这里我会从服务端和客户端这两头分别讲起。
服务端代码

服务端代码包括服务端启动类和处理网络事件的 Handler 类。启动类重要是一些 Netty 核心类的初始化及端口的绑定;Handler 类是用来处理网络事件对应的业务逻辑。
起首,服务端启动类 NettyServer 代码如下:
 java
  1. public class NettyServer {
  2.     public static void main(String[] args) {
  3.         // 第一步,分别创建两个处理网络的EventLoopGroup。
  4.         EventLoopGroup parentGroup = new NioEventLoopGroup();   //Acceptor线程组
  5.         EventLoopGroup childGroup = new NioEventLoopGroup();    //Processor或Handler 线程组
  6.         try{
  7.             // 第二步,初始化服务器
  8.             ServerBootstrap serverBootstrap = new ServerBootstrap(); //相当于Netty服务器
  9.             // 第三步,给服务器做一系列的配置。
  10.             serverBootstrap
  11.                     .group(parentGroup, childGroup)
  12.                     .channel(NioServerSocketChannel.class)//监听端口的ServerSocketChannel
  13.                     .option(ChannelOption.SO_BACKLOG, 1024)
  14.                     .childHandler(new ChannelInitializer<SocketChannel>() { //处理每个连接的 SocketChannel,SocketChannel代表每一个连接
  15.                         @Override
  16.                         protected void initChannel(SocketChannel socketChannel) throws Exception {
  17.                             socketChannel.pipeline().addLast(new NettyServerHandler()); //针对网络请求的处理逻辑
  18.                         }
  19.                     });
  20.             System.out.println("Server 启动了");
  21.             // 第四步,绑定端口。
  22.             ChannelFuture channelFuture =  serverBootstrap.bind(50099).sync(); //监听指定端口
  23.             // 第五步,等待服务器关闭
  24.             channelFuture.channel().closeFuture().sync();// 同步等待关闭启动服务器的结果
  25.         }catch (Exception ex){
  26.             ex.printStackTrace();
  27.         }finally {
  28.             parentGroup.shutdownGracefully();
  29.             childGroup.shutdownGracefully();
  30.         }
  31.     }
  32. }
复制代码
我们接下来看看自定义的处理网络事件的类 NettyServerHandler 是怎么写的。
NettyServerHandler 这个类继承了 Netty 类库里提供的类 ChannelInboundHandlerAdapter 来实现业务操作。也就是说,Netty 已经把复杂的网络题目封装好了,我们只要关注数据处理就好了。处理网络事件的类 NettyServerHandler 代码如下:
 java
  1. // 这个类很像 reactor 模式里的processor线程,负责读区请求然后返回响应
  2. public class NettyServerHandler extends ChannelInboundHandlerAdapter {
  3.     @Override
  4.     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  5.         // 第一步,获取客户端请求的内容
  6.         ByteBuf buffer= (ByteBuf) msg;
  7.         byte[] requestBytes = new byte[buffer.readableBytes()];
  8.         buffer.readBytes(requestBytes);
  9.         
  10.         String request = new String(requestBytes,"UTF-8");
  11.         System.out.println("收到请求"+request);
  12.         //第二步,向客户端返回信息
  13.         String response = "收到请求后返回响应";
  14.         ByteBuf responseBuffer = Unpooled.copiedBuffer(response.getBytes());
  15.         ctx.write(responseBuffer);
  16.     }
  17.     @Override
  18.     //
  19.     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
  20.         // 真正的发送
  21.         ctx.flush();
  22.     }
  23.     @Override
  24.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  25.         cause.printStackTrace();
  26.         ctx.close();
  27.     }
  28.     @Override
  29.     // 只要channel打通了,就会执行
  30.     public void channelActive(ChannelHandlerContext ctx) throws Exception {
  31.         System.out.println("Server is Active......");
  32.     }
  33. }
复制代码
但是需要阐明的是,调用 ctx.write() 时并不代表数据已经发送了,因为操作体系要根据本身的实际情况发送数据。这时如果我们对一致性要求很高,就可以重载 channelReadComplete() 方法,并调用 ctx.flush() 方法,这样数据就能同步发送出去了。
当然另有别的方法比如 channelActive(),这个方法表示有客户端连接并且连接成功后 Channel 也是可用的。
结合上面两个类,我给各人画张服务器端流程图:

好,服务端的步伐就介绍完了,接下来介绍客户端的步伐。
客户端代码

与服务端步伐一样,客户端步伐也分为启动类和处理网络事件的 Handler 类。
服务端启动类 NettyClient 代码如下:
 java
  1. public class NettyClient {
  2.     public static void main(String[] args) {
  3.         // 第一步,定义一个EventLoopGroup
  4.         EventLoopGroup parent = new NioEventLoopGroup();   //Acceptor线程组
  5.         try{
  6.             Bootstrap bootstrap= new Bootstrap();
  7.             // 第二步,对客户端做各种配置
  8.             bootstrap.group(parent)
  9.                     .channel(NioSocketChannel.class)
  10.                     .option(ChannelOption.TCP_NODELAY,true)
  11.                     .handler(new ChannelInitializer<Channel>() {
  12.                         @Override
  13.                         protected void initChannel(Channel channel) throws Exception {
  14.                             channel.pipeline().addLast(new NettyClintHandler());
  15.                         }
  16.                     });
  17.             //第三步,向服务端连接
  18.             ChannelFuture channelFuture= bootstrap.connect("127.0.0.1",50099).sync();
  19.             channelFuture.channel().closeFuture().sync();
  20.         }catch (Exception ex){
  21.             ex.printStackTrace();
  22.         }
  23.     }
  24. }
复制代码
接下来看看客户端处理网络事件的 Handler 类是怎样写的。
处理网络事件的类 NettyClientHandler 代码如下:
 java
  1. public class NettyClintHandler extends ChannelInboundHandlerAdapter {
  2.         // 第一步,定义要发送的内容
  3.         private ByteBuf requestBuffer;
  4.         
  5.         public NettyClintHandler(){
  6.             byte[] requestBytes = "发送请求".getBytes();
  7.             requestBuffer = Unpooled.buffer(requestBytes.length);
  8.             requestBuffer.writeBytes(requestBytes);
  9.         }
  10.     @Override
  11.     public void channelActive(ChannelHandlerContext ctx) throws Exception {
  12.             // 第二步,向服务端发送消息
  13.             ctx.writeAndFlush(requestBuffer);
  14.     }
  15.     @Override
  16.     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  17.             // 第三步,读取服务端的响应
  18.         ByteBuf responseBuffer = (ByteBuf) msg;
  19.         byte[] responseBytes = new byte[responseBuffer.readableBytes()];
  20.         responseBuffer.readBytes(responseBytes);
  21.         String response = new String(responseBytes,"UTF-8");
  22.         System.out.println("收到服务端的响应:"+response);
  23.     }
  24.     @Override
  25.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  26.         cause.printStackTrace();
  27.         ctx.close();
  28.     }
  29. }
复制代码
到这里,用 Netty 实现服务端和客户端的代码就解说完了,各人可以看到 Netty 把底层的 Java NIO 全部屏蔽掉了,我们只要关注配置参数,只关心业务实现类就可以了。
建议各人可以在本地尝试运行一下,这样理解地会更加深刻。
从代码中学习到的计划头脑

起首,各人可以看到,负责通讯模块的启动类和负责处理网络事件的 Handler 类是分开的,这样的好处是 Handler 类的业务逻辑功能和启动类的通讯功能分离。功能分类的好处是显而易见的,这样做可以淘汰耦合。
根据上述代码,对于网络事件的处理可以用多个 Handler 类对象处理。Netty 采用了链式调用来让各个 Handler 类对象串联起来。其实,这种计划也是为了淘汰耦合,我们可以对网络事件的处理分成几个步调,每一个步调由一个 Handler 负责。这样一方面做到了解耦,代表差别功能的 Handler 类互不影响。另一方面,我们对于某个 Channel 可以灵活地增长或淘汰处理它的 Hanlder。这样就会更加灵活便捷。
另外,事件驱动的头脑也有很好的表现,在 Handler 类里有许多表示事件的方法,比如表示读事件的方法 channelRead(),表示 Channel 连接活泼的方法 channelActive()。事件驱动的好处是,代码会有很好的可读性,同时比较轻易理解。
总结


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4