/\*\*
* 网关状态
*
* @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年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!
08886)]
[外链图片转存中…(img-fE08qHZM-1715676808887)]
[外链图片转存中…(img-vkbnzlJk-1715676808887)]
[外链图片转存中…(img-tlY4iO42-1715676808888)]
[外链图片转存中…(img-WCQjeFWm-1715676808888)]
[外链图片转存中…(img-chuuMgXJ-1715676808889)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!