从零开始搭建属于自己的物联网平台(三)基于netty实现mqtt server网关

/\*\*

* 网关状态
*
* @return 网关状态
*/
Boolean status();
}


#### 使用netty实现mqtt server


这里使用netty来实现mqtt server。



package com.soft863.gateway.mqtt;

import com.soft863.gateway.DeviceGateway;
import com.soft863.gateway.message.codec.DefaultTransport;
import com.soft863.gateway.message.codec.Transport;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.ResourceLeakDetector;
import lombok.extern.slf4j.Slf4j;

/**
* @Author: 刘华林
* @Date: 21-3-9 下午20:07
* @Version 2.0
*/
@Slf4j
public class MqttServerGateway implements DeviceGateway {

/\*\* 网关ID(为后续多实例扩展) \*/
private String id;
/\*\* 服务端口 \*/
private Integer port;
/\*\* netty boos线程 \*/
private Integer bossGroupThreadCount;
/\*\* netty woreker线程数 推荐设置为核数\*2 \*/
private Integer workerGroupThreadCount;
/\*\* 网关状态 true 启动 false 关闭 \*/
private Boolean status;
/\*\*

* DISABLED(禁用): 不进行内存泄露的检测;
*
* SIMPLE(操作简单): 抽样检测,且只对部分方法调用进行记录,消耗较小,有泄漏时可能会延迟报告,默认级别;
*
* ADVANCED(高级): 抽样检测,记录对象最近几次的调用记录,有泄漏时可能会延迟报告;
*
* PARANOID(偏执): 每次创建一个对象时都进行泄露检测,且会记录对象最近的详细调用记录。是比较激进的内存泄露检测级别,消耗最大,建议只在测试时使用。
*/
private String leakDetectorLevel;
/**
* 最大消息长度
*/
private Integer maxPayloadSize;

private ChannelFuture channelFuture;
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;

public MqttServerGateway(String id,
                         Integer port,
                         Integer bossGroupThreadCount,
                         Integer workerGroupThreadCount,
                         String leakDetectorLevel,
                         Integer maxPayloadSize) {
    this.id = id;
    this.port = port;
    this.bossGroupThreadCount = bossGroupThreadCount;
    this.workerGroupThreadCount = workerGroupThreadCount;
    this.leakDetectorLevel = leakDetectorLevel;
    this.maxPayloadSize = maxPayloadSize;
}

@Override
public String getId() {
    return this.id;
}

@Override
public Integer getPort() {
    return this.port;
}

@Override
public Transport getTransport() {
    return DefaultTransport.MQTT;
}

@Override
public void startup() {
    log.info("Setting resource leak detector level to {}", leakDetectorLevel);
    ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.valueOf(leakDetectorLevel.toUpperCase()));

    log.info("Starting Server");
    //创建boss线程组 用于服务端接受客户端的连接
    bossGroup = new NioEventLoopGroup(bossGroupThreadCount);
    // 创建 worker 线程组 用于进行 SocketChannel 的数据读写
    workerGroup = new NioEventLoopGroup(workerGroupThreadCount);
    // 创建 ServerBootstrap 对象
    ServerBootstrap b = new ServerBootstrap();
    //设置使用的EventLoopGroup
    b.group(bossGroup, workerGroup)
            //设置要被实例化的为 NioServerSocketChannel 类
            .channel(NioServerSocketChannel.class)
            // 设置 NioServerSocketChannel 的处理器
            .handler(new LoggingHandler(LogLevel.INFO))
            // 设置连入服务端的 Client 的 SocketChannel 的处理器
            .childHandler(new MqttTransportServerInitializer(maxPayloadSize));
    // 绑定端口,并同步等待成功,即启动服务端
    try {
        channelFuture = b.bind(port).sync();
        status = true;
    } catch (InterruptedException e) {
        log.error("Server starting error");
        status = false;
    }
    log.info("Server started!");
}

@Override
public void shutdown() {
    log.info("Stopping Server");
    workerGroup.shutdownGracefully();
    bossGroup.shutdownGracefully();
    status = false;
    log.info("server stopped!");

}

@Override
public Boolean status() {
    return this.status;
}

}


##### 消息处理


当netty接收到设备上行消息之后,进行解析处理,将消息广播到消息总线,后续设备订阅消息总线中的内容,再进行业务处理。



package com.soft863.gateway.mqtt;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.mqtt.MqttDecoder;
import io.netty.handler.codec.mqtt.MqttEncoder;

/**
* @Author: 刘华林
* @Date: 19-4-3 下午3:26
* @Version 1.0
*/
public class MqttTransportServerInitializer extends ChannelInitializer {

private final int maxPayloadSize;

public MqttTransportServerInitializer(int maxPayloadSize) {
    this.maxPayloadSize = maxPayloadSize;
}

@Override
protected void initChannel(SocketChannel socketChannel) {
    ChannelPipeline pipeline = socketChannel.pipeline();
    pipeline.addLast("decoder", new MqttDecoder(maxPayloadSize));
    pipeline.addLast("encoder", MqttEncoder.INSTANCE);
    MqttTransportHandler handler = new MqttTransportHandler();
    pipeline.addLast(handler);
    socketChannel.closeFuture().addListener(handler);

}

}



package com.soft863.gateway.mqtt;

import com.alibaba.fastjson.JSON;
import com.soft863.gateway.DeviceInstance;
import com.soft863.gateway.auth.DeviceAuthenticator;
import com.soft863.gateway.matcher.TopicMatcher;
import com.soft863.gateway.message.DefaultDeviceMsg;
import com.soft863.gateway.message.Message;
import com.soft863.gateway.message.codec.DefaultTransport;
import com.soft863.gateway.message.codec.FromDeviceMessageContext;
import com.soft863.gateway.message.codec.mqtt.SimpleMqttMessage;
import com.soft863.gateway.mqtt.adapter.JsonMqttAdaptor;
import com.soft863.gateway.protocol.DeviceMessageCodec;
import com.soft863.gateway.protocol.ProtocolSupport;
import com.soft863.gateway.registry.DeviceSession;
import com.soft863.gateway.registry.MemoryProtocolSupportRegistry;
import com.soft863.gateway.tsl.adaptor.AdaptorException;
import com.soft863.gateway.util.ApplicationUtil;
import com.soft863.stream.core.eventbus.EventBus;
import com.soft863.stream.core.eventbus.message.AdapterMessage;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.mqtt.*;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import lombok.extern.slf4j.Slf4j;

import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static io.netty.handler.codec.mqtt.MqttMessageType.*;
import static io.netty.handler.codec.mqtt.MqttQoS.*;

/**
* @Author: 刘华林
* @Date: 21-3-9 下午20:22
* @Version 2.0
*/
@Slf4j
public class MqttTransportHandler extends ChannelInboundHandlerAdapter implements GenericFutureListener<Future<? super Void>> {

public static final MqttQoS MAX\_SUPPORTED\_QOS\_LVL = MqttQoS.AT\_LEAST\_ONCE;

private volatile boolean connected;
private volatile InetSocketAddress address;
private final ConcurrentMap<TopicMatcher, Integer> mqttQoSMap;
private String clientId;
private DeviceInstance instance;

/\*\*

* 设备session
*/
private DeviceSession deviceSession;

/\*\*

* 协议解析注册器
*/
private MemoryProtocolSupportRegistry memoryProtocolSupportRegistry;

public MqttTransportHandler() {
    this.mqttQoSMap = new ConcurrentHashMap<>();
    this.deviceSession = ApplicationUtil.getBean(DeviceSession.class);
    this.memoryProtocolSupportRegistry = ApplicationUtil.getBean(MemoryProtocolSupportRegistry.class);
}

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    if (msg instanceof MqttMessage) {
        processMqttMsg(ctx, (MqttMessage) msg);
    } else {
        ctx.close();
    }
}

/\*\*

* 处理MQTT消息事件(链接、发布、订阅、取消订阅、PING、断开连接)
*
* @param ctx
* @param msg
*/
private void processMqttMsg(ChannelHandlerContext ctx, MqttMessage msg) {
address = (InetSocketAddress) ctx.channel().remoteAddress();
if (msg.fixedHeader() == null) {
processDisconnect(ctx);
return;
}

    switch (msg.fixedHeader().messageType()) {
        case CONNECT:
            processConnect(ctx, (MqttConnectMessage) msg);
            break;
        case PUBLISH:
            processPublish(ctx, (MqttPublishMessage) msg);
            break;
        case SUBSCRIBE:
            processSubscribe(ctx, (MqttSubscribeMessage) msg);
            break;
        case UNSUBSCRIBE:
            processUnsubscribe(ctx, (MqttUnsubscribeMessage) msg);
            break;
        case PINGREQ:
            if (checkConnected(ctx)) {
                ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT\_MOST\_ONCE, false, 0)));
            }
            break;
        case DISCONNECT:
            if (checkConnected(ctx)) {
                processDisconnect(ctx);
            }
            break;
        default:
            break;

    }
}

private void processPublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg) {

    if (!checkConnected(ctx)) {
        return;
    }

    String topicName = mqttMsg.variableHeader().topicName();
    int msgId = mqttMsg.variableHeader().packetId();
    processDevicePublish(ctx, mqttMsg, topicName, msgId);

}

/\*\*

* 处理设备上行消息
*
* @param ctx
* @param mqttMsg
* @param topicName
* @param msgId
*/
private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) {
try {
if (topicName.equals(MqttTopics.DEVICE_REGISTRY_TOPIC)) {
// todo 设备自注册
log.info(JsonMqttAdaptor.validatePayload(mqttMsg.payload()));
} else {
log.info("message arrive " + JsonMqttAdaptor.validatePayload(mqttMsg.payload()));

            // 构建标准消息
            SimpleMqttMessage simpleMqttMessage = SimpleMqttMessage.builder()
                    .topic(topicName)
                    .payload(mqttMsg.payload())
                    .clientId(clientId)
                    .build();
            FromDeviceMessageContext messageDecodeContext = FromDeviceMessageContext.of(simpleMqttMessage, deviceSession, instance.getProtocolId());
            // 调用解析器
            ProtocolSupport protocolSupport = memoryProtocolSupportRegistry.getProtocolSupport(instance.getProtocolId());
            DeviceMessageCodec deviceMessageCodec = protocolSupport.getMessageCodecSupport(DefaultTransport.MQTT.getId());
            Message message = deviceMessageCodec.decode(messageDecodeContext);
            // 取得event bus
            EventBus eventBus = ApplicationUtil.getBean(EventBus.class);
            AdapterMessage adapterMessage = new AdapterMessage();
            adapterMessage.setTopic("/device/message/" + clientId);
            adapterMessage.setPayload(JSON.toJSONString(message));
            eventBus.publish(adapterMessage);
        }
        if (msgId > 0) {
            ctx.writeAndFlush(createMqttPubAckMsg(msgId));
        }
    } catch (AdaptorException e) {
        ctx.close();
    }

}

private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) {
    if (!checkConnected(ctx)) {
        return;
    }
    List<Integer> grantedQoSList = new ArrayList<>();
    for (MqttTopicSubscription subscription : mqttMsg.payload().topicSubscriptions()) {
        String topic = subscription.topicName();
        MqttQoS reqQoS = subscription.qualityOfService();
        switch (topic) {
            default:
                grantedQoSList.add(FAILURE.value());
                break;
        }
    }
    ctx.writeAndFlush(createSubAckMessage(mqttMsg.variableHeader().messageId(), grantedQoSList));
}

private static MqttSubAckMessage createSubAckMessage(Integer msgId, List<Integer> grantedQoSList) {
    MqttFixedHeader mqttFixedHeader =
            new MqttFixedHeader(SUBACK, false, AT\_LEAST\_ONCE, false, 0);
    MqttMessageIdVariableHeader mqttMessageIdVariableHeader = MqttMessageIdVariableHeader.from(msgId);
    MqttSubAckPayload mqttSubAckPayload = new MqttSubAckPayload(grantedQoSList);
    return new MqttSubAckMessage(mqttFixedHeader, mqttMessageIdVariableHeader, mqttSubAckPayload);
}

private void registerSubQoS(String topic, List<Integer> grantedQoSList, MqttQoS reqQoS) {
    grantedQoSList.add(getMinSupportedQos(reqQoS));
    mqttQoSMap.put(new TopicMatcher(topic), getMinSupportedQos(reqQoS));
}

private static int getMinSupportedQos(MqttQoS reqQoS) {
    return Math.min(reqQoS.value(), MAX\_SUPPORTED\_QOS\_LVL.value());
}

private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) {
    if (!checkConnected(ctx)) {
        return;
    }
    for (String topicName : mqttMsg.payload().topics()) {
        mqttQoSMap.remove(new TopicMatcher(topicName));
    }
    ctx.writeAndFlush(createUnSubAckMessage(mqttMsg.variableHeader().messageId()));
}

private void processDisconnect(ChannelHandlerContext ctx) {
    connected = false;
    // 更新注册器状态
    instance = deviceSession.get(clientId);
    if (instance != null) {
        instance.setStatus(connected);
    }
    ctx.close();
}

private void processConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) {
    String username = msg.payload().userName();
    String password = "";
    if (msg.variableHeader().hasPassword()) {
        try {
            password = new String(msg.payload().passwordInBytes(), "utf-8");
        } catch (UnsupportedEncodingException e) {

        }
    }
    // 设备认证
    clientId = msg.payload().clientIdentifier();
    boolean authResult = ApplicationUtil.getBean(DeviceAuthenticator.class).auth(clientId, username, password);
    if (authResult) {
        ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION\_ACCEPTED));
        connected = true;
        // 更新注册器状态
        instance = deviceSession.get(clientId);
        if (instance != null) {
            instance.setChannel(ctx);
            instance.setStatus(connected);
        }

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!

08886)]

[外链图片转存中…(img-fE08qHZM-1715676808887)]

[外链图片转存中…(img-vkbnzlJk-1715676808887)]

[外链图片转存中…(img-tlY4iO42-1715676808888)]

[外链图片转存中…(img-WCQjeFWm-1715676808888)]

[外链图片转存中…(img-chuuMgXJ-1715676808889)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值