通过Netty实现主动向硬件设备发送控制命令

准备工作

如果没有现成的硬件设备可以通过下载网络调试助手进行模拟。

  1. 填写好远程主机地址,以及netty服务端口。
    在这里插入图片描述

编写netty服务端

@Component
public class NettyServer {

    /**
     * 服务端口
     */
    @Value("${netty.port}")
    private int port;

    public static NioServerSocketChannel nioServerSocketChannel;
    /**
     * 保存连接上来的客户端,每个客户端会对应唯一ChannelId
     */
    public static final Map<ChannelId, ChannelPipeline> CHANNEL_MAP = new ConcurrentHashMap<>();

    @Autowired
    private ServerHandler serverHandler;

    public void start() {
        //连接处理group
        NioEventLoopGroup boss = new NioEventLoopGroup();
        //事件处理group
        NioEventLoopGroup worker = new NioEventLoopGroup();
        //创建serverBootStrap实例
        ServerBootstrap bootstrap = new ServerBootstrap();
        //1.绑定group
        bootstrap.group(boss, worker)
                //2.设置并绑定Reactor线程池:EventLoopGroup,EventLoop就是处理所有注册到本线程的Selector上面的Channel
                .channel(NioServerSocketChannel.class)
                //3.保存连接数
                .option(ChannelOption.SO_BACKLOG, 1024)
                //4.有数据立即发送
                .option(ChannelOption.SO_SNDBUF, 4 * 1024)
                //5.保持连接
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                //6.处理新连接
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    //设置了客户端连接socket属性。
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        //增加任务处理
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        CHANNEL_MAP.put(socketChannel.id(), socketChannel.pipeline());
                        pipeline.addLast(
                                //自定义解码器
                                new DecoderHandler()
                                , new StringEncoder(StandardCharsets.UTF_8)
                                , new StringDecoder(StandardCharsets.UTF_8)
                                //自定义处理器
                                , serverHandler);
                    }

                });
        // 绑定端口, 同步等待成功
        ChannelFuture future;
        try {
            log.info("netty服务器在[{}]端口启动监听", port);
            //让netty跑起来的核心代码
            future = bootstrap.bind(port).sync();
            if (future.isSuccess()) {
                nioServerSocketChannel = (NioServerSocketChannel) future.channel();
                log.info("netty服务开启成功" + nioServerSocketChannel);
            } else {
                log.info("netty服务开启失败");
            }
            // 等待服务监听端口关闭,就是由于这里会将线程阻塞,导致无法发送信息,所以我这里开了线程
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //优雅退出,释放线程池资源
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }

    }

}

编写服务处理器

@Component
@ChannelHandler.Sharable
public class ServerHandler extends ChannelInboundHandlerAdapter {

    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     * 用来保存对应的设备-channel   后续可通过channelId找到对应的通道进行消息发送和接收
     */
    private static final Map<String, ChannelId> CHANNEL_MAP = new ConcurrentHashMap<>();

    /**
     * 在与客户端的连接已经建立之后将被调用
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        log.info("new gateway socket from {}", channel.remoteAddress());
    }

    /**
     * 当从客户端接收到一个消息时被调用
     * msg 就是硬件传送过来的数据信息
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //这是下面自己写的业务逻辑处理的方法
        String gwMac = message.getMsgGwMac();
        ChannelId channelId = ctx.channel().id();
        if (Objects.isNull(mac)) {
            //这里主要区分主从网关
            this.mac = gwMac;
            //从网关无需再绑定通道
            CHANNEL_MAP.put(gwMac, channelId);
        }
        //下面写业务
    }

    /**
     * 主动发送信息
     *
     * @param code code
     * @param msg  信息
     */
    public void send(String code, String msg) {
        if (CHANNEL_MAP.containsKey(code)) {
            ChannelPipeline pipeline = NettyServer.CHANNEL_MAP.get(CHANNEL_MAP.get(code));
            if (pipeline == null) {
                //
                SocketChannelInitHandler.CHANNEL_MAP.remove(CHANNEL_MAP.get(code));
            }
            assert pipeline != null;
            pipeline.writeAndFlush(msg);
        } else {
            System.out.println("-------设备已经断开连接-------");
        }
    }


    /**
     * 客户端与服务端断开连接时调用
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
     ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
                .addListener(ChannelFutureListener.CLOSE);
        ChannelId id = ctx.channel().id();
        //关闭
        SocketChannelInitHandler.CHANNEL_MAP.remove(id);
        log.info("网关: {} 服务端连接关闭...", ctx.channel().remoteAddress());
    }

    /**
     * 服务端接收客户端发送过来的数据结束之后调用
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
        log.info("信息接收完毕...");
    }

    /**
     * 在处理过程中引发异常时被调用
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常信息:rn " + cause.getMessage());
    }

}

编写解析器

public class DecoderHandler extends ByteToMessageDecoder {

    private static Map<ChannelHandlerContext, String> msgBufMap = new ConcurrentHashMap<>();

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        byte[] data = new byte[in.readableBytes()];
        in.readBytes(data);
        String msg = new String(data, StandardCharsets.UTF_8);
        if (msg.startsWith("#")) {
            if (msg.endsWith("#")) {
                out.add(msg);
            } else {
                msgBufMap.put(ctx, msg);
            }
        } else if (msg.endsWith("#") && msgBufMap.containsKey(ctx)) {
            msg = msgBufMap.get(ctx) + msg.split("#")[0];
            out.add(msg);
            msgBufMap.remove(ctx);
        }
    }
}

启动Netty服务

在主线程启动后,调用start启动netty服务

public class InitNettyServer implements CommandLineRunner {

    @Autowired
    private NettyServer nettyServer;

    @Autowired
    public void setNettyServer(NettyServer nettyServer) {
        this.nettyServer = nettyServer;
    }

    @Override
    public void run(String... args) throws Exception {
        nettyServer.start();
    }
}

配置文件application.yml

# 应用名称
spring:
  application:
    name: netty

# 应用服务 WEB 访问端口
server:
  port: 8010
netty:
  port: 7002

所需要依赖pom

<properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <netty-all-version>4.1.65.Final</netty-all-version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>${netty-all-version}</version>
        </dependency>

        <!-- 解码and编码器 -->
        <dependency>
            <groupId>org.msgpack</groupId>
            <artifactId>msgpack</artifactId>
            <version>0.6.12</version>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>netty</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>

    </dependencies>

编写测试程序

@RestController
public class SendTest {

    @Autowired
    private ServerHandler serverHandler;

    @GetMapping("/send/{code}")
    public String send(@PathVariable(value = "code") String code) {
        serverHandler.send(code,"123456789");
        return "o";
    }
}

这里我采用了api方式调用测试主动发送是否成功

启动程序

@EnableAsync
@SpringBootApplication
public class NettyNioApplication {

    public static void main(String[] args) {
        SpringApplication.run(NettyNioApplication.class, args);
    }
}

在这里插入图片描述
上图显示启动成功

连接设备

在这里插入图片描述
连接设备后发送一条数据去绑定设备。一定要先发送信息绑定设备(这里是模拟心跳,重心跳数据中获取对应的设备mac,这样我们就可以通过设备mac去主动向设备发送信息了)。

使用postman模拟控制命令

在这里插入图片描述
send后调试助手可以看到模拟的信息123456789

代码参考博客

https://blog.csdn.net/zhangleiyes123/article/details/103871450#comments_20159110

  • 3
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值