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

}

posted @ 2026-01-30 13:16  kevinWwm  阅读(11)  评论(0)    收藏  举报