需求
基础
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