netty spring 實現handler 配置

需求

基礎

netty 中接收一個數據處理流程 inboundHandler1->inboundHandler2->inboundHandler3
netty 發送一個數據的處理流程outboundHandler3->outboundHandler2->outboundHandler1

我們使用 netty 開發的時候很多初始化的代碼都是重複的,一般都是 handler(數據的處理邏輯) 會根據業務的不同進行變化。
我們現在實現 spring 配置文件中動態的配置 handler。

在 spring 的配置文件 application.xml 中可以這樣動態的配置 handler 。

<constructor-arg name="adapters">
   <list>
      <value>inbound1</value>
      <value>inbound2</value>
      <value>serverHandler</value>
      <value>outbound1</value>
      <value>outbound2</value>
   </list>
</constructor-arg>

實現

首先我們先實現一個 netty tcp server 入口類 NettyTcpServer.

package netty;

public interface IServer {
   /**
    * 啓動服務器
    */
   void start();

   /**
    * 停止服務器
    */
   void stop();
}
/**
 * 創建日期: 2017/10/18
 * 創建作者:helloworldyu
 * 文件名稱:NettyTcpServer.java
 * 功能:
 */
package netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.Future;
import io.netty.util.internal.logging.InternalLoggerFactory;
import io.netty.util.internal.logging.Slf4JLoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 功能:
 *
 * 創建作者:helloworldyu
 * 文件名稱:NettyTcpServer.java
 * 創建日期: 2017/10/18
 */
public class NettyTcpServer implements IServer{
   private static final Logger logger = LoggerFactory.getLogger(NettyTcpServer.class);


   /**
    * 初始化 netty 的日誌系統
    */
   static {
      InternalLoggerFactory.setDefaultFactory(Slf4JLoggerFactory.INSTANCE);
   }

   private int port = 8080;
   private ChannelInitializer<SocketChannel> channelInitializer;

   /**
    * 接收請求的 nio 池
    */
   private EventLoopGroup bossGroup = new NioEventLoopGroup();
   /**
    * 接收數據的 nio 池
    */
   private EventLoopGroup workerGroup = new NioEventLoopGroup();

   public int getPort() {
      return port;
   }

   public void setPort(int port) {
      this.port = port;
   }

   public ChannelInitializer<SocketChannel> getChannelInitializer() {
      return channelInitializer;
   }

   public void setChannelInitializer(ChannelInitializer<SocketChannel> channelInitializer) {
      this.channelInitializer = channelInitializer;
   }

   @Override
   public void start() {
      ServerBootstrap b = new ServerBootstrap();
      //指定接收鏈接的 NioEventLoop,和接收數據的 NioEventLoop
      b.group(bossGroup, workerGroup);
      //指定server使用的 channel
      b.channel(NioServerSocketChannel.class);
      //初始化處理請求的編解碼,處理響應類等
      b.childHandler(channelInitializer);
      b.option(ChannelOption.SO_BACKLOG,1024);
      b.option(ChannelOption.SO_REUSEADDR,true);
      try {
         // 服務器綁定端口監聽
         b.bind(port).sync();
         logger.info("啓動服務器成功,port={}",port);
      }catch (InterruptedException e){
         //錯誤日誌
         logger.error("啓動服務器報錯:",e);
      }
   }

   @Override
   public void stop(){
      //異步關閉 EventLoop
      Future<?> future = bossGroup.shutdownGracefully();
      Future<?> future1 = workerGroup.shutdownGracefully();

      //等待關閉成功
      future.syncUninterruptibly();
      future1.syncUninterruptibly();
      logger.info("退出服務器成功");
   }

}

這個類有兩個重要的參數 port 和 channelInitializer。其中 port 爲監聽的端口號。channelInitializer 爲服務初始化相關的,這個類也是我們的重點。

初始化類 NettyTcpServerInitializer

NettyTcpServerInitializer 繼承了ChannelInitializer,同時實現了 spring 的 ApplicationContextAware 接口。在 bean 初始化的時候獲取所有的 spring 的上下文信息(這裏爲了獲取其他的 bean)。

核心部分向 pipeline 中添加 handler 的時候是通過傳進來的 bean 名字從 spring 中獲取的。這也是爲什麼我們實現了 spring 的ApplicationContextAware。另外由於我經常會用到 IdleStateHandler 這個超時處理 handler 所以在這裏專門單獨處理了。實際上可以和普通的 handler 來實現。

pipeline.addLast("logging",new LoggingHandler(this.logLevel));
//添加超時處理器
if( null != this.idleStateHandler ){
   IdleStateHandler handler = (IdleStateHandler)getBean(this.idleStateHandler);
   pipeline.addLast(handler);
}
//添加處理器
this.adapters.stream().forEach(c->{
   //通過初始化傳進來的 bean 名字來初始化獲取 bean
   ChannelHandlerAdapter handler = (ChannelHandlerAdapter)getBean(c);
   pipeline.addLast(handler);
});

完整代碼:

/**
 * 創建日期: 2017/10/18
 * 創建作者:helloworldyu
 * 文件名稱:NettyTcpServerInitializer.java
 * 功能:
 */
package netty;

import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.util.List;

/**
 * 功能: nettyserver 初始化相關的參數
 *
 *  * @author helloworldyu
 * 文件名稱:NettyTcpServerInitializer.java
 * 創建日期: 2017/10/18
 */
public class NettyTcpServerInitializer extends ChannelInitializer<SocketChannel> implements ApplicationContextAware{
   private static final Logger logger = LoggerFactory.getLogger(NettyTcpServerInitializer.class);


   /**
    * spring 的 bean 信息
    */
   private static ApplicationContext applicationContext;


   /**
    * 重寫以獲取 spring 的 bean 信息
    * @param applicationContext
    * @throws BeansException
    */
   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
      // The spring application context.
      NettyTcpServerInitializer.applicationContext = applicationContext;
   }

   /**
    * 根據beanName獲取bean
    */
   public static Object getBean(String beanName)
   {
      if (null == beanName)
      {
         return null;
      }
      return applicationContext.getBean(beanName);
   }


   /**
    * netty 日誌等級
    */
   private LogLevel logLevel;
   /**
    * 所有handler處理適配器
    */
   private List<String> adapters;
   /**
    * 空閒超時處理器
    */
   private String idleStateHandler;

   /**
    * 沒有超時處理的版本
    * @param logLevel
    * @param adapters
    */
   public NettyTcpServerInitializer(LogLevel logLevel, List<String> adapters){
      this.logLevel = logLevel;
      this.adapters = adapters;
   }

   /**
    * 有超時處理器的版本
    * @param logLevel 日誌等級
    * @param idleStateHandler 超時處理器
    * @param adapters  入站處理
    */
   public NettyTcpServerInitializer(LogLevel logLevel,
                                    String idleStateHandler,
                                    List<String> adapters) {
      this.logLevel = logLevel;
      this.adapters = adapters;
      this.idleStateHandler = idleStateHandler;
   }


   @Override
   protected void initChannel(SocketChannel ch){
      ChannelPipeline pipeline = ch.pipeline();

      //設置日誌
      pipeline.addLast("logging",new LoggingHandler(this.logLevel));
      //添加超時處理器
      if( null != this.idleStateHandler ){
         IdleStateHandler handler = (IdleStateHandler)getBean(this.idleStateHandler);
         pipeline.addLast(handler);
      }
      //添加處理器
      this.adapters.stream().forEach(c->{
         //通過初始化傳進來的 bean 名字來初始化獲取 bean
         ChannelHandlerAdapter handler = (ChannelHandlerAdapter)getBean(c);
         pipeline.addLast(handler);
      });

      logger.debug("新客戶端鏈接:{}",this.toString());
   }

   @Override
   public String toString() {
      return "\n========================================================================\n"+
            "NettyTcpServerInitializer\n" +
            "logLevel=" + logLevel + "\n"+
            "adapters=" + adapters + "\n"+
            "idleStateHandlers=" + idleStateHandler +"\n"+
            "========================================================================\n"
            ;
   }
}

下面是spring 中的配置文件

配置文件中的 inbound1,inbound2,serverHandler,outbound1,outbound2 是測試用的代碼。後面會給全。
核心部分: 添加不通的 handler

 <bean id="channelInitializer" class="netty.NettyTcpServerInitializer" scope="prototype">
      <!--netty 調試日誌的輸出等級-->
      <constructor-arg name="logLevel" value="DEBUG"/>
      <constructor-arg name="idleStateHandler" value="idleStateHandler"/>
      <constructor-arg name="adapters">
         <list>
            <value>inbound1</value>
            <value>inbound2</value>
            <value>serverHandler</value>
            <value>outbound1</value>
            <value>outbound2</value>
         </list>
      </constructor-arg>
   </bean>

完整的代碼:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context-4.2.xsd">

   <!-- 掃描bean -->
   <context:component-scan base-package="com.yhy">
   </context:component-scan>

   <!-- 加載項目配置文件 -->
   <context:property-placeholder location="project.properties"/>

   <!--=============初始化 tcpserver ==========-->
   <!--指定處理的編解碼器,響應處理器-->
   <!--要注意是否可以重入,可以的話記得加 @Sharedable,否則scope 要是 prototype-->
   <bean id="inbound1" class="com.yhy.tcpserver.inbound.FirstInboundChannel" scope="prototype"/>
   <bean id="inbound2" class="com.yhy.tcpserver.inbound.SecondInboundChannle" scope="prototype"/>
   <bean id="serverHandler" class="com.yhy.tcpserver.inbound.TcpServerHandler" scope="prototype"/>
   <bean id="outbound1" class="com.yhy.tcpserver.outbound.FirstOutboundChannel" scope="prototype"/>
   <bean id="outbound2" class="com.yhy.tcpserver.outbound.SecondOutboundChannel" scope="prototype"/>


   <!--超時處理器-->
   <bean id="idleStateHandler" class="io.netty.handler.timeout.IdleStateHandler" scope="prototype">
      <constructor-arg name="readerIdleTime" value="0"/>
      <constructor-arg name="writerIdleTime" value="0"/>
      <constructor-arg name="allIdleTime" value="5"/>
      <constructor-arg name="unit" value="SECONDS"/>
   </bean>
   <!--指定處理的編解碼器,響應處理器-->
   <!--要注意是否可以重入,可以的話記得加 @Sharedable,否則scope 要是 prototype-->
   <bean id="channelInitializer" class="netty.NettyTcpServerInitializer" scope="prototype">
      <!--netty 調試日誌的輸出等級-->
      <constructor-arg name="logLevel" value="DEBUG"/>
      <constructor-arg name="idleStateHandler" value="idleStateHandler"/>
      <constructor-arg name="adapters">
         <list>
            <value>inbound1</value>
            <value>inbound2</value>
            <value>serverHandler</value>
            <value>outbound1</value>
            <value>outbound2</value>
         </list>
      </constructor-arg>
   </bean>
   <bean id="tpcserver" class="netty.NettyTcpServer"
        scope="singleton" init-method="start" destroy-method="stop">
      <!--初始化類,和端口號-->
      <property name="port" value="8899"/>
      <property name="channelInitializer" ref="channelInitializer"/>
   </bean>

</beans>

完整代碼

完整源代碼:spring-netty 模塊
https://gitee.com/yuhaiyang457288/netty-test

仔細看完有疑惑的,或者有建議的可以加 物聯網交流羣: 651219170

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章