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

标题: 构建异步高并发服务器:Netty与Spring Boot的完美结合 [打印本页]

作者: 立山    时间: 2024-6-24 10:00
标题: 构建异步高并发服务器:Netty与Spring Boot的完美结合
媒介


「作者主页」:雪碧有白泡泡
「个人网站」:雪碧的个人网站

ChatGPT体验地点


  
IO

在Java基础中,IO流是一个重要操纵,先上八股

Netty

Netty是建立在NIO之上的一个框架,提供了更高级的抽象,如ChannelHandler和EventLoop,简化了变乱处置惩罚和网络编程。
执行流程如下图

具有高性能,高可靠性,高可扩展性,还支持多种协议
   我们以聊天流程为例
    1. 引入依靠

  1.                   <dependency>
  2.             <groupId>io.netty</groupId>
  3.             <artifactId>netty-all</artifactId>
  4.             <version>4.1.76.Final</version>
  5.         </dependency>
复制代码
2. 服务端

  1. @Slf4j
  2. public class NettyServer {
  3.     private final static int PORT = 9012;
  4.     public static void main(String[] args) throws InterruptedException {
  5.         /**
  6.          * 包含childGroup,childHandler,config,继承的父类AbstractBootstrap包括了parentGroup
  7.          * */
  8.         ServerBootstrap bootstrap = new ServerBootstrap();
  9.         /**
  10.          * EventLoopGroup用于处理所有ServerChannel和Channel的所有事件和IO
  11.          * */
  12.         EventLoopGroup parentGroup = new NioEventLoopGroup();
  13.         EventLoopGroup childGroup = new NioEventLoopGroup();
  14.         try {
  15.             /**
  16.              * 绑定两个事件组
  17.              * */
  18.             bootstrap.group(parentGroup, childGroup)
  19.                     /**
  20.                      * 初始化socket,定义tcp连接的实例
  21.                      * 内部调用ReflectiveChannelFactory实现对NioServerSocketChannel实例化
  22.                      * channelFactory是在AbstractBootstrap,也就是bootstrap的父类
  23.                      * */
  24.                     .channel(NioServerSocketChannel.class)
  25.                     /**
  26.                      * 添加处理器
  27.                      * ChannelInitializer包括了Set<ChannelHandlerContext> initMap
  28.                      *
  29.                      * 这里比较有趣的事情就是使用被注册的channel去初始化其他的channel,
  30.                      * 等初始化结束后移除该channel
  31.                      * 所以SocketChannel是一个工具,
  32.                      *
  33.                      * 在bind绑定端口的时候,进行初始化和注册initAndRegister,
  34.                      * 通过channel = channelFactory.newChannel()得到初始化channel
  35.                      * init(channel)真正开始初始化,
  36.                      * p = channel.pipeline()得到ChannelPipeline,
  37.                      * p.addLast开始添加
  38.                      * ch.eventLoop().execute将childHandler赋值并开启一个任务setAutoRead
  39.                      * 所以最后在监听读取的时候将会按照下面添加的channel进行读取
  40.                      *
  41.                      * ChannelInitializer继承了ChannelInboundHandlerAdapter
  42.                      * 间接继承ChannelHandlerAdapter,ChannelInboundHandler,
  43.                      * */
  44.                     .childHandler(new ChannelInitializer<SocketChannel>() {
  45.                         @Override
  46.                         protected void initChannel(SocketChannel ch) throws Exception {
  47.                             ChannelPipeline pipeline = ch.pipeline();
  48.                             /**
  49.                              * ByteBuf和String之间的转换
  50.                              *
  51.                              *  Decoders解密
  52.                              *  pipeline.addLast("frameDecoder", new {@link LineBasedFrameDecoder}(80))
  53.                              *  pipeline.addLast("stringDecoder", new {@link StringDecoder}(CharsetUtil.UTF_8))
  54.                              *
  55.                              *  Encoder加密
  56.                              *  pipeline.addLast("stringEncoder", new {@link StringEncoder}(CharsetUtil.UTF_8))
  57.                              *
  58.                              *  使用上面的加密解密后就可以直接读取字符串
  59.                              *   void channelRead({@link ChannelHandlerContext} ctx, String msg) {
  60.                              *       ch.write("Did you say '" + msg + "'?\n")
  61.                              *  }
  62.                              *
  63.                              * */
  64.                             pipeline.addLast(new StringDecoder());
  65.                             pipeline.addLast(new StringEncoder());
  66.                             //自定义处理器
  67.                             pipeline.addLast(new ServerHandler1());
  68.                         }
  69.                     });
  70.             ChannelFuture future = bootstrap.bind(PORT).sync();
  71.             log.info("服务器已启动");
  72.             future.channel().closeFuture().sync();
  73.         } finally {
  74.             parentGroup.shutdownGracefully();
  75.             childGroup.shutdownGracefully();
  76.         }
  77.     }
  78. }
复制代码
这段代码实现了一个使用Netty框架的服务器端,它监听指定的端口并处置惩罚客户端的连接哀求。
     
  1. @Slf4j
  2. public class ServerHandler1 extends ChannelInboundHandlerAdapter {
  3.     @Override
  4.     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  5.         log.info("Client Address ====== {},读取的信息:{}", ctx.channel().remoteAddress(),msg);
  6.         ctx.channel().writeAndFlush("服务端writeAndFlush:我是服务端");
  7.         ctx.fireChannelActive();
  8.         //睡眠
  9.         TimeUnit.MILLISECONDS.sleep(500);
  10.     }
  11.     @Override
  12.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  13.         //打印异常
  14.         cause.printStackTrace();
  15.         //关闭Channel连接,并通知ChannelFuture,通常是出现异常或者是完成了操作
  16.         ctx.close();
  17.     }
  18. }
复制代码
4. 客户端

  1. @Slf4j
  2. public class NettyClient {
  3.     private final static int PORT = 9012;
  4.     private final static String IP = "localhost";
  5.     public static void main(String[] args) throws InterruptedException {
  6.         /**
  7.          * 服务端是ServerBootstrap,客户端是Bootstrap
  8.          * Bootstrap引导channel连接,UDP连接用bind方法,TCP连接用connect方法
  9.          * */
  10.         Bootstrap bootstrap = new Bootstrap();
  11.         /**
  12.          * 服务端是EventLoopGroup,客户端是NioEventLoopGroup
  13.          * 这里创建默认0个线程,一个线程工厂,一个选择器提供者
  14.          * */
  15.         NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
  16.         try {
  17.             bootstrap.group(eventLoopGroup)
  18.                     /**
  19.                      * 初始化socket,定义tcp连接的实例
  20.                      * */
  21.                     .channel(NioSocketChannel.class)
  22.                     .handler(new ChannelInitializer<SocketChannel>() {
  23.                         @Override
  24.                         protected void initChannel(SocketChannel ch) throws Exception {
  25.                             ChannelPipeline pipeline = ch.pipeline();
  26.                             /**
  27.                              * 进行字符串的转换
  28.                              * */
  29.                             pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
  30.                             pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
  31.                             /**
  32.                              * 自定义处理器
  33.                              * */
  34.                             pipeline.addLast(new ClientHandler1());
  35.                         }
  36.                     });
  37.             ChannelFuture future = bootstrap.connect(IP, PORT).sync();
  38.             log.info("客户端访问");
  39.             future.channel().closeFuture().sync();
  40.         } finally {
  41.             eventLoopGroup.shutdownGracefully();
  42.         }
  43.     }
  44. }
复制代码
这段代码实现了一个使用Netty框架的客户端,它连接到指定的服务器端并与服务器举行通讯。
     
  1. @Slf4j
  2. public class ClientHandler1 extends ChannelInboundHandlerAdapter {
  3.     @Override
  4.     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  5.         log.info("客户端读取的信息:{}", msg);
  6.         ctx.channel().writeAndFlush("客户端writeAndFlush:我是客户端");
  7.         TimeUnit.MILLISECONDS.sleep(5000);
  8.     }
  9.     /**
  10.      * 当事件到达pipeline时候触发
  11.      */
  12.     @Override
  13.     public void channelActive(ChannelHandlerContext ctx) throws Exception {
  14.         ctx.channel().writeAndFlush("客户端:开始聊天");
  15.     }
  16.     @Override
  17.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  18.         cause.printStackTrace();
  19.         //关闭Channel连接
  20.         ctx.close();
  21.     }
  22. }
复制代码

结果

服务端日志
  1. Client Address ====== /127.0.0.1:63740,读取的信息:客户端:开始聊天
  2. Client Address ====== /127.0.0.1:63740,读取的信息:客户端writeAndFlush:我是客户端
  3. Client Address ====== /127.0.0.1:63740,读取的信息:客户端writeAndFlush:我是客户端
复制代码
客户端日志
  1. 客户端读取的信息:服务端writeAndFlush:我是服务端
  2. 客户端读取的信息:服务端writeAndFlush:我是服务端
复制代码
总结


引导类-Bootstarp和ServerBootstrap

Bootstarp和ServerBootstrap被称为引导类,使你的应用程序和网络层相隔离。类似java项目的启动类。
连接-NioSocketChannel

客户端和服务端的启动都是采用设置的channel去连接处置惩罚器,这里服务端和客户端是用NioSocketChannel
变乱组-EventLoopGroup和NioEventLoopGroup

客户端使用的是NioEventLoopGroup,服务端使用的是EventLoopGroup。 服务端和客户端的引导类启动后实现了设置的运行,客户端和服务端的连接都是采用NioSocketChannel。 连接的流程:
     现实上服务端的变乱组也可以使用NioEventLoopGroup。
![在这里插入图片形貌](https://img-blog.csdnimg.cn/direct/5e676670b49e40dd83155e76094e9017.png
送书活动