Netty学习篇--整合springboot

经过前面的netty学习,大概了解了netty各个组件的概念和作用,开始自己瞎鼓捣netty和我们常用的项目的整合(很简单的整合)
  1. 项目准备
    工具:IDEA2017
    
    jar包导入:maven
    
    项目框架:springboot+netty
  2. 项目操作
    • 右键创建一个maven项目,项目名称: hetangyuese-netty-03(项目已上传github)

      项目完整结构

    ? Netty学习篇--整合springboot

    • maven导包

      <!-- netty start -->
      <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.15.Final</version>
      </dependency>
      <!-- netty end -->
      <!-- springboot start -->
      <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter</artifactId>
       </dependency>
      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
            <!-- 热部署 -->
      </dependency>
      <!-- springboot end -->
      
      // 之所以没版本,我是在parent项目中配置了maven的全局版本,只能在顶级项目中配置
      <parent>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-parent</artifactId>
              <version>1.5.6.RELEASE</version>
      </parent>
      <!-- 日志 slf4j及logback包 start -->
      <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
             <version>1.7.7</version>
      </dependency>
      
      <dependency>
             <groupId>ch.qos.logback</groupId>
             <artifactId>logback-core</artifactId>
             <version>1.1.7</version>
      </dependency>
      <dependency>
              <groupId>ch.qos.logback</groupId>
              <artifactId>logback-classic</artifactId>
              <version>1.1.7</version>
      </dependency>
      <!-- 日志 slf4j及logback包 end -->
    • 编码

      • springboot启动类 HetangyueseApplication

        因为需要集成netty启动类不再是继承SpringBootServletInitializer类修改为实现CommandLineRunner(CommandLineRunner项目启动后执行)

        package com.hetangyuese.netty;
        
        import com.hetangyuese.netty.controller.HtServer;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.boot.CommandLineRunner;
        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        
        /**
         * @program: netty-root
         * @description: 启动类
         * @author: hetangyuese
         * @create: 2019-10-28 16:47
         **/
        @SpringBootApplication
        public class HetangyueseApplication implements CommandLineRunner {
        
            @Autowired
            private HtServer htServer; // Netty服务端类
        
            public static void main(String[] args) {
                SpringApplication.run(HetangyueseApplication.class, args);
            }
        
            @Override
            public void run(String... strings) throws Exception {
                // 调用netty服务端启动方法
                htServer.start(9000);
            }
        }
      • Netty启动类

        package com.hetangyuese.netty.controller;
        
        import com.hetangyuese.netty.channel.HtServerChannel;
        import io.netty.bootstrap.ServerBootstrap;
        import io.netty.channel.ChannelFuture;
        import io.netty.channel.ChannelHandler;
        import io.netty.channel.ChannelOption;
        import io.netty.channel.EventLoopGroup;
        import io.netty.channel.nio.NioEventLoopGroup;
        import io.netty.channel.socket.nio.NioServerSocketChannel;
        import io.netty.handler.logging.LoggingHandler;
        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Component;
        
        /**
         * @program: netty-root
         * @description: ht服务类
         * @author: hetangyuese
         * @create: 2019-10-28 17:28
         **/
        @Component
        public class HtServer {
        
            private Logger log = LoggerFactory.getLogger(HtServer.class);
        
            /**
             * Netty服务端启动类
             */
            private ServerBootstrap serverBootstrap;
        
            /**
             *  服务通道
             */
            @Autowired
            private HtServerChannel htServerChannel;
        
            /**
             * Netty日志处理类,可以打印出入站出站的日志,方便排查 
             * 需搭配 channelPipeline.addLast(new LoggingHandler(LogLevel.INFO));
             * 使用
             */
            private ChannelHandler logging = new LoggingHandler();
        
            /**
             *
             * @param port 启动端口号
             */
            public void start(int port) {
                log.debug("htServer start port:{}", port);
                // 主线程组 用于处理连接
                EventLoopGroup boss = new NioEventLoopGroup(1);
                // 工作线程组用于处理业务逻辑
                EventLoopGroup work = new NioEventLoopGroup();
                try {
                    serverBootstrap = getServerBootstrap();
                    // 配置服务端启动类
                    serverBootstrap.group(boss, work)
                            .channel(NioServerSocketChannel.class)
                            .option(ChannelOption.SO_BACKLOG, 1024)
                            .childOption(ChannelOption.SO_REUSEADDR, true)
                            .handler(logging)
                            .childHandler(htServerChannel);
        
                    // 服务端绑定端口并持续等待
                    ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
                    // 通道持续阻塞等待直到关闭了服务
                    channelFuture.channel().closeFuture().sync();
                } catch (Exception e) {
                    // 输出错误日志
                    log.error("netty server start happened exception e:{}", e);
                } finally {
                    // 关闭线程组
                    boss.shutdownGracefully();
                    work.shutdownGracefully();
                }
            }
        
            /**
             *  初始化启动类
             * @return
             */
            public ServerBootstrap getServerBootstrap() {
                if (null == serverBootstrap) {
                    serverBootstrap = new ServerBootstrap();
                }
                return serverBootstrap;
            }
        }
      • 管道类(channel、pipeline)

        package com.hetangyuese.netty.channel;
        
        import com.hetangyuese.netty.handler.HtServerHandler;
        import io.netty.channel.Channel;
        import io.netty.channel.ChannelInitializer;
        import io.netty.channel.ChannelPipeline;
        import io.netty.handler.codec.string.StringDecoder;
        import io.netty.handler.logging.LogLevel;
        import io.netty.handler.logging.LoggingHandler;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Service;
        
        /**
         * @program: netty-root
         * @description: 配置管道
         * @author: hetangyuese
         * @create: 2019-10-28 17:35
         **/
        @Service
        public class HtServerChannel extends ChannelInitializer {
        
            @Autowired
            private HtServerHandler htServerHandler;
        
            @Override
            protected void initChannel(Channel ch) throws Exception {
                // 通道流水线 管理channelHandler的有序执行
                ChannelPipeline channelPipeline = ch.pipeline();
                // netty日志
                channelPipeline.addLast(new LoggingHandler(LogLevel.INFO));
                // 字符串解码器 接收到数据直接转为string 这里没有弄自定义和其他的解码器
                channelPipeline.addLast(new StringDecoder());
                // 业务逻辑处理类
                channelPipeline.addLast(htServerHandler);
            }
        }
      • 业务逻辑类(handler)

        服务端ChannelPipeline中有许多的ChannelHandler, 如果每个都实例化一个ChannelHandler,在大量的客户端连接的时候将会产生大量的ChannelHandler实例,为了解决这个问题netty中可以通过@ChannelHandler.Sharable注解实现共享实例,由这一个实例去处理客户端连接

        package com.hetangyuese.netty.handler;
        
        import io.netty.buffer.Unpooled;
        import io.netty.channel.ChannelHandler;
        import io.netty.channel.ChannelHandlerContext;
        import io.netty.channel.ChannelInboundHandlerAdapter;
        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;
        import org.springframework.stereotype.Service;
        
        /**
         * @program: netty-root
         * @description: 处理类
         * @author: hetangyuese
         * @create: 2019-10-28 17:39
         **/
        @ChannelHandler.Sharable
        @Service
        public class HtServerHandler extends ChannelInboundHandlerAdapter {
        
            private Logger log = LoggerFactory.getLogger(HtServerHandler.class);
        
            @Override
            public void channelActive(ChannelHandlerContext ctx) throws Exception {
                log.debug("channel已注册");
                ctx.writeAndFlush(Unpooled.copiedBuffer("xixixix".getBytes()));
            }
        
            /**
             *  服务端接收到的数据
             * @param ctx
             * @param msg
             * @throws Exception
             */
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                log.debug("htServer receive" + (String)msg);
            }
        
            /**
             *  服务端接收完毕事件
             * @param ctx
             * @throws Exception
             */
            @Override
            public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
                ctx.writeAndFlush(Unpooled.copiedBuffer("Htserver readComplete".getBytes()));
            }
        
            /**
             *  异常捕获事件
             * @param ctx
             * @param cause
             * @throws Exception
             */
            @Override
            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                cause.printStackTrace();
                ctx.close();
            }
        }
      • 配置文件(application.yml、logback.xml)

        application.yml文件
        
        spring:
          profiles:
            active: prod
        -----------------------------------------------------------
        application-prod.yml
        
        server:
          port: 8081
        <?xml version="1.0" encoding="UTF-8"?>
        <configuration>
         <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径 -->
         <property name="LOG_HOME" value="log" />
         <!-- 控制台输出日志 -->
         <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
             <encoder>
                 <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger -%msg%n
                 </pattern>
             </encoder>
         </appender>
        
         <!-- 文件输出指定项目日志 -->
         <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
             <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                 <fileNamePattern>${LOG_HOME}/netty03.%d{yyyy-MM-dd}.log</fileNamePattern>
                 <maxHistory>30</maxHistory>
             </rollingPolicy>
        
             <encoder>
                 <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
                 </pattern>
             </encoder>
         </appender>
        
         <!-- 异步输出指定项目日志 -->
         <appender name="async" class="ch.qos.logback.classic.AsyncAppender">
             <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
             <discardingThreshold>0</discardingThreshold>
             <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
             <queueSize>512</queueSize>
             <!-- 添加附加的appender,最多只能添加一个 -->
             <appender-ref ref="file" />
         </appender>
        
         <logger name="org.apache" level="info">
             <appender-ref ref="async" />
             <appender-ref ref="stdout"/>
         </logger>
        
         <logger name="org.springframework" level="info">
             <appender-ref ref="async" />
             <appender-ref ref="stdout"/>
         </logger>
        
         <logger name="com.hetangyuese" level="debug">
             <appender-ref ref="async" />
             <appender-ref ref="stdout"/>
         </logger>
        </configuration>
    • 启动(客户端我就不贴代码了)

      // 服务端
      2019-10-29 16:10:20 [restartedMain] DEBUG com.hetangyuese.netty.controller.HtServer -htServer start port:9000
      2019-10-29 16:10:48 [nioEventLoopGroup-3-1] DEBUG com.hetangyuese.netty.handler.HtServerHandler -channel已注册
      2019-10-29 16:10:48 [nioEventLoopGroup-3-1] DEBUG com.hetangyuese.netty.handler.HtServerHandler -htServer receivehello!_My name is hanleilei !_What is your name !_How are you? !_
      
      
      // 客户端
      服务端返回str: xixixix
      服务端返回str: Htserver readComplete

总结

学习了netty的基础知识后,了解到很多应用框架都运用了netty,看了下dubbo的netty源码部分,也能看明白每一步的用途,学无止境!!!

相关推荐