netty收发
package pro.nbbt.xulian.business.rtk.proxy.netty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.stereotype.Component;
@Component
public class NtripNettyServerRunner {
private final ApplicationContext context;
public NtripNettyServerRunner(ApplicationContext context) {
this.context = context;
}
@EventListener(ApplicationReadyEvent.class)
public void startNetty() {
new Thread(() -> {
try {
context.getBean(NtripNettyServer.class).start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "Netty-NtripCaster").start();
}
}
package pro.nbbt.xulian.business.rtk.proxy.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class NtripNettyServer {
private static final Logger log = LoggerFactory.getLogger(NtripNettyServer.class);
private final int port = 2101;
private final ApplicationContext context;
public NtripNettyServer(ApplicationContext context) {
this.context = context;
}
public void start() throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(context.getBean(NtripServerInitializer.class))
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
log.info("NtripCaster Netty started on port {}", port);
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
log.info("NtripCaster Netty stopped");
}
}
}
package pro.nbbt.xulian.business.rtk.proxy.netty;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class NtripServerInitializer extends ChannelInitializer
private final ApplicationContext context;
public NtripServerInitializer(ApplicationContext context) {
this.context = context;
}
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
p.addLast(context.getBean(AuthHandler.class));
p.addLast(context.getBean(DataHandler.class));
}
}
package pro.nbbt.xulian.business.rtk.proxy.netty;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import pro.nbbt.xulian.admin.api.entity.SysTranslation;
import pro.nbbt.xulian.business.common.util.toolkit.ThreadPoolUtils;
import pro.nbbt.xulian.business.mower.api.entity.rtk.SelfStation;
import pro.nbbt.xulian.business.rtk.proxy.config.Rtcm1005Encoder;
import pro.nbbt.xulian.business.rtk.proxy.service.SelfStationService;
import pro.nbbt.xulian.business.rtk.proxy.utils.mqtt.RtkCommonMqttUtil;
import pro.nbbt.xulian.common.core.util.tool.ToolUtil;
import pro.nbbt.xulian.common.util.redis.RedisRtkUtils;
import pro.nbbt.xulian.common.util.redis.RtkRedisKeyCons;
import javax.annotation.Resource;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.ExecutorService;
@Component
@Scope("prototype")
public class DataHandler extends SimpleChannelInboundHandler
private static final Logger log = LoggerFactory.getLogger(DataHandler.class);
private long totalBytes = 0;
/*private final KafkaMessageSender kafkaMessageSender;*/
private static final String ORDER_CREATE_TOPIC = "self_station_data";
private static final AttributeKey<String> ATTR_SN = AttributeKey.valueOf("sn");
/* public DataHandler(KafkaMessageSender kafkaMessageSender) {
this.kafkaMessageSender = kafkaMessageSender;
}*/
private static final ExecutorService executorService = ThreadPoolUtils.newCpuCachedThreadPool();
@Resource
private SelfStationService selfStationService;
@Resource
RedisRtkUtils redisRtkUtils;
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
int len = msg.readableBytes();
byte[] data = new byte[len];
msg.readBytes(data);
String sn = ctx.channel().attr(AuthHandler.ATTR_SN).get();
if (sn == null) {
log.warn("SN is null, closing channel");
ctx.close();
return;
}
// 打印相关日志,后续去除=====================
parseAndLogRtcmMessages(data,sn);
String hexData = toHexString(data);
log.info("Received hexData from sn={} to {}", sn, hexData);
// =======================================
if (len > 4 * 1024) {
log.info("超过4kb数据写入文件");
String fileName = "error_" + sn + ".rtcm3";
writeRtcmFile(fileName, data);
return;
}
SelfStation station = selfStationService.getBySn(sn);
if (station == null) {
log.warn("SN not exist, closing channel");
ctx.close();
return;
}
selfStationOnLine(station);
boolean changeData = checkData(data);
if(changeData){
log.info("设备:{}拦截到1005或者1006",sn);
//调用脚本修改data数据
selfStationService.cacheSelfStation(station.getSn(),station.getLat(),station.getLng());
int stationId = Integer.valueOf(station.getBaseStationId());
if(ToolUtil.isNotEmpty(station.getPosX()) && ToolUtil.isNotEmpty(station.getPosY()) && ToolUtil.isNotEmpty(station.getPosZ())) {
double[] ecef = {
station.getPosX(),
station.getPosY(),
station.getPosZ()
};
log.info("Station ID: {} 对应的ecef:{}", stationId, ecef);
log.info(String.format(
"ECEF Position (m): X=%.4f, Y=%.4f, Z=%.4f",
ecef[0], ecef[1], ecef[2]
));
data = Rtcm1005Encoder.encode1005(stationId, ecef);
log.info("Byte Length: " + data.length);
System.out.print("HEX: ");
for (byte b : data) {
System.out.printf("%02X ", b);
}
}
}
pushToRtks(sn,data);
}
private void selfStationOnLine(SelfStation station){
ClientSessionManager.refresh(station.getSn());
if(!station.getOnlineFlag()) {
selfStationService.update(Wrappers.<SelfStation>lambdaUpdate()
.set(SelfStation::getOnlineFlag, Boolean.TRUE)
.set(SelfStation::getStatus, 1)
.eq(SelfStation::getSn, station.getSn()));
}
}
private String toHexString(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return "";
}
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
return sb.toString().trim();
}
public void pushToRtks(String baseStationSn, byte[] data) {
Set<String> devices = redisRtkUtils.sGet(RtkRedisKeyCons.REDIS_RTK_STATION_RELATEION + baseStationSn);
if (devices != null && !devices.isEmpty()) {
for (String deviceSn : devices) {
executorService.submit(() -> {
try {
RtkCommonMqttUtil.sendRtk(data, deviceSn);
System.out.println("Sent RTCM data to device SN=" + deviceSn);
} catch (Exception e) {
System.err.println("Failed to send RTCM data to device SN=" + deviceSn);
e.printStackTrace();
}
});
}
}
RtkCommonMqttUtil.sendRtk(data, baseStationSn);
System.out.println("No devices found for base station SN=" + baseStationSn);
}
/**
* 写入 RTCM 文件(二进制方式)
*/
private void writeRtcmFile(String fileName, byte[] data) {
try (FileOutputStream fos = new FileOutputStream(fileName, true)) {
fos.write(data);
fos.flush();
} catch (IOException e) {
log.error("Failed to write RTCM file: {}", fileName, e);
}
}
private boolean checkData(byte[] fileData) {
final byte HEADER = (byte) 0xD3;
int index = 0;
while (index < fileData.length - 6) {
if ((fileData[index] & 0xFF) != (HEADER & 0xFF)) {
index++;
continue;
}
int len = ((fileData[index + 1] & 0xFF) << 8 | (fileData[index + 2] & 0xFF)) & 0x3FF;
if (len < 2 || index + 3 + len + 3 > fileData.length) {
index++;
continue;
}
int msgType = ((fileData[index + 3] & 0xFF) << 4) | ((fileData[index + 4] & 0xF0) >> 4);
log.info("检查数据打印的msgType:{}",msgType);
if (msgType == 1005 || msgType == 1006) {
return true;
}
index += 3 + len + 3;
}
return false;
}
private void parseAndLogRtcmMessages(byte[] fileData,String sn) {
int index = 0;
while (index < fileData.length - 6) {
if ((fileData[index] & 0xFF) != 0xD3) {
index++;
continue;
}
int len = ((fileData[index + 1] & 0xFF) << 8 | (fileData[index + 2] & 0xFF)) & 0x3FF;
if (index + 3 + len + 3 > fileData.length) break; // 数据不完整
byte[] payload = new byte[len];
System.arraycopy(fileData, index + 3, payload, 0, len);
int msgType = ((payload[0] & 0xFF) << 4) | ((payload[1] & 0xF0) >> 4);
log.info("自建站sn:{} msgType内容是:{}",sn,msgType);
if (msgType == 1005 || msgType == 1006) {
log.info("-------------------------------------------------");
log.info("Found message type: {}", msgType);
int referenceStationId = (int) extractBits(payload, 12, 12);
int reserved1 = (int) extractBits(payload, 24, 6);
int gpsFlag = (int) extractBits(payload, 30, 1);
int gloFlag = (int) extractBits(payload, 31, 1);
int galFlag = (int) extractBits(payload, 32, 1);
int reserved2 = (int) extractBits(payload, 33, 1);
int bitPos = 34;
long x = extractSignedBits(payload, bitPos, 38);
bitPos += 38;
int singleOsc = (int) extractBits(payload, bitPos, 1);
bitPos += 1;
int reserved3 = (int) extractBits(payload, bitPos, 1);
bitPos += 1;
long y = extractSignedBits(payload, bitPos, 38);
bitPos += 38;
int quarterFlag = (int) extractBits(payload, bitPos, 2);
bitPos += 2;
long z = extractSignedBits(payload, bitPos, 38);
double scale = 0.0001;
double ecefX = x * scale;
double ecefY = y * scale;
double ecefZ = z * scale;
double[] latLonHeight = ecefToLatLonHeight(ecefX, ecefY, ecefZ);
// 打印全部字段
log.info("自建站sn:{} ReferenceStationID: {}", sn,referenceStationId);
log.info("Reserved1: {}", reserved1);
log.info("GPS Flag: {}", gpsFlag);
log.info("GLO Flag: {}", gloFlag);
log.info("GAL Flag: {}", galFlag);
log.info("Reserved2: {}", reserved2);
log.info(String.format("ECEF X: %.4f, Y: %.4f, Z: %.4f", ecefX, ecefY, ecefZ));
log.info("Single Receiver Oscillator Flag: {}", singleOsc);
log.info("Reserved3: {}", reserved3);
log.info("1/4 Week Flag: {}", quarterFlag);
log.info(String.format("Latitude: %.10f, Longitude: %.10f, Height: %.4f",
latLonHeight[0], latLonHeight[1], latLonHeight[2]));
}
index += 3 + len + 3;
}
}
private void buildUpdateEntity(SelfStation selfStation,
Integer referenceStationId,
double[] latLonHeight) {
SelfStation updateEntity = new SelfStation();
boolean hasUpdate = false;
String newStationId = String.valueOf(referenceStationId);
if (ToolUtil.isEmpty(selfStation.getBaseStationId())
|| !newStationId.equals(selfStation.getBaseStationId())) {
updateEntity.setBaseStationId(newStationId);
hasUpdate = true;
}
if (ToolUtil.isEmpty(selfStation.getLat())
|| Math.abs(selfStation.getLat() - latLonHeight[0]) > 1e-6) {
updateEntity.setLat(latLonHeight[0]);
hasUpdate = true;
}
if (ToolUtil.isEmpty(selfStation.getLng())
|| Math.abs(selfStation.getLng() - latLonHeight[1]) > 1e-6) {
updateEntity.setLng(latLonHeight[1]);
hasUpdate = true;
}
updateEntity.setId(selfStation.getId());
log.info("hasUpdate:{} 更新的实体:{}",hasUpdate,updateEntity);
if (hasUpdate) {
selfStationService.getBaseMapper().updateById(updateEntity);
}
selfStationService.cacheSelfStation(selfStation.getSn(),latLonHeight[1],latLonHeight[0]);
}
/**
* 提取 unsigned bits
*/
private long extractBits(byte[] data, int bitPos, int length) {
long val = 0;
for (int i = 0; i < length; i++) {
int byteIndex = (bitPos + i) / 8;
int bitIndex = 7 - ((bitPos + i) % 8);
int bit = (data[byteIndex] >> bitIndex) & 1;
val = (val << 1) | bit;
}
return val;
}
/**
* 提取 signed bits
*/
private long extractSignedBits(byte[] data, int bitPos, int length) {
long val = extractBits(data, bitPos, length);
if ((val & (1L << (length - 1))) != 0) {
val -= 1L << length;
}
return val;
}
/**
* ECEF -> WGS84 纬度/经度/高程
*/
private double[] ecefToLatLonHeight(double x, double y, double z) {
double a = 6378137.0;
double f = 1 / 298.257223563;
double e2 = 2 * f - f * f;
double lon = Math.atan2(y, x);
double p = Math.sqrt(x * x + y * y);
double lat = Math.atan2(z, p * (1 - e2));
double latPrev;
do {
latPrev = lat;
double N = a / Math.sqrt(1 - e2 * Math.sin(lat) * Math.sin(lat));
lat = Math.atan2(z + e2 * N * Math.sin(lat), p);
} while (Math.abs(lat - latPrev) > 1e-12);
double N = a / Math.sqrt(1 - e2 * Math.sin(lat) * Math.sin(lat));
double h = p / Math.cos(lat) - N;
return new double[]{Math.toDegrees(lat), Math.toDegrees(lon), h};
}
// 异步发送到 Kafka
/*kafkaMessageSender.sendAsync(
ORDER_CREATE_TOPIC,
sn,
data,
new ListenableFutureCallback<SendResult<String, Object>>() {
@Override
public void onSuccess(SendResult<String, Object> result) {
log.info("发送Kafka消息成功, sn={}", sn);
}
@Override
public void onFailure(Throwable ex) {
log.error("发送Kafka失败, sn={}", sn, ex);
}
}
);*/
}
package pro.nbbt.xulian.business.rtk.proxy.netty;
import cn.hutool.extra.spring.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import pro.nbbt.xulian.common.util.redis.RedisRtkUtils;
import pro.nbbt.xulian.common.util.redis.RtkRedisKeyCons;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
@Slf4j
public class ClientSessionManager {
public static boolean register(String sn,String channelId) {
log.info("自建站开始注册:{} 对应通道:{}",sn,channelId);
RedisRtkUtils redisRtkUtils = SpringUtil.getBean(RedisRtkUtils.class);
boolean flag = redisRtkUtils.setNx(RtkRedisKeyCons.REDIS_KEY_PREFIX + sn,channelId, 20);
return flag;
}
public static void remove(String sn, String channelId) {
log.info("自建站离线:{} 对应通道:{}",sn,channelId);
RedisRtkUtils redisRtkUtils = SpringUtil.getBean(RedisRtkUtils.class);
String key = RtkRedisKeyCons.REDIS_KEY_PREFIX + sn;
String lua = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else return 0 end";
redisRtkUtils.eval(lua, Collections.singletonList(key), Collections.singletonList(channelId));
}
public static boolean isOnline(String sn) {
RedisRtkUtils redisRtkUtils = SpringUtil.getBean(RedisRtkUtils.class);
return redisRtkUtils.hasKey(RtkRedisKeyCons.REDIS_KEY_PREFIX + sn);
}
public static String getNodeId(String sn) {
RedisRtkUtils redisRtkUtils = SpringUtil.getBean(RedisRtkUtils.class);
return (String) redisRtkUtils.get(RtkRedisKeyCons.REDIS_KEY_PREFIX + sn);
}
public static void refresh(String sn) {
RedisRtkUtils redisRtkUtils = SpringUtil.getBean(RedisRtkUtils.class);
if(redisRtkUtils.hasKey(RtkRedisKeyCons.REDIS_KEY_PREFIX + sn)) {
boolean expire = redisRtkUtils.expire(RtkRedisKeyCons.REDIS_KEY_PREFIX + sn, 20, TimeUnit.SECONDS);
log.info("自建站sn:{}缓存续期:{}", sn,expire);
}else{
log.error("自建站sn:{}缓存续期失败");
}
}
}
package pro.nbbt.xulian.business.rtk.proxy.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.util.AttributeKey;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import pro.nbbt.xulian.business.rtk.proxy.service.SelfStationService;
import javax.annotation.Resource;
import java.util.Base64;
@Component
@Scope("prototype")
public class AuthHandler extends ChannelInboundHandlerAdapter {
private static final Logger log = LoggerFactory.getLogger(AuthHandler.class);
@Resource
private SelfStationService selfStationService;
// 用于保存 SN 的 Channel 属性
public static final AttributeKey<String> ATTR_SN = AttributeKey.valueOf("sn");
public static final AttributeKey<String> ATTR_CHANNEL_ID = AttributeKey.valueOf("channelId");
private boolean authenticated = false;
private String sn;
private String channelId;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
channelId = ctx.channel().id().asLongText();
if (authenticated) {
ctx.fireChannelRead(msg);
return;
}
ByteBuf buf = (ByteBuf) msg;
String request = buf.toString(CharsetUtil.US_ASCII);
buf.release();
log.info("Received handshake request: {}", request);
if (!request.startsWith("POST")) {
sendResponseAndClose(ctx, "HTTP/1.1 400 Bad Request\r\n\r\n");
return;
}
String authLine = null;
for (String line : request.split("\r\n")) {
if (line.startsWith("Authorization: Basic")) {
authLine = line;
break;
}
}
if (authLine == null) {
sendResponseAndClose(ctx, "HTTP/1.1 401 Unauthorized\r\n\r\n");
return;
}
try {
String base64 = authLine.split(" ")[2];
String decoded = new String(Base64.getDecoder().decode(base64), CharsetUtil.UTF_8);
String[] parts = decoded.split(":");
if (parts.length != 2) {
sendResponseAndClose(ctx, "HTTP/1.1 401 Unauthorized\r\n\r\n");
return;
}
sn = parts[0];
String password = parts[1];
if (!selfStationService.validate(sn, password)) {
log.warn("Auth failed for sn={} from {}", sn, ctx.channel().remoteAddress());
sendResponseAndClose(ctx, "HTTP/1.1 401 Unauthorized\r\n\r\n");
return;
}
if (!ClientSessionManager.register(sn,channelId)) {
log.warn("Duplicate login attempt for sn={} from {}", sn, ctx.channel().remoteAddress());
sendResponseAndClose(ctx, "HTTP/1.1 403 Forbidden\r\n\r\n");
return;
}
ctx.channel().attr(ATTR_SN).set(sn);
authenticated = true;
ctx.writeAndFlush(ctx.alloc().buffer().writeBytes("HTTP/1.1 200 OK\r\n\r\n".getBytes()));
log.info("Auth success for sn={} from {}", sn, ctx.channel().remoteAddress());
} catch (Exception e) {
log.error("AuthHandler exception", e);
sendResponseAndClose(ctx, "HTTP/1.1 500 Internal Server Error\r\n\r\n");
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
if (sn != null) {
/*ClientSessionManager.remove(sn,channelId);
selfStationService.removeCacheSelfStation(sn);*/
log.info("SN {} disconnected, session removed", sn);
}
}
/**
* 发送响应并关闭连接
*/
private void sendResponseAndClose(ChannelHandlerContext ctx, String response) {
ctx.writeAndFlush(ctx.alloc().buffer().writeBytes(response.getBytes()))
.addListener(ChannelFutureListener.CLOSE);
}
}
浙公网安备 33010602011771号