基于Vertx实现可配置及可扩展的IOT服务

搭建框架的目标

        相信写过IOT服务的伙伴应该知道,面对各种千奇百怪的通信协议,特别是16进制报文的协议,有些协议看的确实有点让人头疼。但这些协议中也有很多共性,不必针对每过协议都把一些业务无关的代码再撸一遍。

        搭建这个项目主要是针对常见的TCP连接为基础的设备通信协议做一些抽象及规范化处理,减低一些开发的成本,目标是实现一个可配置的,便于扩展各种协议的框架。

Vertx简介

        Vert.x是Eclipse基金会下面的一个开源项目,Vert.x的基本定位是一个事件驱动的编程框架,通过Vert.x使用者可以用相对低的成本就享受到NIO带来的高性能。netty是Vert.x底层使用的通讯组件,Vert.x为了最大限度的降低使用门槛,刻意屏蔽掉了许多底层netty相关的细节,比如ByteBuf、引用计数等等。

        本文主要见解的是搭建一个可配置和可扩展的IOT服务,并不会详细展开讲解Vertx,Vertx相关内容可上官网查看《vertx官网》​​​​​​

IOT通信中的常见概念

1.logicAddress

        逻辑通信地址。在常见的设备协议中,都会有逻辑通信地址这个概念,用于标识当前的连接是具体的某个设备。有了这个逻辑地址之后就可以很方便的找到这个连接。

        在业务系统中建立档案的时候用这个地址,后续也可以通过这个通信地址将指定的命令下发给指定的设备。

2.messageType

        消息类型。在TCP通信中,设备上报的不止一类消息,但在常见的设备通信协议中都会针对不同的消息做不同的标识,借此来区分每条上传消息的含义。所以在设备上报的报文中我们根据通信协议的定义找到报文的标识位,然后再做对应的处理。

3.session

        会话。session主要用于管理连接。设备和服务端建立连接之后会产生一个socket,但很多时候这个socket缺少一些语义和描述,所以我们会对这个socket做一些包装,比如抽象一些方法,绑定设备的逻辑地址以便后续查找和调用。

代码架构流程

        整体的核心流程如下:

核心代码分析

yaml文件配置

        配置多个协议的协议名称和协议通信端口号,这里用多个端口区分不同的协议,避免协议内容相近的时候出现解析错误的情况。

protocols:
  - name: ZHONGXING #中兴
    port: 8898
  - name: HUAWEI #华为
    port: 9666

 ProtocolServerBootstrap

        这里主要是加载yaml配置,然后启动相应的TcpServer服务监听端口,并根据配置定义找打对应协议的编解码器,将消息转发到对应的编解码器中。

        这里用到了两个自定义注解:

        @CodecScan:标识编解码器要扫描哪些包。

        @Protocol:注解来标识编解码器对应的通信协议。

/**
 * @author yan
 * @date 2023/9/12
 */
@Slf4j
public class ProtocolServerBootstrap extends AbstractVerticle {

    private Class<?> starter;
    private static Map<String, ProtocolConfig> protocols = new ConcurrentHashMap<>();
    private static Map<String, AbstractProtocolCodec> codecMap = new ConcurrentHashMap<>();

    public ProtocolServerBootstrap(Class<?> starter) {
        this.starter = starter;
    }

    @Override
    public void init(Vertx vertx, Context context) {
        super.init(vertx, context);
        loadProfile();
        loadProtocolCodec();
    }

    public void loadProfile() {
        InputStream inputStream = null;
        try {
            inputStream = this.getClass().getClassLoader().getResourceAsStream("protocol.yml");
            Yaml yaml = new Yaml();
            Map<String, List<Object>> map = yaml.load(inputStream);
            List<Object> protocolConfigs = map.get("protocols");
            String host = NetUtil.getLocalhost().getHostAddress();
            protocolConfigs.stream()
                    .map(item -> JSONUtil.toBean(JSONUtil.toJsonStr(item), ProtocolConfig.class))
                    .forEach(config -> {
                        protocols.put(config.getName(), new ProtocolConfig().setName(config.getName()).setHost(host).setPort(config.getPort()));
                    });
        } catch (Exception e) {
            e.printStackTrace();
            log.error("配置文件解析失败:" + e);
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void loadProtocolCodec() {
        try {
            CodecScan codecScan = starter.getAnnotation(CodecScan.class);
            if(codecScan == null){
                this.protocols.clear();
                return;
            }
            String[] packages = codecScan.value();
            for (String p : packages) {
                Reflections reflection = new Reflections(p);
                Set<Class<?>> classes = reflection.getTypesAnnotatedWith(Protocol.class);
                for (Class<?> aClass : classes) {
                    Protocol annotation = aClass.getAnnotation(Protocol.class);
                    codecMap.put(annotation.value(), (AbstractProtocolCodec) aClass.newInstance());
                    log.info("加载编解码器:" + aClass.getName());
                }
            }
        } catch (Exception e) {
            log.error("加载编解码器失败:" + e);
        }
    }

    @Override
    public void start() {
        protocols.forEach((name, protocol) -> {
            AbstractProtocolCodec codec = codecMap.get(name);
            vertx.deployVerticle(codec);
            SocketAddress address = new SocketAddressImpl(protocol.getPort(), protocol.getHost());
            NetServer server = vertx.createNetServer();
            server.connectHandler(codec)
                    .listen(address)
                    .onComplete(res -> {
                        if (res.succeeded()) {
                            log.info("{}服务启动成功,绑定/{}", protocol.getName(), address);
                        } else {
                            if (res.cause() != null) {
                                log.error("服务启动失败,cause:" + res.cause());
                            }
                        }
                    });
        });
    }

    public AbstractProtocolCodec getProtocolCodec(String protocolName) {
        return codecMap.get(protocolName);
    }
}

AbstractProtocolCodec

         抽象编解码器类,主要包含监听服务端收到的消息,会话管理,管理处理器等。

/**
 * @author yan
 * @date 2023/9/12
 */
@Slf4j
public abstract class AbstractProtocolCodec extends AbstractVerticle implements Handler<NetSocket> {

    private Map<String, BaseSession> logicAddressSessionMap = new ConcurrentHashMap<>();
    private Map<NetSocket, BaseSession> socketSessionMap = new ConcurrentHashMap<>();

    private Map<String, AbstractProtocolHandler> handlerMap = new ConcurrentHashMap<>();

    @Override
    public void init(Vertx vertx, Context context) {
        super.init(vertx, context);
        vertx.eventBus().registerDefaultCodec(BaseMessage.class, new GenericMessageCodec<BaseMessage>() {
        });
        vertx.eventBus().registerDefaultCodec(BaseSession.class, new GenericMessageCodec<BaseSession>() {
        });
        registerHandlers();
    }

    @Override
    public void handle(NetSocket socket) {
        log.info("收到新的连接:" + socket);
        activeSocket(socket);
        socket.closeHandler(handler -> {
            log.info("连接已断开:" + socket);
            afterCloseSocket(socket);
            removeSession(socket);
        });
        socket.handler(data -> {
            try {
                BaseMessage message = new BaseMessage().setSocket(socket).setBuffer(data);
                if(!socketSessionMap.containsKey(socket)){
                    String logicAddress = getLogicAddress(message);
                    registerSession(logicAddress, socket);
                }
                decode(message);
            } catch (Exception e) {
                e.printStackTrace();
                log.error("解码处理失败,throw:" + e);
            }
        });
    }

    private BaseSession registerSession(String logicAddress, NetSocket socket) {
        BaseSession session = new BaseSession().setLogicAddress(logicAddress).setSocket(socket);
        logicAddressSessionMap.put(logicAddress, session);
        socketSessionMap.put(socket, session);
        return session;
    }

    private void removeSession(NetSocket socket) {
        BaseSession session = socketSessionMap.remove(socket);
        if(session != null){
            logicAddressSessionMap.remove(session.getLogicAddress());
        }
    }

    public BaseSession getSessionByLogicAddress(String logicAddress) {
        return logicAddressSessionMap.get(logicAddress);
    }

    protected abstract List<AbstractProtocolHandler> getHandlers();

    private void registerHandlers() {
        List<AbstractProtocolHandler> handlers = getHandlers();
        handlers.forEach(handler -> {
            handlerMap.put(handler.getMessageType(), handler);
            vertx.deployVerticle(handler);
        });
    }

    public AbstractProtocolHandler getHandlerByMessageType(String messageType) {
        return handlerMap.get(messageType);
    }

    protected abstract void decode(BaseMessage message);


    protected abstract String getLogicAddress(BaseMessage message);


    protected void activeSocket(NetSocket socket) {

    }

    protected void afterCloseSocket(NetSocket socket) {

    }
}

AbstractProtocolHandler

        处理器抽象类

/**
 * @author yan
 * @date 2023/9/14
 */
public abstract class AbstractProtocolHandler<T, R> extends AbstractVerticle implements Handler<Message<T>>, InvokeHandler<R> {

    @Override
    public void start() throws Exception {
        vertx.eventBus().consumer(getTopic(), this::handle);
    }

    protected abstract String getTopic();

    protected abstract String getMessageType();

    @Override
    public void write(BaseSession session, Buffer buffer) {
        session.getSocket().write(buffer);
    }
}

InvokeHandler

/**
 * @author yan
 * @date 2023/9/14
 */
public interface InvokeHandler<T> {

    /**
     * 根据传入参数获取buffer
     * @param req
     * @return
     */
    Buffer getBuffer(T req);

    /**
     * 下发消息
     * @param session
     * @param buffer
     */
    void write(BaseSession session, Buffer buffer);
}

InvokeAdapter

/**
 * 服务调用适配器
 *
 * @author yan
 * @date 2023/9/14
 */
public class InvokeAdapter {

    private ProtocolServerBootstrap bootstrap;

    public InvokeAdapter(ProtocolServerBootstrap bootstrap){
        this.bootstrap = bootstrap;
    }

    public void send(String protocolName, String logicAddress, String messageType, Object param) {
        AbstractProtocolCodec codec = bootstrap.getProtocolCodec(protocolName);
        BaseSession session = codec.getSessionByLogicAddress(logicAddress);
        if (session == null || session.getSocket() == null) {
            throw new RuntimeException("session is not exist or closed");
        }
        AbstractProtocolHandler handler = codec.getHandlerByMessageType(messageType);
        Buffer buffer = handler.getBuffer(param);
        handler.write(session, buffer);
    }

}

华为协议实现

HuaweiCodec

        华为协议编解码器

/**
 * @author yan
 * @date 2023/9/12
 */
@Slf4j
@Protocol("HUAWEI")
public class HuaweiCodec extends AbstractProtocolCodec {

    @Override
    protected List<AbstractProtocolHandler> getHandlers() {
        return Arrays.asList(new HuaweiParamReadHandler(), new HuaweiParamWriteHandler(), new HuaweiParamWriteBatchHandler());
    }

    @Override
    protected void decode(BaseMessage message) {
        String dataStr = ByteUtils.hexToHexString(message.getBuffer().getBytes());
        String messageType = dataStr.substring(14, 16);
        vertx.eventBus().publish(HuaweiMessageTypeConstants.getMessageTopic(messageType), message);
    }

    @Override
    protected String getLogicAddress(BaseMessage message) {
        // 这里根据消息解析出对应的通信地址
        return "001";
    }
}

Handler处理器

/**
 * @author yan
 * @date 2023/9/13
 */
@Slf4j
public class HuaweiParamReadHandler extends AbstractProtocolHandler<BaseMessage, Object> {

    @Override
    protected String getTopic() {
        return HuaweiMessageTypeConstants.READ;
    }

    @Override
    public void handle(Message<BaseMessage> message) {
        BaseMessage baseMessage = message.body();
        log.info("收到读参数命令返回:" + ByteUtils.hexToHexString(baseMessage.getBuffer().getBytes()));
        baseMessage.getSocket().write(baseMessage.getBuffer());
    }

    @Override
    public String getMessageType() {
        return HuaweiMessageTypeConstants.READ;
    }

    @Override
    public Buffer getBuffer(Object req) {
        log.info("发送read消息:" + req);
        return Buffer.buffer(new byte[]{0x11, 0x11, 0x11});
    }
}

测试调用

编写主类

/**
 * @author yan
 * @date 2023/9/11
 */
@CodecScan("com.cdw.pv.iot.modules")
public class PvApplication {

    public static void main(String[] args) {
        ProtocolServerBootstrap bootstrap = new ProtocolServerBootstrap(PvApplication.class);
        Vertx vertx = Vertx.vertx();
        vertx.deployVerticle(bootstrap);
        // 开启一个http服务,模拟外部调用
        startHttpServer(vertx, bootstrap);
    }

    private static void startHttpServer(Vertx vertx, ProtocolServerBootstrap bootstrap) {
        InvokeAdapter adapter = new InvokeAdapter(bootstrap);
        HttpServer httpServer = vertx.createHttpServer();
        httpServer.requestHandler(handler -> {
            System.out.println("request请求:" + handler);
            adapter.send("HUAWEI", "001", HuaweiMessageTypeConstants.READ, "123");
            handler.response().end(Buffer.buffer("success"));
        }).listen(8899).onComplete(handler -> {
            if (handler.succeeded()) {
                System.out.println("http服务器启动成功");
            }
        });
    }
}

发生指令

      使用TCP连接根据发送指令  

服务调用

        模拟发送请求

        收到消息

总结

        以上就是通用IOT服务的整体架构了。

        这个只是一个基本版本的,后续可以根据实际情况做调整。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: Vertx是一种基于事件驱动的应用程序框架,而MyBatis是一种流行的Java持久层框架。Vertx是轻量级的,可扩展的,适用于构建高性能的网络应用程序,而MyBatis则提供了一种优雅的方法来管理应用程序与数据库之间的交互。 Vertx与MyBatis的结合可以在构建Web应用程序时提供更好的开发体验和性能。Vertx通过异步IO和事件驱动的方式,可以快速地处理来自Web客户端的请求。而MyBatis可以通过提供有针对性的SQL语句,方便地对数据库进行操作。 Vertx提供了一个轻量级的Web服务器,可以处理HTTP请求和Websocket连接。而MyBatis可以方便地将数据库查询结果映射为Java对象。通过使用Vertx和MyBatis,我们可以将数据库查询和处理请求的逻辑解耦,使代码更易于理解和维护。 使用Vertx和MyBatis的组合,我们可以轻松地构建可扩展的,高性能的Web应用程序。我们可以使用Vertx的事件总线机制来处理不同组件之间的消息传递,同时利用MyBatis的缓存机制提高数据库查询的性能。 总的来说,Vertx和MyBatis是一对强大的组合,可以帮助我们构建高性能、可扩展的Web应用程序。通过将异步事件驱动的编程模型与优雅的数据库交互方式结合起来,我们可以更轻松地开发复杂的应用程序。 ### 回答2: Vert.x是一个基于JVM的高性能、非阻塞、事件驱动的应用框架,它可以用于构建轻量级、可伸缩、可扩展的分布式系统。而MyBatis是一个流行的Java持久化框架,它能够帮助我们方便地进行数据库访问。 Vert.x和MyBatis可以结合使用,以实现高效的数据库访问和处理。通常情况下,我们可以使用Vert.x的事件驱动机制来处理Web请求,而MyBatis则用于执行数据库操作。 首先,我们可以使用Vert.x的路由器来处理HTTP请求,并在请求处理程序中调用MyBatis执行数据库查询或更新操作。通过这种方式,我们可以确保数据库的访问是非阻塞的,避免了阻塞其他请求的情况发生。 其次,Vert.x的事件总线机制可以用于将不同模块之间的消息进行传递和处理。我们可以在MyBatis的映射器接口中定义观察者,当数据库发生变化时,MyBatis会向事件总线发布消息,其他模块可以根据需求来订阅这些消息进行相应的处理。 另外,Vert.x的异步特性与MyBatis的异步API结合使用,可以进一步提升应用程序的性能。我们可以使用Vert.x的异步回调机制来执行MyBatis的异步数据库操作,从而避免线程阻塞和资源浪费。 总而言之,Vert.x和MyBatis的结合使用可以为我们提供一个高性能、可伸缩的应用程序架构。通过将事件驱动的非阻塞处理与灵活的数据库访问相结合,我们可以构建出更好的分布式系统。 ### 回答3: Vertx是一个开源的、响应式的应用程序平台,可用于构建高性能、可伸缩的微服务和分布式系统。MyBatis是一个Java持久层框架,用于管理数据库访问,并且可以将数据库操作与应用程序逻辑分离。那么,Vertx和MyBatis可以结合使用吗? 答案是可以的。Vertx提供了异步的、非阻塞的I/O操作,而MyBatis则可以将I/O操作,例如数据库查询和更新,封装成简洁易用的API。Vertx和MyBatis的结合可以提供高效的数据库访问和管理,并且保持系统的响应性和可伸缩性。 在Vertx中使用MyBatis非常简单。首先,需要在Vertx应用程序的依赖中添加MyBatis的库。然后,可以通过MyBatis的配置文件来配置数据库连接和SQL映射。接下来,可以在Vertx的事件处理器中使用MyBatis的API来执行数据库操作。 在Vertx中使用MyBatis可以享受到Vertx的优势,例如事件驱动的架构和高性能的I/O操作。此外,Vertx还提供了丰富的异步编程模型和工具,可以方便地与MyBatis结合使用,例如使用Vertx的Future和Promise来处理异步的数据库操作结果。 总而言之,Vertx和MyBatis的结合可以提供高效的数据库访问和管理,并且保持系统的响应性和可伸缩性。这是一种强大的组合,可以用于构建高性能、可伸缩的微服务和分布式系统。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值