提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
提示:以下是本篇文章正文内容,下面案例可供参考
一、实现netty采集jtt808协议
二、使用步骤
1.netty接入数据
代码如下(示例):
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.30.Final</version>
</dependency>
实现netty接入jtt808协议:
import com.qycloud.iot.microserviceindoorpositioning.gps.config.GpsConfiguration;
import com.qycloud.iot.microserviceindoorpositioning.netty.utils.GpsDecoder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@Profile("!test1") //单元测试类添加 @ActiveProfiles("test1")
public class NettyServer implements ApplicationRunner {
@Autowired
GpsConfiguration gpsConfiguration;
@Autowired
NettyServerHandler nettyServerHandler;
@Override
public void run(ApplicationArguments args)
{
// 创建对应的 线程池
// 创建Boss group
EventLoopGroup boosGroup = new NioEventLoopGroup(1);
// 创建 workgroup
EventLoopGroup workGroup = new NioEventLoopGroup();
// 创建对应的启动类
ServerBootstrap bootstrap = new ServerBootstrap();
try{
// 设置相关的配置信息
bootstrap.group(boosGroup,workGroup) // 设置对应的线程组
.channel(NioServerSocketChannel.class) // 设置对应的通道
.option(ChannelOption.SO_BACKLOG,1024) // 设置线程的连接个数
.childHandler(new ChannelInitializer<SocketChannel>() { // 设置
/**
* 给pipeline 设置处理器
*/
@Override
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline().addLast(new GpsDecoder()); //自定义的解码
socketChannel.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
socketChannel.pipeline().addLast(nettyServerHandler);
}
});
log.info("netty服务启动了....");
// 绑定端口 启动服务
ChannelFuture channelFuture = bootstrap.bind(gpsConfiguration.getPort()).sync();
// 对关闭通道进行监听
channelFuture.channel().closeFuture().sync();
}catch (Exception e){
log.error("netty启动错误....",e);
}finally {
// 优雅停服
boosGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
import com.qycloud.iot.microserviceindoorpositioning.gps.GpsProtocolDataHandler;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@ChannelHandler.Sharable
@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Autowired
GpsProtocolDataHandler gpsProtocolDataHandler;
/**
* 读取客户端发送来的数据
*
* @param ctx ChannelHandler的上下文对象 有管道 pipeline 通道 channel 和 请求地址 等信息
* @param msg 客户端发送的具体数据
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
log.info("开始解析设备,拿到的数据为-------{}" ,msg);
if (null == msg || msg.toString().isEmpty()) return;
String body = (String) msg;
gpsProtocolDataHandler.handleProtocol(ctx, body);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws InterruptedException {
// SocketAddress address = ctx.channel().remoteAddress();
// System.out.println("客户端请求到了..." + address);
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
// SocketAddress address = ctx.channel().remoteAddress();
// System.out.println("客户端请求断开..." + address);
}
/**
* 读取客户端发送数据完成后的方法
* 在本方法中可以发送返回的数据
*
* @param ctx
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
// ctx.writeAndFlush(hexStrToBytes("ab13"));
// writeAndFlush 是组合方法
// ctx.channel().writeAndFlush(hexStrToBytes("ab13"));
// ctx.writeAndFlush(Unpooled.copiedBuffer(rMsg,CharsetUtil.UTF_8));
}
}
netty解码,回写工具
import com.qycloud.iot.microserviceindoorpositioning.gps.utils.ByteUtils;
import com.qycloud.iot.microserviceindoorpositioning.gps.utils.GpsAnalyseUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Locale;
/**
* @Author: WangHao
* @Date: 2021/10/19/16:47
* @Description:
*/
@Slf4j
public class GpsDecoder extends ByteToMessageDecoder {
StringBuffer temp = new StringBuffer();
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
String HEXES = "0123456789ABCDEF";
byte[] req = new byte[msg.readableBytes()];
msg.readBytes(req);
final StringBuilder hex = new StringBuilder(2 * req.length);
for (int i = 0; i < req.length; i++) {
byte b = req[i];
hex.append(HEXES.charAt((b & 0xF0) >> 4))
.append(HEXES.charAt((b & 0x0F))).append(" ");
}
// String content = decodeOri(hex.toString());
String content = hex.toString().replace(" ", "");
log.info("原始报文-------{}", content);
StringBuffer realMsg = null;
if (temp.length() > 0) {
realMsg = new StringBuffer(temp);
realMsg.append(content);
log.info("合并:上一数据包余下的长度为:" + temp.length() + ",合并后长度为:" + realMsg.length());
} else {
realMsg = new StringBuffer(content);
}
String realMsgStr = realMsg.toString();
//如果是 7E开头
if (realMsgStr.startsWith("7E")) {
if (realMsgStr.length()>50000){
//找不到全部舍去
temp = new StringBuffer();
return;
}
dealStartWith7E(realMsgStr, out);
} else {
//查找第一个7E开头的
int index7E = realMsgStr.indexOf("7E");
if (index7E < 0) {
//找不到全部舍去
temp = new StringBuffer();
return;
}
realMsgStr = realMsgStr.substring(index7E);
dealStartWith7E(realMsgStr, out);
}
}
private void dealStartWith7E(String realMsgStr, List<Object> out) {
try {
int index = realMsgStr.indexOf("7E", realMsgStr.indexOf("7E") + 1);
if (index == -1) {
temp = new StringBuffer(realMsgStr);
// out.add("");
return;
}
String data = realMsgStr.substring(0, index + 2);
//data 必然为 7E开头,7E结尾
boolean check = check(data);
if (check) {
out.add(decodeOri(data));
String substring = realMsgStr.substring(data.length());
//判断是否需要拆包
if (substring.length() == 0) {
temp = new StringBuffer();
return;
} else {
dealStartWith7E(substring, out);
return;
}
}
String substring = realMsgStr.substring(index);
dealStartWith7E(substring, out);
}catch (Exception e){
log.error("解析数据格式错误,数据为{},错误原因:{}",realMsgStr,e);
}
}
//完整数据结构校验
private boolean check(String data) {
String decodeData = decodeOri(data);
if (decodeData == null || decodeData.length() < 30 || !decodeData.toLowerCase(Locale.CHINA).startsWith("7e") || !decodeData.toLowerCase(Locale.CHINA).endsWith("7e")) {
log.error("校验数据结构:数据{},错误原因:{}", decodeData, "数据不是以7E开头或者结尾,或者长度不满足最基础的30位");
return false;
}
if (decodeData.length() < 26) {
log.error("校验数据结构:数据{},错误原因:{}", decodeData, "数据头不正确,不满足26位");
return false;
}
String bodySizeStr = decodeData.substring(6, 10);
int bodySize = ByteUtils.hexStringToDec(bodySizeStr) * 2; //一个字节两位
int contentSize = bodySize + 30;
if (decodeData.length() < contentSize) {
log.error("校验数据结构:数据{},错误原因:{}", decodeData, "消息体长度不正确");
return false;
}
//验证校验位
String headAndBody = decodeData.substring(2, 26 + bodySize);
String check = decodeData.substring(bodySize + 26, bodySize + 26 + 2);
String checkCode = GpsAnalyseUtils.createCheckCode(headAndBody);
if (!check.equalsIgnoreCase(checkCode)) {
log.error("校验数据结构:数据{},错误原因:{}", decodeData, "校验位不匹配");
return false;
}
return true;
}
private static String decodeOri(String oriStr) {
return oriStr.replaceAll("(?i)7D 02", "7E")
.replaceAll("(?i)7D 01", "7D").replace(" ", "");
}
public static void main(String[] args) {
// String realMsgStr = "7E07040342017028567195004B000A010055000000000000000001F0F61E06FD3D0F000000000000220521151419010400000000300114310100542405D46BA64B22BC32ECDA599807C12A54E061460D912890F7B225CEDB2690F7B226E83922E1014EEA0200010055000000000000000001F0F61E06FD3D0F000000000000220521151429010400000000300114310100542405D46BA64B22BC32ECDA599807C12A54E061460D912890F7B225CEDB2690F7B226E83922E1014EEA0200010055000000000000000001F0F61E06FD3D0F000000000000220521151439010400000000300114310100542405D46BA64B22BC32ECDA599807C12A54E061460D912890F7B225CEDB2690F7B226E83922E1014EEA0200010048000000000000000001F0F61E06FD3D0F0000000000002205211525200104000000003001173101009F173436302C30302C353533342C30383238323561302C3233E1014EEA0200010048000000000000000001F0F61E06FD3D0F0000000000002205211525300104000000003001173101009F173436302C30302C353533342C30383238323561302C3233E1014EEA0200010048000000000000000001F0F61E06FD3D0F0000000000002205211525400104000000003001173101009F173436302C30302C353533342C30383238323561302C3233E1014EEA0200010055000000000000000001F0F67E07040335017028567195004C000A010055000000000000000001F0F61E06FD3D0F000000000000220521152632010400000000300115310100542405ECDA599807C13454E061460D912A90F7B225CEDB2390F7B226E83923ECF8EB205F0923E1014EEA0200010055000000000000000001F0F61E06FD3D0F000000000000220521152642010400000000300115310100542405ECDA599807C13454E061460D912A90F7B225CEDB2390F7B226E83923ECF8EB205F0923E1014EEA0200010048000000000000000001F0F61E06FD3D0F0000000000002205211526520104000000003001153101009F173436302C30302C353533342C30383238323561302C3231E1014EEA0200010048000000000000000001F0F61E06FD3D0F0000000000002205211527020104000000003001153101009F173436302C30302C353533342C30383238323561302C3231E1014EEA0200010048000000000000000001F0F61E06FD3D0F0000000000002205211528220104000000003001133101009F173436302C30302C353533342C30383238323561302C3139E1014EEA0200010055000000000000000001F0F61E06FD3D0F000000000000220521152832010400000000300116310100542405ECDA599807C12A54E061460D9128ECF8EB205F092590F7B225CEDB2390F7B226E83921E1014EEA0200010055000000000000000001F0F61E06FD3D0F000000000000220521152953010400000000300113310100542405ECDA599807C13154E061460D912890F7B226E8392190F7B225CEDB20ECF8EB205F0920E1014EEA0200010055000000000000000001F0F61E06FD3D0F000000000000220521153003010400000000300113310100542405ECDA599807C13154E061460D912890F7B226E8392190F7B225CEDB20ECF8EB205F0920E1014EEA0200010055000000000000000001F0F61E06FD3D0F000000000000220521153014010400000000300113310100542405ECDA599807C13154E061460D912890F7B226E8392190F7B225CEDB20ECF8EB205F0920E1014EEA0200010048000000000000000001F0F61E06FD3D0F0000000000002205211530440104000000003001133101009F173436302C30302C353533342C30383238323561302C3139E1014EEA020001A97E";
String realMsgStr = "7Esdfsdf7E";
String data = "7Esdfsdf7E";
String substring = realMsgStr.substring(data.length());
System.out.println("substring:"+substring);
String[] split = realMsgStr.split("7E07040E7E", 1);
System.out.println(split);
// int i = realMsgStr.indexOf("7E", realMsgStr.indexOf("7E") + 1);
//
// System.out.println(i);
// String substring = realMsgStr.substring(realMsgStr.substring(2).indexOf("7E") + 2);
// System.out.println(substring);
}
}
import com.qycloud.iot.microserviceindoorpositioning.gps.utils.ByteUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
/**
* @Author: WangHao
* @Date: 2021/10/19/16:49
* @Description:
*/
@Slf4j
public class MyTools {
private static volatile MyTools myTools;
private MyTools() {
}
public static MyTools getInstance() {
if (null == myTools) {
synchronized (MyTools.class) {
if (null == myTools) {
myTools = new MyTools();
}
}
}
return myTools;
}
public void writeToClient(final String receiveStr, ChannelHandlerContext channel, final String mark) {
try {
ByteBuf bufff = Unpooled.buffer();//netty需要用ByteBuf传输
bufff.writeBytes(ByteUtils.hexString2Bytes(receiveStr));//对接需要16进制
channel.writeAndFlush(bufff).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
StringBuilder sb = new StringBuilder();
if (null != mark && !mark.isEmpty()) {
sb.append("【").append(mark).append("】");
}
if (future.isSuccess()) {
log.debug(sb.toString() + "回写成功" + receiveStr);
} else {
log.warn(sb.toString() + "回写失败" + receiveStr);
}
}
});
} catch (Exception e) {
e.printStackTrace();
log.warn("调用通用writeToClient()异常" + e.getMessage());
}
}
}
2.jtt808协议解析
采用策略模式解析
自定义注解
import com.qycloud.iot.microserviceindoorpositioning.gps.common.SignCode;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author: WangHao
* @Date: 2021/10/21/18:29
* @Description:
*/
@Target( ElementType.TYPE) //注解定义到类上
@Retention( RetentionPolicy.RUNTIME) //生命周期
public @interface Sign {
// String value();
// String message() ;
SignCode value();
}
import com.qycloud.iot.microserviceindoorpositioning.gps.config.GpsConfiguration;
import com.qycloud.iot.microserviceindoorpositioning.gps.domain.GpsProtocol;
import com.qycloud.iot.microserviceindoorpositioning.gps.handle.ISign;
import com.qycloud.iot.microserviceindoorpositioning.gps.utils.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashMap;
import java.util.Set;
/**
* @Author: WangHao
* @Date: 2021/10/21/18:40
* @Description:
*/
@Slf4j
public class SignFactory {
@Autowired
GpsConfiguration gpsConfiguration;
private static HashMap<String,String> sourceMap = new HashMap<>();
/**
* 单例
*
*/
static {
//反射工具包,指明扫描路径
Reflections reflections = new Reflections("com.qycloud.iot.microserviceindoorpositioning");
//获取带我们pay注解的类
Set<Class<?>> classSet = reflections.getTypesAnnotatedWith(Sign.class);
//根据注解的值,将全类名放到map中
for (Class clazz : classSet){
Sign pay = (Sign) clazz.getAnnotation(Sign.class);
sourceMap.put(pay.value().getCode(),clazz.getCanonicalName());
}
}
public static ISign getInstance(GpsProtocol gpsProtocol){
ISign chatType = null;
try {
//取得全类名
String className = sourceMap.get(gpsProtocol.getSignCode());
//取得类对象
Class clazz= Class.forName(className);
//======================创建对象=====================
chatType = (ISign) SpringUtils.getInterface(clazz);
}catch (Exception e){
log.error("尚未定义处理类标志:"+gpsProtocol.getSignCode()+"原始数据为:"+gpsProtocol.getRawData(),e);
}
return chatType;
}
}
/**
* @Author: WangHao
* @Date: 2021/10/22/13:51
* @Description:
*/
public enum SignCode {
Sign_0002("0002", "终端心跳"),
Sign_0100("0100", "终端注册"),
Sign_0102("0102", "终端鉴权"),
Sign_0104("0104", "查询终端参数应答"),
Sign_0200("0200", "位置信息汇报"),
Sign_0704("0704", "定位数据批量上传"),
Sign_8001("8001", "平台通用应答"),
Sign_8100("8100", "终端注册应答"),
Sign_8104("8104", "查询终端参数"),
;
SignCode(String code, String desc) {
this.code = code;
this.desc = desc;
}
private String code;
private String desc;
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
开始解析
import com.qycloud.iot.microserviceindoorpositioning.gps.domain.GpsProtocol;
import com.qycloud.iot.microserviceindoorpositioning.gps.handle.ISign;
import com.qycloud.iot.microserviceindoorpositioning.gps.sign.SignFactory;
import com.qycloud.iot.microserviceindoorpositioning.gps.utils.GpsAnalyseUtils;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @Author: WangHao
* @Date: 2021/10/21/10:05
* @Description: Gps协议数据解析类
*/
@Component
@Slf4j
public class GpsProtocolDataHandler {
/**
* Gps
*
* @param ctx
* @param oriMsg
*/
public void handleProtocol(ChannelHandlerContext ctx, String oriMsg) {
try {
GpsProtocol gpsProtocol = GpsAnalyseUtils.handleGpsProtocol(oriMsg);
if (null == gpsProtocol) return;
ISign instance = SignFactory.getInstance(gpsProtocol);
if (null != instance) {
instance.handle(ctx, gpsProtocol);
}
} catch (Exception e) {
log.error("原始数据:"+oriMsg);
log.error("GPS错误信息:",e);
e.printStackTrace();
}
}
}
aa
aa
aa