第一次提交
This commit is contained in:
25
fastbee-server/iot-server-core/pom.xml
Normal file
25
fastbee-server/iot-server-core/pom.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>fastbee-server</artifactId>
|
||||
<groupId>com.fastbee</groupId>
|
||||
<version>3.8.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>iot-server-core</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.fastbee</groupId>
|
||||
<artifactId>base-server</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fastbee</groupId>
|
||||
<artifactId>fastbee-mq</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,77 @@
|
||||
package com.fastbee.server;
|
||||
|
||||
|
||||
import com.fastbee.server.config.NettyConfig;
|
||||
import io.netty.bootstrap.AbstractBootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
/**
|
||||
* 基础服务器启动类
|
||||
*
|
||||
* @Author guanshubiao
|
||||
* @Date 2022/9/12 20:22
|
||||
*/
|
||||
@Slf4j
|
||||
@NoArgsConstructor
|
||||
public abstract class Server {
|
||||
|
||||
protected EventLoopGroup bossGroup;
|
||||
protected EventLoopGroup workerGroup;
|
||||
protected ExecutorService businessService;
|
||||
protected boolean isRunning;
|
||||
public NettyConfig config;
|
||||
|
||||
|
||||
|
||||
protected Server(NettyConfig config){
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/*初始化方法*/
|
||||
protected abstract AbstractBootstrap initialize();
|
||||
|
||||
|
||||
public synchronized boolean start() {
|
||||
if (isRunning) {
|
||||
log.warn("=>服务:{},在端口:{},已经运行", config.name, config.port);
|
||||
return isRunning;
|
||||
}
|
||||
AbstractBootstrap bootstrap = initialize();
|
||||
ChannelFuture future = bootstrap.bind(config.port).awaitUninterruptibly();
|
||||
future.channel().closeFuture().addListener(event -> {
|
||||
if (isRunning) {
|
||||
stop();
|
||||
}
|
||||
});
|
||||
if (isRunning = future.isSuccess()) {
|
||||
log.info("=>服务:{},在端口:{},启动成功!", config.name, config.port);
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
if (future.cause() != null) {
|
||||
log.error("服务启动失败", future.cause());
|
||||
}
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
|
||||
public synchronized void stop() {
|
||||
|
||||
isRunning = false;
|
||||
bossGroup.shutdownGracefully();
|
||||
if (workerGroup != null) {
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
if (businessService != null) {
|
||||
businessService.shutdown();
|
||||
}
|
||||
log.warn("=>服务:{},在端口:{},已经停止!", config.name, config.port);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package com.fastbee.server;
|
||||
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.server.config.NettyConfig;
|
||||
import com.fastbee.server.handler.*;
|
||||
import io.netty.bootstrap.AbstractBootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioChannelOption;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* TCP服务
|
||||
* 粘包处理为 分隔符和固定长度
|
||||
* 需要其他方式处理粘包,按照流程添加
|
||||
*
|
||||
* @author bill
|
||||
*/
|
||||
public class TCPServer extends Server {
|
||||
|
||||
public TCPServer(NettyConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractBootstrap initialize() {
|
||||
bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory(config.name, Thread.MAX_PRIORITY));
|
||||
workerGroup = new NioEventLoopGroup(config.workerCore, new DefaultThreadFactory(config.name, Thread.MAX_PRIORITY));
|
||||
if (config.businessCore > 0) {
|
||||
businessService = new ThreadPoolExecutor(config.businessCore, config.businessCore, 1L,
|
||||
TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new DefaultThreadFactory(config.name + "-B", true, Thread.NORM_PRIORITY));
|
||||
}
|
||||
return new ServerBootstrap()
|
||||
.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.option(NioChannelOption.SO_REUSEADDR, true)
|
||||
.option(NioChannelOption.SO_BACKLOG, 1024)
|
||||
.childOption(NioChannelOption.TCP_NODELAY, true)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
|
||||
/*第二个处理器 session处理*/
|
||||
private final TCPMessageAdapter adapter = new TCPMessageAdapter(config.sessionManager);
|
||||
/*3.解码 适配解码器 解码后业务处理*/
|
||||
private final MessageDecoderWrapper decoder = new MessageDecoderWrapper(config.decoder);
|
||||
/*3.编码 适配编码器-编码后业务处理*/
|
||||
private final MessageEncoderWrapper encoder = new MessageEncoderWrapper(config.encoder);
|
||||
/*4.编解码后消息分发器 同步和异步处理*/
|
||||
private final DispatcherHandler dispatcher = new DispatcherHandler(config.handlerMapping, config.handlerInterceptor, bossGroup);
|
||||
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.pipeline()
|
||||
.addLast(new IdleStateHandler(config.readerIdleTime, config.writerIdleTime, config.allIdleTime)) //设置心跳时间
|
||||
// .addLast(FastBeeConstant.SERVER.FRAMEDECODER, frameDecoder())//粘包处理器
|
||||
.addLast(FastBeeConstant.SERVER.ADAPTER, adapter)//消息适配器
|
||||
.addLast(FastBeeConstant.SERVER.DECODER, decoder) //报文解码器
|
||||
.addLast(FastBeeConstant.SERVER.ENCODER, encoder) //报文编码器
|
||||
.addLast(FastBeeConstant.SERVER.DISPATCHER, dispatcher); //消息分发
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加TCP粘包处理器
|
||||
*/
|
||||
private ByteToMessageDecoder frameDecoder() {
|
||||
if (config.lengthField == null) {
|
||||
/*分隔符处理器,报文以固定包头包尾结束*/
|
||||
return new DelimiterBasedFrameDecoder(config.maxFrameLength, config.delimiters);
|
||||
}
|
||||
/*报文长度的,以长度固定处理器和分隔符处理器 处理*/
|
||||
return new LengthFieldAndDelimiterFrameDecoder(config.maxFrameLength, config.lengthField, config.delimiters);
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.fastbee.server;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.server.config.NettyConfig;
|
||||
import com.fastbee.server.handler.DispatcherHandler;
|
||||
import com.fastbee.server.handler.MessageDecoderWrapper;
|
||||
import com.fastbee.server.handler.MessageEncoderWrapper;
|
||||
import com.fastbee.server.handler.UDPMessageAdapter;
|
||||
import io.netty.bootstrap.AbstractBootstrap;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioChannelOption;
|
||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* UDP服务
|
||||
*
|
||||
* @author gsb
|
||||
* @date 2022/11/7 13:44
|
||||
*/
|
||||
public class UDPServer extends Server {
|
||||
|
||||
public UDPServer(NettyConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractBootstrap initialize() {
|
||||
bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory(config.name, Thread.MAX_PRIORITY));
|
||||
if (config.businessCore > 0) {
|
||||
businessService = new ThreadPoolExecutor(config.businessCore, config.businessCore, 1L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(), new DefaultThreadFactory(config.name + "-B", true, Thread.NORM_PRIORITY));
|
||||
}
|
||||
return new Bootstrap()
|
||||
.group(bossGroup)
|
||||
.channel(NioDatagramChannel.class)
|
||||
.option(NioChannelOption.SO_REUSEADDR, true)
|
||||
.option(NioChannelOption.SO_RCVBUF, 1024 * 1024 * 50)
|
||||
.handler(new ChannelInitializer<NioDatagramChannel>() {
|
||||
|
||||
private final UDPMessageAdapter adapter = UDPMessageAdapter.newInstance(config.sessionManager, config.readerIdleTime, config.delimiters);
|
||||
private final MessageDecoderWrapper decoder = new MessageDecoderWrapper(config.decoder);
|
||||
private final MessageEncoderWrapper encoder = new MessageEncoderWrapper(config.encoder);
|
||||
private final DispatcherHandler dispatcherHandler = new DispatcherHandler(config.handlerMapping, config.handlerInterceptor, businessService);
|
||||
|
||||
@Override
|
||||
protected void initChannel(NioDatagramChannel channel) throws Exception {
|
||||
channel.pipeline()
|
||||
.addLast(FastBeeConstant.SERVER.ADAPTER, adapter)
|
||||
.addLast(FastBeeConstant.SERVER.DECODER, decoder)
|
||||
.addLast(FastBeeConstant.SERVER.ENCODER, encoder)
|
||||
.addLast(FastBeeConstant.SERVER.DISPATCHER, dispatcherHandler);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,274 @@
|
||||
package com.fastbee.server.config;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.enums.ServerType;
|
||||
import com.fastbee.server.Server;
|
||||
import com.fastbee.server.TCPServer;
|
||||
import com.fastbee.server.UDPServer;
|
||||
import com.fastbee.base.codec.Delimiter;
|
||||
import com.fastbee.base.codec.LengthField;
|
||||
import com.fastbee.base.codec.MessageDecoder;
|
||||
import com.fastbee.base.codec.MessageEncoder;
|
||||
import com.fastbee.base.core.HandlerInterceptor;
|
||||
import com.fastbee.base.core.HandlerMapping;
|
||||
import com.fastbee.base.session.SessionManager;
|
||||
import io.netty.util.NettyRuntime;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
/**
|
||||
* 基础配置类
|
||||
* @Author guanshubiao
|
||||
* @Date 2022/9/12 20:22
|
||||
*/
|
||||
public class NettyConfig {
|
||||
|
||||
public final int workerCore;
|
||||
/*boss线程核数*/
|
||||
public final int businessCore;
|
||||
/*读空闲时间*/
|
||||
public final int readerIdleTime;
|
||||
/*写空闲时间*/
|
||||
public final int writerIdleTime;
|
||||
/*读写空闲时间*/
|
||||
public final int allIdleTime;
|
||||
/*端口*/
|
||||
public final Integer port;
|
||||
/*TCP/UDP数据最大长度限定*/
|
||||
public final Integer maxFrameLength;
|
||||
/*基础编码*/
|
||||
public final MessageDecoder decoder;
|
||||
/*基础解码*/
|
||||
public final MessageEncoder encoder;
|
||||
public final Delimiter[] delimiters;
|
||||
public final LengthField lengthField;
|
||||
public final HandlerMapping handlerMapping;
|
||||
public final HandlerInterceptor handlerInterceptor;
|
||||
public final SessionManager sessionManager;
|
||||
/*基础服务端*/
|
||||
public Server server;
|
||||
public String name;
|
||||
/*服务名*/
|
||||
public final ServerType type;
|
||||
|
||||
|
||||
|
||||
|
||||
public NettyConfig(int workerGroup,
|
||||
int businessGroup,
|
||||
int readerIdleTime,
|
||||
int writerIdleTime,
|
||||
int allIdleTime,
|
||||
Integer port,
|
||||
Integer maxFrameLength,
|
||||
LengthField lengthField,
|
||||
Delimiter[] delimiters,
|
||||
MessageDecoder decoder,
|
||||
MessageEncoder encoder,
|
||||
HandlerMapping handlerMapping,
|
||||
HandlerInterceptor handlerInterceptor,
|
||||
SessionManager sessionManager,
|
||||
ServerType type,
|
||||
String name,
|
||||
Server server) {
|
||||
|
||||
/*校验值是否正确*/
|
||||
ObjectUtil.checkNotNull(port, FastBeeConstant.SERVER.PORT);
|
||||
ObjectUtil.checkPositive(port, FastBeeConstant.SERVER.PORT);
|
||||
|
||||
if (ServerType.UDP == type || ServerType.TCP == type){
|
||||
ObjectUtil.checkNotNull(decoder, "decoder");
|
||||
ObjectUtil.checkNotNull(encoder, "encoder");
|
||||
ObjectUtil.checkNotNull(handlerMapping, "handlerMapping");
|
||||
ObjectUtil.checkNotNull(handlerInterceptor, "handlerInterceptor");
|
||||
}
|
||||
if (type == ServerType.TCP){
|
||||
ObjectUtil.checkNotNull(maxFrameLength, FastBeeConstant.SERVER.MAXFRAMELENGTH);
|
||||
ObjectUtil.checkPositive(maxFrameLength, FastBeeConstant.SERVER.MAXFRAMELENGTH);
|
||||
// ObjectUtil.checkNotNull(delimiters,FastBeeConstant.SERVER.DELIMITERS);
|
||||
|
||||
}
|
||||
/*获取核数*/
|
||||
int processors = NettyRuntime.availableProcessors();
|
||||
this.workerCore = workerGroup > 0 ? workerGroup : processors + 2;
|
||||
this.businessCore = businessGroup > 0 ? businessGroup : Math.max(1, processors >> 1);
|
||||
this.readerIdleTime = readerIdleTime;
|
||||
this.writerIdleTime = writerIdleTime;
|
||||
this.allIdleTime = allIdleTime;
|
||||
this.port = port;
|
||||
this.maxFrameLength = maxFrameLength;
|
||||
this.lengthField = lengthField;
|
||||
this.delimiters = delimiters;
|
||||
this.decoder = decoder;
|
||||
this.encoder = encoder;
|
||||
this.handlerMapping = handlerMapping;
|
||||
this.handlerInterceptor = handlerInterceptor;
|
||||
this.sessionManager = sessionManager != null ? sessionManager : new SessionManager();
|
||||
this.type = type;
|
||||
|
||||
switch (type){
|
||||
case TCP:
|
||||
this.server = new TCPServer(this);
|
||||
this.name = name != null ? name : ServerType.TCP.name();
|
||||
break;
|
||||
case UDP:
|
||||
this.name = name != null ? name : ServerType.UDP.name();
|
||||
this.server = new UDPServer(this);
|
||||
break;
|
||||
case MQTT:
|
||||
case WEBSOCKET:
|
||||
this.name = name != null ? name : ServerType.MQTT.name();
|
||||
this.server = server;
|
||||
this.server.config = this;
|
||||
break;
|
||||
case HTTP:
|
||||
this.name = name != null ? name : ServerType.HTTP.name();
|
||||
this.server = server;
|
||||
this.server.config = this;
|
||||
break;
|
||||
case COAP:
|
||||
this.name = name != null ? name : ServerType.COAP.name();
|
||||
this.server = server;;
|
||||
this.server.config = this;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Server build() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public static NettyConfig.Builder custom() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private int workerCore;
|
||||
private int businessCore ;
|
||||
private int readerIdleTime ;
|
||||
private int writerIdleTime = 0;
|
||||
private int allIdleTime = 0;
|
||||
private Integer port;
|
||||
private Integer maxFrameLength;
|
||||
private LengthField lengthField;
|
||||
private Delimiter[] delimiters;
|
||||
private MessageDecoder decoder;
|
||||
private MessageEncoder encoder;
|
||||
private HandlerMapping handlerMapping;
|
||||
private HandlerInterceptor handlerInterceptor;
|
||||
private SessionManager sessionManager;
|
||||
private ServerType type;
|
||||
private String name;
|
||||
private Server server;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
public Builder setThreadGroup(int workerCore, int businessCore) {
|
||||
this.workerCore = workerCore;
|
||||
this.businessCore = businessCore;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setIdleStateTime(int readerIdleTime, int writerIdleTime, int allIdleTime) {
|
||||
this.readerIdleTime = readerIdleTime;
|
||||
this.writerIdleTime = writerIdleTime;
|
||||
this.allIdleTime = allIdleTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPort(Integer port) {
|
||||
this.port = port;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setServer(Server server){
|
||||
this.server = server;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMaxFrameLength(Integer maxFrameLength) {
|
||||
this.maxFrameLength = maxFrameLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLengthField(LengthField lengthField) {
|
||||
this.lengthField = lengthField;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDelimiters(byte[][] delimiters) {
|
||||
Delimiter[] t = new Delimiter[delimiters.length];
|
||||
for (int i = 0; i < delimiters.length; i++) {
|
||||
t[i] = new Delimiter(delimiters[i]);
|
||||
}
|
||||
this.delimiters = t;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDelimiters(Delimiter... delimiters) {
|
||||
this.delimiters = delimiters;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDecoder(MessageDecoder decoder) {
|
||||
this.decoder = decoder;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setEncoder(MessageEncoder encoder) {
|
||||
this.encoder = encoder;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setHandlerMapping(HandlerMapping handlerMapping) {
|
||||
this.handlerMapping = handlerMapping;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setHandlerInterceptor(HandlerInterceptor handlerInterceptor) {
|
||||
this.handlerInterceptor = handlerInterceptor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSessionManager(SessionManager sessionManager) {
|
||||
this.sessionManager = sessionManager;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setType(ServerType type){
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Server build() {
|
||||
return new NettyConfig(
|
||||
this.workerCore,
|
||||
this.businessCore,
|
||||
this.readerIdleTime,
|
||||
this.writerIdleTime,
|
||||
this.allIdleTime,
|
||||
this.port,
|
||||
this.maxFrameLength,
|
||||
this.lengthField,
|
||||
this.delimiters,
|
||||
this.decoder,
|
||||
this.encoder,
|
||||
this.handlerMapping,
|
||||
this.handlerInterceptor,
|
||||
this.sessionManager,
|
||||
this.type,
|
||||
this.name,
|
||||
this.server
|
||||
).build();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
package com.fastbee.server.handler;
|
||||
|
||||
|
||||
import com.fastbee.base.codec.Delimiter;
|
||||
import com.fastbee.base.util.ByteBufUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.TooLongFrameException;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkPositive;
|
||||
|
||||
/**
|
||||
* 分隔符报文解码器 -消息进站处理步骤1 可选
|
||||
* @author bill
|
||||
*/
|
||||
public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
|
||||
|
||||
/*分隔符 例如报文头 0xFF 报文尾 0x0D*/
|
||||
private final Delimiter[] delimiters;
|
||||
/*最大帧长度*/
|
||||
private final int maxFrameLength;
|
||||
private final boolean failFast;
|
||||
/*是否丢弃超过固定长度的报文*/
|
||||
private boolean discardingTooLongFrame;
|
||||
/*最长帧长度*/
|
||||
private int tooLongFrameLength;
|
||||
|
||||
/*构造分隔符解码器*/
|
||||
public DelimiterBasedFrameDecoder(int maxFrameLength, Delimiter... delimiters) {
|
||||
this(maxFrameLength, true, delimiters);
|
||||
}
|
||||
|
||||
public DelimiterBasedFrameDecoder(int maxFrameLength, boolean failFast, Delimiter... delimiters) {
|
||||
validateMaxFrameLength(maxFrameLength);
|
||||
ObjectUtil.checkNonEmpty(delimiters, "delimiters");
|
||||
|
||||
this.delimiters = delimiters;
|
||||
this.maxFrameLength = maxFrameLength;
|
||||
this.failFast = failFast;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
/*根据分隔符处理粘包报文*/
|
||||
Object decoded = decode(ctx, in);
|
||||
if (decoded != null) {
|
||||
/*报文出站,流入下一个处理器*/
|
||||
out.add(decoded);
|
||||
}
|
||||
}
|
||||
|
||||
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) {
|
||||
// 使用所有分隔符并选择产生最短帧的分隔符
|
||||
int minFrameLength = Integer.MAX_VALUE;
|
||||
Delimiter minDelim = null;
|
||||
for (Delimiter delim : delimiters) {
|
||||
int frameLength = ByteBufUtils.indexOf(buffer, delim.value);
|
||||
if (frameLength >= 0 && frameLength < minFrameLength) {
|
||||
/*最小报文长度*/
|
||||
minFrameLength = frameLength;
|
||||
minDelim = delim;
|
||||
}
|
||||
}
|
||||
|
||||
if (minDelim != null) {
|
||||
int minDelimLength = minDelim.value.length;
|
||||
ByteBuf frame = null;
|
||||
|
||||
if (discardingTooLongFrame) {
|
||||
// 如果true,将长度不符合报文丢弃
|
||||
// 初始化原来的值
|
||||
discardingTooLongFrame = false;
|
||||
buffer.skipBytes(minFrameLength + minDelimLength);
|
||||
|
||||
int tooLongFrameLength = this.tooLongFrameLength;
|
||||
this.tooLongFrameLength = 0;
|
||||
if (!failFast) {
|
||||
fail(tooLongFrameLength);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/*小于最小长度帧处理*/
|
||||
if (minFrameLength > maxFrameLength) {
|
||||
//放弃读取帧
|
||||
buffer.skipBytes(minFrameLength + minDelimLength);
|
||||
fail(minFrameLength);
|
||||
return null;
|
||||
}
|
||||
/*是否需要跳过某字节*/
|
||||
if (minDelim.strip) {
|
||||
//忽略长度等于0的报文
|
||||
if (minFrameLength != 0) {
|
||||
frame = buffer.readRetainedSlice(minFrameLength);
|
||||
}
|
||||
buffer.skipBytes(minDelimLength);
|
||||
} else {
|
||||
if (minFrameLength != 0) {
|
||||
frame = buffer.readRetainedSlice(minFrameLength + minDelimLength);
|
||||
} else {
|
||||
buffer.skipBytes(minDelimLength);
|
||||
}
|
||||
}
|
||||
|
||||
return frame;
|
||||
} else {
|
||||
if (!discardingTooLongFrame) {
|
||||
if (buffer.readableBytes() > maxFrameLength) {
|
||||
// Discard the content of the buffer until a delimiter is found.
|
||||
tooLongFrameLength = buffer.readableBytes();
|
||||
buffer.skipBytes(buffer.readableBytes());
|
||||
discardingTooLongFrame = true;
|
||||
if (failFast) {
|
||||
fail(tooLongFrameLength);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Still discarding the buffer since a delimiter is not found.
|
||||
tooLongFrameLength += buffer.readableBytes();
|
||||
buffer.skipBytes(buffer.readableBytes());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void fail(long frameLength) {
|
||||
if (frameLength > 0) {
|
||||
throw new TooLongFrameException("frame length exceeds " + maxFrameLength + ": " + frameLength + " - discarded");
|
||||
} else {
|
||||
throw new TooLongFrameException("frame length exceeds " + maxFrameLength + " - discarding");
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateMaxFrameLength(int maxFrameLength) {
|
||||
checkPositive(maxFrameLength, "maxFrameLength");
|
||||
}
|
||||
}
|
@ -0,0 +1,208 @@
|
||||
package com.fastbee.server.handler;
|
||||
|
||||
import com.fastbee.base.core.HandlerInterceptor;
|
||||
import com.fastbee.base.core.HandlerMapping;
|
||||
import com.fastbee.base.core.hanler.BaseHandler;
|
||||
import com.fastbee.base.session.Packet;
|
||||
import com.fastbee.base.session.Session;
|
||||
import com.fastbee.base.util.Stopwatch;
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.DeviceReport;
|
||||
import com.fastbee.common.core.mq.DeviceReportBo;
|
||||
import com.fastbee.common.core.mq.DeviceTestReportBo;
|
||||
import com.fastbee.common.core.mq.message.DeviceMessage;
|
||||
import com.fastbee.common.core.protocol.Message;
|
||||
import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem;
|
||||
import com.fastbee.common.enums.ServerType;
|
||||
import com.fastbee.common.enums.TopicType;
|
||||
import com.fastbee.common.utils.DateUtils;
|
||||
import com.fastbee.modbus.pak.ModbusEndPoint;
|
||||
import com.fastbee.modbus.pak.TcpDtu;
|
||||
import com.fastbee.mq.redischannel.producer.MessageProducer;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
/**
|
||||
* 消息分发处理
|
||||
* @author bill
|
||||
*/
|
||||
@Slf4j
|
||||
@ChannelHandler.Sharable
|
||||
public class DispatcherHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final HandlerMapping handlerMapping;
|
||||
|
||||
private final HandlerInterceptor interceptor;
|
||||
|
||||
private final ExecutorService executor;
|
||||
|
||||
public static boolean STOPWATCH = false;
|
||||
|
||||
private static Stopwatch s;
|
||||
|
||||
public DispatcherHandler(HandlerMapping handlerMapping, HandlerInterceptor interceptor, ExecutorService executor) {
|
||||
if (STOPWATCH && s == null) {
|
||||
s = new Stopwatch().start();
|
||||
}
|
||||
this.handlerMapping = handlerMapping;
|
||||
this.interceptor = interceptor;
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* TCP-4 消息处理
|
||||
* @param ctx
|
||||
* @param msg
|
||||
*/
|
||||
@Override
|
||||
public final void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
if (STOPWATCH) {
|
||||
s.increment();
|
||||
}
|
||||
|
||||
Packet packet = (Packet) msg;
|
||||
Message request = packet.message;
|
||||
//判断是否注册
|
||||
Session session = packet.session;
|
||||
/**
|
||||
* TCP的数据包四种情况
|
||||
* messageId用于标记上传的包数据的标志位的值,isPackage:表示是否整个包上传
|
||||
* 1. 整包上传: 包含:设备编号(注册包),数据包(心跳包) -->处理注册上传 和 上报数据
|
||||
* 2. 单个注册包,或者心跳包上传,有标识位 -> 如0x80,表示注册包,只处理注册上传
|
||||
* 3. 单个数据包上传,有标识位 ,如 0x89 -->表示xxxx 解码数据包转发到MQ
|
||||
* 4. 单个数据包上传,无标识位 ,如 modbus --> 安装约定的协议处理
|
||||
*/
|
||||
// 4.单个数据包上传无标识位
|
||||
if (null == request.getMessageId()){
|
||||
this.handleReport(request);
|
||||
}
|
||||
//处理数据调试
|
||||
|
||||
//1. 整包上传: 包含:设备编号(注册包),数据包(心跳包) -->处理注册上传 和 上报数据
|
||||
if (request.getIsPackage()){
|
||||
//获取设备编号
|
||||
List<ThingsModelSimpleItem> items = ((DeviceReport) request).getThingsModelSimpleItem();
|
||||
for (ThingsModelSimpleItem item : items) {
|
||||
if (null !=item.getId() && item.getId().equals("dev_id")){
|
||||
request.setClientId(item.getValue());
|
||||
}else if (null != item.getId() && item.getId().equals("imei")){
|
||||
request.setClientId(item.getValue());
|
||||
}else if (null != item.getId() && item.getId().equals("id")){
|
||||
request.setClientId(item.getValue());
|
||||
}else if (null != item.getId() && item.getId().equals("device")){
|
||||
request.setClientId(item.getValue());
|
||||
}
|
||||
}
|
||||
if (null != request.getClientId()){
|
||||
((DeviceReport) request).setSerialNumber(request.getClientId());
|
||||
//先处理设备注册
|
||||
this.handleMessage(request,packet,ctx);
|
||||
}
|
||||
//处理消息转发
|
||||
this.handleReport(request);
|
||||
|
||||
// 2/3 单个注册包,心跳,数据包上传,匹配消息id处理
|
||||
}else if (null != request.getMessageId() && !"0".equals(request.getMessageId())){
|
||||
//处理心跳和设备注册
|
||||
this.handleMessage(request,packet,ctx);
|
||||
request.setClientId(request.getClientId()==null ? session.getClientId() : request.getClientId());
|
||||
this.handleOtherMsg(request);
|
||||
}
|
||||
|
||||
if (!session.isRegistered()){
|
||||
//未注册进行注册
|
||||
session.register(request);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMessage(Message message,Packet packet,ChannelHandlerContext ctx){
|
||||
/*获取消息的处理方法 根据注解 @PakMapping 匹配方法*/
|
||||
BaseHandler handler = handlerMapping.getHandler(Integer.parseInt(message.getMessageId()));
|
||||
|
||||
if (handler == null) {
|
||||
Message response = interceptor.notSupported(message, packet.session);
|
||||
if (response != null) {
|
||||
ctx.writeAndFlush(packet.replace(response));
|
||||
}
|
||||
} else {
|
||||
if (handler.async) {
|
||||
executor.execute(() -> channelRead0(ctx, packet, handler));
|
||||
} else {
|
||||
channelRead0(ctx, packet, handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void handleReport(Message request){
|
||||
DeviceReport report = (DeviceReport)request;
|
||||
DeviceReportBo reportBo = new DeviceReportBo();
|
||||
reportBo.setThingsModelSimpleItem(report.getThingsModelSimpleItem());
|
||||
reportBo.setPlatformDate(DateUtils.getNowDate());
|
||||
reportBo.setSerialNumber(report.getClientId());
|
||||
reportBo.setServerType(ServerType.TCP);
|
||||
reportBo.setReplyMessage(report.getReplyMessage());
|
||||
reportBo.setIsReply(report.getIsReply());
|
||||
reportBo.setStatus(report.getStatus());
|
||||
reportBo.setProtocolCode(FastBeeConstant.PROTOCOL.ModbusRtu);
|
||||
reportBo.setSources(report.getSources());
|
||||
MessageProducer.sendPublishMsg(reportBo);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送注册和心跳
|
||||
* @param request
|
||||
*/
|
||||
private void handleOtherMsg(Message request){
|
||||
DeviceReport report = (DeviceReport)request;
|
||||
DeviceTestReportBo reportBo = new DeviceTestReportBo();
|
||||
reportBo.setSources(report.getSources());
|
||||
reportBo.setProductId(report.getProductId());
|
||||
reportBo.setSerialNumber(report.getClientId());
|
||||
reportBo.setIsReply(false);
|
||||
List<ThingsModelSimpleItem> itemList = new ArrayList<>();
|
||||
ThingsModelSimpleItem item = new ThingsModelSimpleItem();
|
||||
item.setTs(new Date());
|
||||
item.setName("");
|
||||
item.setId(report.getClientId());
|
||||
item.setValue(report.getClientId());
|
||||
itemList.add(item);
|
||||
reportBo.setThingsModelSimpleItem(itemList);
|
||||
MessageProducer.sendDeviceTestMsg(reportBo);
|
||||
|
||||
}
|
||||
|
||||
private void channelRead0(ChannelHandlerContext ctx, Packet packet, BaseHandler handler) {
|
||||
Session session = packet.session;
|
||||
Message request = packet.message;
|
||||
Message response;
|
||||
try {
|
||||
//判断是否是前置拦截的消息类型,若不是则不处理
|
||||
if (!interceptor.beforeHandle(request, session)) {
|
||||
|
||||
}
|
||||
/*调用@PakMapping注解标注方法执行*/
|
||||
response = handler.invoke(request, session);
|
||||
//无返回值处理
|
||||
if (handler.returnVoid) {
|
||||
response = interceptor.successful(request, session);
|
||||
} else {
|
||||
//有返回值,进行AOP下一一个方法
|
||||
interceptor.afterHandle(request, response, session);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn(String.valueOf(request), e);
|
||||
response = interceptor.exceptional(request, session, e);
|
||||
}
|
||||
//有返回值,则应答设备
|
||||
if (response != null) {
|
||||
ctx.writeAndFlush(packet.replace(response));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
package com.fastbee.server.handler;
|
||||
|
||||
import com.fastbee.base.codec.Delimiter;
|
||||
import com.fastbee.base.codec.LengthField;
|
||||
import com.fastbee.base.util.ByteBufUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import io.netty.handler.codec.TooLongFrameException;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 固定长度报文解码 消息进站处理步骤1 可选
|
||||
* @author bill
|
||||
*/
|
||||
public class LengthFieldAndDelimiterFrameDecoder extends DelimiterBasedFrameDecoder{
|
||||
|
||||
|
||||
protected final byte[] prefix;
|
||||
private final int maxFrameLength;
|
||||
private final int lengthFieldOffset;
|
||||
private final int lengthFieldLength;
|
||||
private final int lengthFieldEndOffset;
|
||||
private final int lengthAdjustment;
|
||||
private final int initialBytesToStrip;
|
||||
private final boolean failFast;
|
||||
private boolean discardingTooLongFrame;
|
||||
private int tooLongFrameLength;
|
||||
private int bytesToDiscard;
|
||||
|
||||
public LengthFieldAndDelimiterFrameDecoder(int maxFrameLength, LengthField lengthField, Delimiter... delimiters) {
|
||||
this(maxFrameLength, true, lengthField, delimiters);
|
||||
}
|
||||
|
||||
public LengthFieldAndDelimiterFrameDecoder(int maxFrameLength, boolean failFast, LengthField lengthField, Delimiter... delimiters) {
|
||||
super(maxFrameLength, failFast, delimiters);
|
||||
ObjectUtil.checkPositive(maxFrameLength, "delimiterMaxFrameLength");
|
||||
ObjectUtil.checkNonEmpty(delimiters, "delimiters");
|
||||
|
||||
this.prefix = lengthField.prefix;
|
||||
this.maxFrameLength = lengthField.lengthFieldMaxFrameLength;
|
||||
this.lengthFieldOffset = lengthField.lengthFieldOffset;
|
||||
this.lengthFieldLength = lengthField.lengthFieldLength;
|
||||
this.lengthFieldEndOffset = lengthField.lengthFieldEndOffset;
|
||||
this.lengthAdjustment = lengthField.lengthAdjustment;
|
||||
this.initialBytesToStrip = lengthField.initialBytesToStrip;
|
||||
this.failFast = failFast;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
||||
if (discardingTooLongFrame) {
|
||||
discardingTooLongFrame(in);
|
||||
}
|
||||
|
||||
Object decoded;
|
||||
if (ByteBufUtils.startsWith(in, prefix)) {
|
||||
decoded = this.decode(ctx, in);
|
||||
} else {
|
||||
decoded = super.decode(ctx, in);
|
||||
}
|
||||
if (decoded != null) {
|
||||
out.add(decoded);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void discardingTooLongFrame(ByteBuf in) {
|
||||
int bytesToDiscard = this.bytesToDiscard;
|
||||
int localBytesToDiscard = Math.min(bytesToDiscard, in.readableBytes());
|
||||
in.skipBytes(localBytesToDiscard);
|
||||
bytesToDiscard -= localBytesToDiscard;
|
||||
this.bytesToDiscard = bytesToDiscard;
|
||||
this.failIfNecessary(false);
|
||||
}
|
||||
|
||||
private static void failOnNegativeLengthField(ByteBuf in, int frameLength, int lengthFieldEndOffset) {
|
||||
in.skipBytes(lengthFieldEndOffset);
|
||||
throw new CorruptedFrameException("negative pre-adjustment length field: " + frameLength);
|
||||
}
|
||||
|
||||
private static void failOnFrameLengthLessThanLengthFieldEndOffset(ByteBuf in, int frameLength, int lengthFieldEndOffset) {
|
||||
in.skipBytes(lengthFieldEndOffset);
|
||||
throw new CorruptedFrameException("Adjusted frame length (" + frameLength + ") is less than lengthFieldEndOffset: " + lengthFieldEndOffset);
|
||||
}
|
||||
|
||||
private void exceededFrameLength(ByteBuf in, int frameLength) {
|
||||
int discard = frameLength - in.readableBytes();
|
||||
this.tooLongFrameLength = frameLength;
|
||||
if (discard < 0) {
|
||||
in.skipBytes(frameLength);
|
||||
} else {
|
||||
this.discardingTooLongFrame = true;
|
||||
this.bytesToDiscard = discard;
|
||||
in.skipBytes(in.readableBytes());
|
||||
}
|
||||
|
||||
this.failIfNecessary(true);
|
||||
}
|
||||
|
||||
private static void failOnFrameLengthLessThanInitialBytesToStrip(ByteBuf in, int frameLength, int initialBytesToStrip) {
|
||||
in.skipBytes(frameLength);
|
||||
throw new CorruptedFrameException("Adjusted frame length (" + frameLength + ") is less than initialBytesToStrip: " + initialBytesToStrip);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) {
|
||||
if (in.readableBytes() < this.lengthFieldEndOffset) {
|
||||
return null;
|
||||
} else {
|
||||
int actualLengthFieldOffset = in.readerIndex() + this.lengthFieldOffset;
|
||||
int frameLength = this.getUnadjustedFrameLength(in, actualLengthFieldOffset, this.lengthFieldLength);
|
||||
if (frameLength < 0) {
|
||||
failOnNegativeLengthField(in, frameLength, this.lengthFieldEndOffset);
|
||||
}
|
||||
|
||||
frameLength += this.lengthAdjustment + this.lengthFieldEndOffset;
|
||||
if (frameLength < this.lengthFieldEndOffset) {
|
||||
failOnFrameLengthLessThanLengthFieldEndOffset(in, frameLength, this.lengthFieldEndOffset);
|
||||
}
|
||||
|
||||
if (frameLength > this.maxFrameLength) {
|
||||
this.exceededFrameLength(in, frameLength);
|
||||
return null;
|
||||
} else {
|
||||
if (in.readableBytes() < frameLength) {
|
||||
return null;
|
||||
} else {
|
||||
if (this.initialBytesToStrip > frameLength) {
|
||||
failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, initialBytesToStrip);
|
||||
}
|
||||
|
||||
in.skipBytes(this.initialBytesToStrip);
|
||||
int readerIndex = in.readerIndex();
|
||||
int actualFrameLength = frameLength - this.initialBytesToStrip;
|
||||
ByteBuf frame = in.retainedSlice(readerIndex, actualFrameLength);
|
||||
in.readerIndex(readerIndex + actualFrameLength);
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected int getUnadjustedFrameLength(ByteBuf buf, int offset, int length) {
|
||||
int frameLength;
|
||||
switch (length) {
|
||||
case 2:
|
||||
frameLength = buf.getUnsignedShort(offset);
|
||||
break;
|
||||
case 3:
|
||||
frameLength = buf.getUnsignedMedium(offset);
|
||||
break;
|
||||
case 4:
|
||||
frameLength = buf.getInt(offset);
|
||||
break;
|
||||
default:
|
||||
throw new DecoderException("unsupported lengthFieldLength: " + lengthFieldLength + " (expected: 2, 3, 4)");
|
||||
}
|
||||
return frameLength;
|
||||
}
|
||||
|
||||
private void failIfNecessary(boolean firstDetectionOfTooLongFrame) {
|
||||
if (this.bytesToDiscard == 0) {
|
||||
int tooLongFrameLength = this.tooLongFrameLength;
|
||||
this.tooLongFrameLength = 0;
|
||||
this.discardingTooLongFrame = false;
|
||||
if (!this.failFast || firstDetectionOfTooLongFrame) {
|
||||
this.fail(tooLongFrameLength);
|
||||
}
|
||||
} else if (this.failFast && firstDetectionOfTooLongFrame) {
|
||||
this.fail(this.tooLongFrameLength);
|
||||
}
|
||||
}
|
||||
|
||||
private void fail(long frameLength) {
|
||||
if (frameLength > 0) {
|
||||
throw new TooLongFrameException("Adjusted frame length exceeds " + this.maxFrameLength + ": " + frameLength + " - discarded");
|
||||
} else {
|
||||
throw new TooLongFrameException("Adjusted frame length exceeds " + this.maxFrameLength + " - discarding");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
package com.fastbee.server.handler;
|
||||
|
||||
import com.fastbee.common.core.mq.DeviceReport;
|
||||
import com.fastbee.common.core.protocol.Message;
|
||||
import com.fastbee.base.codec.MessageDecoder;
|
||||
import com.fastbee.base.session.Packet;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 基础消息解码 -进站消息-处理步骤3
|
||||
*
|
||||
* @author bill
|
||||
*/
|
||||
@Slf4j
|
||||
@ChannelHandler.Sharable
|
||||
public class MessageDecoderWrapper extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final MessageDecoder decoder;
|
||||
|
||||
public MessageDecoderWrapper(MessageDecoder decoder) {
|
||||
this.decoder = decoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* TCP-2.报文解码
|
||||
*/
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
/*取出上一站报文*/
|
||||
Packet packet = (Packet) msg;
|
||||
ByteBuf input = packet.take();
|
||||
try {
|
||||
/*匹配协议中适合的协议解码器 解码*/
|
||||
DeviceReport message = decoder.decode(input, packet.session.getClientId());
|
||||
if (message != null) {
|
||||
/*传递到下一个处理器*/
|
||||
ctx.fireChannelRead(packet.replace(message));
|
||||
}
|
||||
/*将当前readerIndex增加指定的长度,已经解码完成*/
|
||||
input.skipBytes(input.readableBytes());
|
||||
} catch (Exception e) {
|
||||
log.error("消息解码异常[" + ByteBufUtil.hexDump(input, 0, input.writerIndex()) + "]", e);
|
||||
throw new DecoderException(e);
|
||||
} finally {
|
||||
input.release();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package com.fastbee.server.handler;
|
||||
|
||||
import com.fastbee.base.codec.MessageEncoder;
|
||||
import com.fastbee.base.session.Packet;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOutboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.handler.codec.EncoderException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 基础消息编码
|
||||
* @author bill
|
||||
*/
|
||||
@Slf4j
|
||||
@ChannelHandler.Sharable
|
||||
public class MessageEncoderWrapper extends ChannelOutboundHandlerAdapter {
|
||||
private final MessageEncoder encoder;
|
||||
|
||||
public MessageEncoderWrapper(MessageEncoder encoder) {
|
||||
this.encoder = encoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
|
||||
Packet packet = (Packet) msg;
|
||||
ByteBuf output = packet.take();
|
||||
try {
|
||||
if (output == null) {
|
||||
output = encoder.encode(packet.message, packet.session.getClientId());
|
||||
}
|
||||
//是否可写,发送消息到客户端
|
||||
if (output.isReadable()) {
|
||||
ctx.write(packet.wrap(output), promise);
|
||||
} else {
|
||||
//不可写,释放byteBuf
|
||||
output.release();
|
||||
ctx.write(packet.wrap(Unpooled.EMPTY_BUFFER), promise);
|
||||
}
|
||||
output = null;
|
||||
} catch (EncoderException e) {
|
||||
log.error("消息编码异常" + packet.message, e);
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
log.error("消息编码异常" + packet.message, e);
|
||||
throw new EncoderException(e);
|
||||
} finally {
|
||||
if (output != null) {
|
||||
output.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
package com.fastbee.server.handler;
|
||||
|
||||
import com.fastbee.base.session.Packet;
|
||||
import com.fastbee.base.session.Session;
|
||||
import com.fastbee.base.session.SessionManager;
|
||||
import com.fastbee.base.util.AttributeUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.timeout.IdleState;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* TCP消息适配器,消息进栈-处理步骤2
|
||||
*
|
||||
* @author bill
|
||||
*/
|
||||
@ChannelHandler.Sharable
|
||||
@Slf4j
|
||||
public class TCPMessageAdapter extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final SessionManager sessionManager;
|
||||
|
||||
public TCPMessageAdapter(SessionManager sessionManager) {
|
||||
this.sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TCP -1 报文入口
|
||||
* 设备端消息响应 ,只处理设备session,报文传递到下一个处理器
|
||||
*/
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
ByteBuf buf = (ByteBuf) msg;
|
||||
/*session存储*/
|
||||
Session session = getSession(ctx);
|
||||
session.access();
|
||||
/*将设备端消息传递给下一个处理器*/
|
||||
ctx.fireChannelRead(Packet.of(session, buf));
|
||||
}
|
||||
|
||||
/**
|
||||
* 初次连接
|
||||
*/
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
log.info("=>设备端连接{}", ctx.channel().remoteAddress());
|
||||
/*session存储*/
|
||||
Session session = getSession(ctx);
|
||||
/*生成上线时间*/
|
||||
session.access();
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接,只处理设备状态,报文传递到下一个处理器
|
||||
*/
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
InetSocketAddress socketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
|
||||
//连接IP地址
|
||||
String host= socketAddress.getAddress().getHostAddress();
|
||||
Session session = AttributeUtils.getSession(ctx.channel());
|
||||
String clientId = session.getClientId();
|
||||
if (session != null) {
|
||||
session.setIp(host);
|
||||
session.invalidate();
|
||||
}
|
||||
ctx.channel().close();
|
||||
log.info("=>channelInactive,设备[{}]断开连接", clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理异常
|
||||
*/
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
if (cause instanceof IOException) {
|
||||
log.error("=>设备端断开连接session=[{}],error=[{}]", client(ctx), cause.getMessage());
|
||||
} else {
|
||||
log.error("=>消息处理异常session=[{}],error=[{}]", client(ctx), cause.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TCP客户端心跳处理
|
||||
* @param ctx
|
||||
* @param evt
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
InetSocketAddress socketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
|
||||
//连接IP地址
|
||||
String host= socketAddress.getAddress().getHostAddress();
|
||||
int port = socketAddress.getPort();
|
||||
if (evt instanceof IdleStateEvent) {
|
||||
IdleStateEvent event = (IdleStateEvent) evt;
|
||||
IdleState state = event.state();
|
||||
if (state == IdleState.READER_IDLE || state == IdleState.WRITER_IDLE || state == IdleState.ALL_IDLE) {
|
||||
log.error("=>设备心跳超时state=[{}],clientId=[{}]", state, client(ctx));
|
||||
ctx.close();
|
||||
Session session = AttributeUtils.getSession(ctx.channel());
|
||||
if (session != null) {
|
||||
//移除客户端
|
||||
session.setIp(host);
|
||||
// session.invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Session getSession(ChannelHandlerContext context) {
|
||||
Channel channel = context.channel();
|
||||
Session session = AttributeUtils.getSession(channel);
|
||||
if (session == null) {
|
||||
session = sessionManager.newInstance(channel);
|
||||
AttributeUtils.setSession(channel, session);
|
||||
}else {
|
||||
//防止设备掉线不同步
|
||||
boolean registered = session.isRegistered();
|
||||
if (registered){
|
||||
//已经注册查询session是否存在
|
||||
if (!sessionManager.containKey(session.getClientId())){
|
||||
sessionManager.add(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
private static Object client(ChannelHandlerContext ctx) {
|
||||
Channel channel = ctx.channel();
|
||||
Session session = AttributeUtils.getSession(channel);
|
||||
if (session != null) {
|
||||
return session.getClientId();
|
||||
}
|
||||
return channel.remoteAddress();
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
package com.fastbee.server.handler;
|
||||
|
||||
import com.fastbee.base.codec.Delimiter;
|
||||
import com.fastbee.base.session.Packet;
|
||||
import com.fastbee.base.session.Session;
|
||||
import com.fastbee.base.session.SessionManager;
|
||||
import com.fastbee.base.util.ByteBufUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* UDP消息适配器
|
||||
*
|
||||
* @author gsb
|
||||
* @date 2022/11/7 11:18
|
||||
*/
|
||||
@ChannelHandler.Sharable
|
||||
@Slf4j
|
||||
public class UDPMessageAdapter extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final SessionManager sessionManager;
|
||||
|
||||
private final long readerIdleTime;
|
||||
|
||||
public static UDPMessageAdapter newInstance(SessionManager sessionManager,
|
||||
int readerIdleTime, Delimiter[] delimiters) {
|
||||
return new DelimiterBaseFrame(sessionManager, readerIdleTime, delimiters);
|
||||
}
|
||||
|
||||
public UDPMessageAdapter(SessionManager sessionManager, int readerIdleTime) {
|
||||
this.sessionManager = sessionManager;
|
||||
this.readerIdleTime = TimeUnit.DAYS.toMillis(readerIdleTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
DatagramPacket packet = (DatagramPacket) msg;
|
||||
ByteBuf buf = packet.content();
|
||||
/*session处理*/
|
||||
Session session = getSession(ctx, packet.sender());
|
||||
/*更新最后上线时间*/
|
||||
session.access();
|
||||
/*将报文透传到下一个处理器*/
|
||||
ctx.fireChannelRead(Packet.of(session, buf));
|
||||
}
|
||||
|
||||
/*TODO tcp跟 udp的session 暂时分开处理*/
|
||||
private final Map<Object, Session> sessionMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 设备注册
|
||||
*/
|
||||
protected Session getSession(ChannelHandlerContext ctx, InetSocketAddress sender) {
|
||||
Session session = sessionMap.get(sender);
|
||||
if (session == null) {
|
||||
session = sessionManager.newInstance(ctx.channel(), sender, s -> sessionMap.remove(sender, s));
|
||||
sessionMap.put(sender, session);
|
||||
log.info("=>UDP设备连接{}", session);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
/*根据UDP设备设定的心跳频率判断是否在线*/
|
||||
Thread thread = new Thread(() -> {
|
||||
for (; ; ) {
|
||||
long nextDelay = readerIdleTime;
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
for (Session session : sessionMap.values()) {
|
||||
/*readerIdleTime 读心跳时间 根据设备的特性设置*/
|
||||
long time = readerIdleTime - (now - session.getLastAccessTime());
|
||||
|
||||
if (time <= 0) {
|
||||
log.warn("=>设备心跳超时 {}", session);
|
||||
session.invalidate();
|
||||
} else {
|
||||
nextDelay = Math.min(time, nextDelay);
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(nextDelay);
|
||||
} catch (Throwable e) {
|
||||
log.warn("IdleStateScheduler", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
thread.setName(Thread.currentThread().getName() + "-c");
|
||||
thread.setPriority(Thread.MIN_PRIORITY);
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分隔符报文处理器
|
||||
*/
|
||||
private static class DelimiterBaseFrame extends UDPMessageAdapter {
|
||||
|
||||
private final Delimiter[] delimiters;
|
||||
|
||||
public DelimiterBaseFrame(SessionManager sessionManager, int readerIdleTime, Delimiter[] delimiters) {
|
||||
super(sessionManager, readerIdleTime);
|
||||
this.delimiters = delimiters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
DatagramPacket packet = (DatagramPacket) msg;
|
||||
ByteBuf content = packet.content();
|
||||
Session session = getSession(ctx, packet.sender());
|
||||
|
||||
try {
|
||||
/*根据长度 分隔符过滤不符合报文*/
|
||||
List<ByteBuf> out = decode(content);
|
||||
for (ByteBuf byteBuf : out) {
|
||||
/*透传到下一个处理器*/
|
||||
ctx.fireChannelRead(Packet.of(session, byteBuf));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new Exception(e);
|
||||
} finally {
|
||||
/*最后释放缓存*/
|
||||
content.release();
|
||||
}
|
||||
}
|
||||
|
||||
protected List<ByteBuf> decode(ByteBuf in) {
|
||||
List<ByteBuf> out = new LinkedList<>();
|
||||
while (in.isReadable()) {
|
||||
|
||||
for (Delimiter delimiter : delimiters) {
|
||||
int length = delimiter.value.length;
|
||||
int frameLength = ByteBufUtils.indexOf(in, delimiter.value);
|
||||
if (frameLength >= 0) {
|
||||
if (delimiter.strip) {
|
||||
if (frameLength != 0) {
|
||||
out.add(in.readRetainedSlice(frameLength));
|
||||
}
|
||||
in.skipBytes(length);
|
||||
} else {
|
||||
if (frameLength != 0) {
|
||||
out.add(in.readRetainedSlice(frameLength + length));
|
||||
} else {
|
||||
in.skipBytes(length);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int i = in.readableBytes();
|
||||
if (i > 0) {
|
||||
out.add(in.readRetainedSlice(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
Reference in New Issue
Block a user