Netty学习篇③--整合springboot

经过前面的netty学习,大概了解了netty各个组件的概念和作用,开始自己瞎鼓捣netty和我们常用的项目的整合(很简单的整合)
项目准备
工具:IDEA2017

jar包导入:maven

项目框架:springboot+netty
项目操作

右键创建一个maven项目,项目名称: hetangyuese-netty-03(项目已上传github)

项目完整结构

avatar

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的基础知识后,了解到很多rpc框架都运用了netty,看了下dubbo的netty源码部分,也能明白每一步的用途,接下来自己琢磨写个rpc框架试试,学无止境!!!
posted @ 2019-11-04 09:07  荷塘月色0519  阅读(509)  评论(0)    收藏  举报