第一次提交

This commit is contained in:
wyw
2024-08-08 00:31:26 +08:00
commit c202e2b63d
1819 changed files with 221890 additions and 0 deletions

View 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>

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
});
}
}

View File

@ -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();
}
}
}

View File

@ -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");
}
}

View File

@ -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));
}
}
}

View File

@ -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");
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}