Mqtt 应用
物联网和手机app通信等。介绍 略
基于Netty实现 mqtt
Mqtt 有发布订阅。怎么通过netty 实现?
Netty 怎么捕获连接事件。
基于Netty channel Session管理?
Topic 和 channel之间的关系
- 消息 Qos 等级
- 怎么集群。
简单的Mqtt 看实现
public static void main(String[] args) {
MqttTsServer server = new MqttTsServer();
server.run();
}
public void run(){
// 监听端口号
int port = 1883;
// 构建主线程-用于分发socket请求
EventLoopGroup boosGroup = new NioEventLoopGroup(1);
// 构建工作线程-用于处理请求处理
EventLoopGroup workGroup = new NioEventLoopGroup(4);
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boosGroup,workGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true)
// .childOption(ChannelOption.SO_BACKLOG,1024) //等待队列
.childOption(ChannelOption.SO_REUSEADDR,true) //快速复用
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 这个地方注意,如果客户端发送请求体超过此设置值,会抛异常
socketChannel.pipeline().addLast(new MqttDecoder(1024*1024));
//增加Mqtt的 Decoder and Encoder
socketChannel.pipeline().addLast( MqttEncoder.INSTANCE);
// 加载MQTT编解码协议,包含业务逻辑对象
socketChannel.pipeline().addLast(new TestMqttHandler());
}
});
//启动服务
serverBootstrap.bind(port).addListener(future -> {
log.info("服务端成功绑定端口号={}",port);
});
}catch (Exception e){
boosGroup.shutdownGracefully();
workGroup.shutdownGracefully();
log.error("mqttServer启动失败:{}",e);
}
}
启动类看,初始化channel 增加了MQTT的协议。
注意增加了一个TestMqttHandler() 重要。所有的Mqtt处理都是通过这个Handler
Handler处理类分析
@Slf4j
@ChannelHandler.Sharable
public class TestMqttHandler extends ChannelInboundHandlerAdapter {
private static final Collection<Channel> clientList = new HashSet();
private static final Map<String,Object> msgMap = new HashMap<>();
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
super.channelUnregistered(ctx);
protocolProcess.disConnect().processDisConnect(ctx.channel(), null);
logger.info("------disconnection------非正常关闭的连接");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof MqttMessage) {
Channel channel = ctx.channel();
MqttMessage message = (MqttMessage) msg;
MqttMessageType messageType = message.fixedHeader().messageType();
log.info("MQTT接收到的发送类型===》{}",messageType);
switch (messageType) {
// 建立连接
case CONNECT:
try {
this.connect(channel, (MqttConnectMessage) message);
}catch (Exception e){
//如果用户密码,客户端ID校验不成功,会二次建立CONNECT类型连接
//但是没有实际意义
}
break;
// 发布消息
case PUBLISH:
this.publish(channel, (MqttPublishMessage) message);
break;
// 订阅主题
case SUBSCRIBE:
this.subscribe(channel, (MqttSubscribeMessage) message);
break;
// 退订主题
case UNSUBSCRIBE:
this.unSubscribe(channel, (MqttUnsubscribeMessage) message);
break;
// 心跳包
case PINGREQ:
this.pingReq(channel, message);
break;
// 断开连接
case DISCONNECT:
this.disConnect(channel, message);
break;
// 确认收到响应报文,用于服务器向客户端推送qos1/qos2后,客户端返回服务器的响应
case PUBACK:
this.puback(channel, message);
break;
// qos2类型,发布收到
case PUBREC:
this.pubrec(channel, message);
break;
// qos2类型,发布释放响应
case PUBREL:
this.pubrel(channel, message);
break;
// qos2类型,发布完成
case PUBCOMP:
this.pubcomp(channel, message);
break;
default:
if (log.isDebugEnabled()) {
log.debug("Nonsupport server message type of '{}'.", messageType);
}
break;
}
}
}
继承extends ChannelInboundHandlerAdapter 类 重写channelRead(ChannelHandlerContext ctx, Object msg)
通过判断 if (msg instanceof MqttMessage) { 。。。switch (messageType) { 分析消息事件
建立连接事件,需要将channel放到List中即Session管理channel.writeAndFlush(okResp); clientList.add(channel); channel需要writeAndFlush 输出应答。
输出应答的枚举 io.netty.handler.codec.mqtt.MqttConnectReturnCode
MqttMessage 枚举事件 io.netty.handler.codec.mqtt.MqttMessageType
CONNECT(1), CONNACK(2), PUBLISH(3)。。。
连接connect
InetSocketAddress address = (InetSocketAddress)contextBo.getHandlerContext().channel().remoteAddress();
String host = address.getAddress().getHostAddress();
String clientId = msg.payload().clientIdentifier();
String username = msg.payload().userName();
String password = msg.payload().passwordInBytes() == null ? null : new String(msg.payload().passwordInBytes(), CharsetUtil.UTF_8);
if (!authManager.checkValid(clientId, username, passw