iot物联网,用netty实现设备的解析

场景是婴儿培养箱设备有一个固定的ip,java服务区监听这个ip和端口,然后发送固定报文给设备,设备就会发送数据过来,netty server就可以监听该客户端后就可以接收到数据,然后把接收到的数据做解析。

1,添加netty maven依赖 

	<dependency>
			<groupId>io.netty</groupId>
			<artifactId>netty-all</artifactId>
		</dependency>
		<dependency>
			<groupId>com.corundumstudio.socketio</groupId>
			<artifactId>netty-socketio</artifactId>
			<version>1.7.7</version>
		</dependency>

2,编写netty 启动类

 


import cn.hutool.cron.CronUtil;
import com.david.analysis.netty.server.NettyServer;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.net.InetSocketAddress;

/**
 * 功能描述: 任务队列
 * @Author
 * @Date
 */
@Component
@Slf4j
public class LaunchRunner implements CommandLineRunner {

    @Resource
    private NettyServer nettyServer;

    @Resource
    private SocketProperties socketProperties;


    /**
     * netty 服务启动 方法
     * @param args
     * @throws Exception
     */
    @Override
    public void run(String... args) throws Exception {
        //不用的时候 注释掉
        TaskRunner();
        InetSocketAddress address = new InetSocketAddress(socketProperties.getHost(),socketProperties.getPort());
        log.info("netty服务器启动地址:"+socketProperties.getHost());
        nettyServer.start(address);
    }


    /**
     * 执行正在运行的任务
     */
    private  void TaskRunner() {
        /**
         * 任务队列启动
         */
        CronUtil.setMatchSecond(true);
        CronUtil.start();
        log.info("\n-----------------------任务服务启动------------------------\n\t" +
                        "当前正在启动的{}个任务"+
                        "\n-----------------------------------------------------------\n\t"
                , CronUtil.getScheduler().size()

        );
    }


}

3,服务端初始化 

import com.david.analysis.netty.server.messge.HexDecoder;
import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.timeout.IdleStateHandler;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 功能描述: 服务端初始化,客户端与服务器端连接一旦创建,这个类中方法就会被回调,设置出站编码器和入站解码器
 *
 * @Author
 * @Date
 */
public class NettyServerChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        //接收消息格式,使用自定义解析数据格式
        pipeline.addLast("decoder",new HexDecoder());
        //发送消息格式,使用自定义解析数据格式
//        pipeline.addLast("encoder",new HexEncoder());

        //针对客户端,如果在1分钟时没有想服务端发送写心跳(ALL),则主动断开
        //如果是读空闲或者写空闲,不处理,这里根据自己业务考虑使用
        //pipeline.addLast(new IdleStateHandler(600,0,0, TimeUnit.SECONDS));
        //自定义的空闲检测
//        pipeline.addLast(new ServerHandler());
        pipeline.addLast(new NettyServerHandler());


    }



}

4,数据转码工具类,因为设备发过来的数据是十六进制的,需要转成十进制 


import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

public class HexDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // 读取到十六进制字符串
        StringBuilder hexString = new StringBuilder();
        while (in.isReadable()) {
            hexString.append(String.format("%02X", in.readByte()));
        }

        // 将十六进制字符串转换为字节数组
        String hex = hexString.toString();
        byte[] bytes = new byte[hex.length() / 2];
        for (int i = 0; i < hex.length(); i += 2) {
            bytes[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
                                 + Character.digit(hex.charAt(i+1), 16));
        }

        // 将字节数组添加到输出列表
        ByteBuf decoded = ctx.alloc().buffer();
        decoded.writeBytes(bytes);
        out.add(decoded);
    }
}

5,服务端处理类 收到设备数据后的处理 

重点是

channelRead方法 netty在收到消息后会调用该方法,然后里面就可以做数据解析后其他业务操作,我在代码里还写了mqtt发送的逻辑,这块 会在其他文章里写,暂时不需要的可以去掉。
 


import com.alibaba.fastjson.JSONObject;
import com.david.analysis.common.DataAnalysisUtil;
import com.david.analysis.dto.BabyIncubatorYP970DTO;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import io.netty.buffer.ByteBuf;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;

import java.net.InetSocketAddress;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.eclipse.paho.client.mqttv3.IMqttClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;


/**
 * 功能描述: netty服务端处理类
 *
 * @Author  
 * @Date
 */
@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    private static final String MQTT_BROKER = "tcp://xxx:1883"; // MQTT服务器地址 改成自己的ip
    private static final String MQTT_TOPIC = "test/data"; //  MQTT主题

    private static IMqttClient mqttClient;
    private static final String USERNAME = "example_user"; // 替换为你的用户名
    private static final String PASSWORD = "123456"; // 替换为你的密码


    /**
     * 功能描述: 有客户端连接服务器会触发此函数
     * @Author  
     * @Date
     * @param  ctx 通道
     * @return void
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = insocket.getAddress().getHostAddress();
        int clientPort = insocket.getPort();
        //获取连接通道唯一标识
        ChannelId channelId = ctx.channel().id();
        //如果map中不包含此连接,就保存连接
        if (ChannelMap.getChannelMap().containsKey(channelId)) {
            log.info("客户端:{},是连接状态,连接通道数量:{} ",channelId,ChannelMap.getChannelMap().size());
        } else {
            //保存连接
            ChannelMap.addChannel(channelId, ctx.channel());
            log.info("客户端:{},连接netty服务器[IP:{}-->PORT:{}]",channelId, clientIp,clientPort);
            log.info("连接通道数量: {}",ChannelMap.getChannelMap().size());


        }

        try {
            initMQTTClient(); // 初始化MQTT客户端
        } catch (MqttException e) {
            throw new RuntimeException(e);
        }


        // 每隔五秒发送一次数据
        scheduler.scheduleAtFixedRate(() -> {
            //xxx 替换成自己的报文
            String message = "xxxx";
//            ByteBuf buffer = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8);


//            byte[] bytes = hexStringToByteArrayHaveSpace(message);
//            ByteBuf buffer = Unpooled.copiedBuffer(bytes);

            byte[] bytes = hexStringToByteArray(message);
            ByteBuf buffer = Unpooled.copiedBuffer(bytes);

//            ctx.writeAndFlush(buffer);
            System.out.println(LocalDateTime.now()+" 向客户端发送数据:"+message);
            //使用 ChannelFuture 来检查操作是否成功完成
            ChannelFuture future = ctx.writeAndFlush(buffer);
            future.addListener(f -> {
                if (f.isSuccess()) {
                    System.out.println("Message sent successfully");
                    System.out.println("发送的实际内容:"+message );
                } else {
                    f.cause().printStackTrace();
                }
            });
        }, 0, 5, TimeUnit.SECONDS);
    }

    private static byte[] hexStringToByteArray(String hexString) {
        hexString = hexString.replaceAll("\\s", "");
        if (hexString.length() % 2 != 0) {
            throw new IllegalArgumentException("Invalid hex string length.");
        }
        int length = hexString.length();
        byte[] data = new byte[length / 2];
        for (int i = 0; i < length; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
                    + Character.digit(hexString.charAt(i + 1), 16));
        }
        return data;
    }

    public static byte[] hexStringToByteArrayHaveSpace(String hexString) {
        hexString = hexString.replaceAll("\\s", "");
        if (hexString.length() % 2 != 0) {
            throw new IllegalArgumentException("Invalid hex string length.");
        }

        int length = hexString.length();
        byte[] data = new byte[length / 2];

        for (int i = 0; i < length; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
                    + Character.digit(hexString.charAt(i + 1), 16));
        }
        return data;
    }

    public static String byteBufToHexString(ByteBuf byteBuf) {
        StringBuilder hexString = new StringBuilder();
        while (byteBuf.isReadable()) {
            hexString.append(String.format("%02X ", byteBuf.readByte()));
        }
        return hexString.toString().trim();
    }

    /**
     * Hex字符串转byte
     * @param inHex 待转换的Hex字符串
     * @return  转换后的byte
     */
    public static byte hexToByte(String inHex){
        return (byte)Integer.parseInt(inHex,16);
    }
    /**
     * hex字符串转byte数组
     * @param inHex 待转换的Hex字符串
     * @return  转换后的byte数组结果
     */
    public static byte[] hexToByteArray(String inHex){
        int hexlen = inHex.length();
        byte[] result;
        if (hexlen % 2 == 1){
            //奇数
            hexlen++;
            result = new byte[(hexlen/2)];
            inHex="0"+inHex;
        }else {
            //偶数
            result = new byte[(hexlen/2)];
        }
        int j=0;
        for (int i = 0; i < hexlen; i+=2){
            result[j]=hexToByte(inHex.substring(i,i+2));
            j++;
        }
        return result;
    }

    /**
     * 功能描述: 有客户端终止连接服务器会触发此函数
     * @Author  
     * @Date
     * @param  ctx 通道处理程序上下文
     * @return void
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        InetSocketAddress inSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = inSocket.getAddress().getHostAddress();
        ChannelId channelId = ctx.channel().id();
        //包含此客户端才去删除
        if (ChannelMap.getChannelMap().containsKey(channelId)) {
            //删除连接
            ChannelMap.getChannelMap().remove(channelId);
            log.info("客户端:{},  断开 netty服务器[IP:{}-->PORT:{}]",channelId, clientIp,inSocket.getPort());
            log.info("连接通道数量: " + ChannelMap.getChannelMap().size());
        }
        // 客户端断开连接时,取消定时任务
        scheduler.shutdownNow();
    }

    private static void initMQTTClient() throws MqttException {
        mqttClient = new MqttClient(MQTT_BROKER, MqttClient.generateClientId());
        MqttConnectOptions options = new MqttConnectOptions();
        options.setCleanSession(true);
        options.setUserName(USERNAME); // 设置用户名
        options.setPassword(PASSWORD.toCharArray()); // 设置密码
        mqttClient.connect(options);
    }


    /**
     * 功能描述: 有客户端发消息会触发此函数
     * @Author  
     * @Date
     * @param  ctx 通道处理程序上下文
     * @param  msg 客户端发送的消息
     * @return void
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        byte[] receivedBytes = new byte[buf.readableBytes()];
        buf.readBytes(receivedBytes);
        String receivedHex = bytesToHex(receivedBytes);
        System.out.println(LocalDateTime.now()+" Received Hex: " + receivedHex);

        //开始解析 接收到的报文
        BabyIncubatorYP970DTO babyIncubatorYP970=DataAnalysisUtil.analysisData(receivedHex);
        String babyIncubatorYP970Str = JSONObject.toJSONString(babyIncubatorYP970);
        System.out.println("解析结果:"+babyIncubatorYP970Str);

        // 发送数据到MQTT服务器
        sendToMQTT(babyIncubatorYP970Str);


        buf.release();
        log.info("加载客户端报文,客户端id:{},客户端消息:{}",ctx.channel().id(), msg);
//        String data = String.valueOf(msg);
        Integer water = Integer.parseInt(data.substring(6,10),16);
//        //十六进制转 十进制
//        Integer water = Integer.parseInt(data,16);
//        log.info("当前数据:{} ",water);
        //响应客户端
        //this.channelWrite(ctx.channel().id(), msg);
    }

    private void sendToMQTT(String message) {
        try {
            MqttMessage mqttMessage = new MqttMessage(message.getBytes());
            mqttClient.publish(MQTT_TOPIC, mqttMessage);
            System.out.println("Published to MQTT topic: " + MQTT_TOPIC);
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }



    private String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            hexString.append(String.format("%02X", b)+" ");
        }
        return hexString.toString();
    }

   /* @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        String bytes = "01 03 00 02 00 01 25 CA";
        ctx.writeAndFlush(bytes);
    }*/

    /**
     * 功能描述: 服务端给客户端发送消息
     * @Author  
     * @Date
     * @param  channelId 连接通道唯一id
     * @param  msg 需要发送的消息内容
     * @return void
     */
    public void channelWrite(ChannelId channelId, Object msg) throws Exception {
        Channel channel = ChannelMap.getChannelMap().get(channelId);
        if (channel == null) {
            log.info("通道:{},不存在",channelId);
            return;
        }
        if (msg == null || msg == "") {
            log.info("服务端响应空的消息");
            return;
        }
        //将客户端的信息直接返回写入ctx
        channel.write(msg);
        //刷新缓存区
        channel.flush();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        String socketString = ctx.channel().remoteAddress().toString();
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.READER_IDLE) {
                log.info("Client:{},READER_IDLE 读超时",socketString);
                ctx.disconnect();
                Channel channel = ctx.channel();
                ChannelId id = channel.id();
                ChannelMap.removeChannelByName(id);
            } else if (event.state() == IdleState.WRITER_IDLE) {
                log.info("Client:{}, WRITER_IDLE 写超时",socketString);
                ctx.disconnect();
                Channel channel = ctx.channel();
                ChannelId id = channel.id();
                ChannelMap.removeChannelByName(id);
            } else if (event.state() == IdleState.ALL_IDLE) {
                log.info("Client:{},ALL_IDLE 总超时",socketString);
                ctx.disconnect();
                Channel channel = ctx.channel();
                ChannelId id = channel.id();
                ChannelMap.removeChannelByName(id);
            }
        }
    }

    /**
     * 功能描述: 发生异常会触发此函数
     * @Author  
     * @Date
     * @param  ctx 通道处理程序上下文
     * @param  cause 异常
     * @return void
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
        log.info("{}:发生了错误,此连接被关闭。此时连通数量:{}",ctx.channel().id(),ChannelMap.getChannelMap().size());
    }

}

6,配置读取类

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

/**
 * 功能描述: 配置类
 *
 * @Author
 * @Date
 */
@Setter
@Getter
@ToString
@Component
@Configuration
@PropertySource("classpath:application.yml")
@ConfigurationProperties(prefix = "socket")
public class SocketProperties {
    private Integer port;
    private String host;

}

yml文件配置内容:

socket:
  # 监听端口 8090
  port: 8081
  #ip地址
  host: xxxx 替换自己的ip

如果没有设备的话 可以参考下面的netty client代码,先自己模拟设备给服务端发数据

 

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class NettyClient {
    private final String host;
    private final int port;

    public NettyClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void start() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) throws Exception {
                     ch.pipeline().addLast(new YourClientHandler());
                 }
             });

            ChannelFuture f = b.connect(host, port).sync();
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        new NettyClient("模拟设备ip", 8081).start();
    }
}


import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;


public class YourClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        // 发送测试消息 这里的xxxx是设备的发送报文根据实际情况填写
       // ByteBuf message = Unpooled.copiedBuffer("xxxx", CharsetUtil.UTF_8);

        String msg="xxxxx";

        byte[] bytes = hexStringToByteArrayHaveSpace(msg);
        ByteBuf buffer = Unpooled.copiedBuffer(bytes);

        ChannelFuture future = ctx.writeAndFlush(buffer);
        future.addListener(f -> {
            if (f.isSuccess()) {
                System.out.println("Client message sent successfully.");
            } else {
                System.err.println("Failed to send client message: " + f.cause());
            }
        });
    }

    public static byte[] hexStringToByteArrayHaveSpace(String hexString) {
        hexString = hexString.replaceAll("\\s", "");
        if (hexString.length() % 2 != 0) {
            throw new IllegalArgumentException("Invalid hex string length.");
        }

        int length = hexString.length();
        byte[] data = new byte[length / 2];

        for (int i = 0; i < length; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
                    + Character.digit(hexString.charAt(i + 1), 16));
        }
        return data;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        System.out.println("Received message from server: " + msg.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

其他依赖的maven就不一一贴了 网上都有 如果实在有不太好确定的可以留言 我会贴出来

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值