基于Netty实现Mqtt客户端(三)-创建连接

如何创建mqtt连接

要建立一个mqtt连接,分两个步骤:

  • 创建socket长连接
  • 发送连接报文(mqtt协议)
  • 接收报文应答

依赖

dependencies {
    implementation 'io.netty:netty-codec-mqtt:4.1.68.Final'
}

创建长连接

示例:

MqttClient mqttClient = new MqttClient();
MqttConnectOptions options = new MqttConnectOptions();
options.setHost("localhost");
options.setPort(1883);
options.setUserName("testuser");
options.setPassword("123456".getBytes(StandardCharsets.UTF_8));
options.setClientIdentifier("netty_mqtt_c1");
options.setKeepAliveTime(10);
options.setCleanSession(true);
mqttClient.connect(options);

发起连接:MqttClient.java

synchronized public void connect(MqttConnectOptions options) throws Exception {
    connect(options, actionTimeout);
}

synchronized public void connect(MqttConnectOptions options, long timeout) throws Exception {
    if (this.connectOptions != null) {
        return;
    }
    this.connectOptions = options;
    this.connectTimeout = timeout;

    try {
        doConnect(options, timeout);
        onConnected();
    } catch (Exception e) {
//            e.printStackTrace();
        onConnectFailed(e);
        throw e;
    }
}

private void doConnect(MqttConnectOptions options, long timeout) throws Exception {
	// 创建连接
	EventLoopGroup group = new NioEventLoopGroup();
	connectTask = new AsyncTask<String>() {
	    @Override
	    public String call() throws Exception {
	        Bootstrap b = new Bootstrap()
	                .group(group)
	                .channel(NioSocketChannel.class)
	                .handler(new ChannelInitializer<SocketChannel>() {
	                    @Override
	                    protected void initChannel(SocketChannel channel) throws Exception {
	                        channel.pipeline()
	                                .addLast("decoder", new MqttDecoder())//解码
	                                .addLast("encoder", MqttEncoder.INSTANCE)//编码
	                                .addLast("handler", new MqttHandler());
	                    }
	                });
	        ChannelFuture ch = b.connect(options.getHost(), options.getPort()).sync();
	        channel = ch.channel();
	        Log.i("--已连接->" + channel.localAddress().toString());
	        return null;
	    }
	}.execute();
	try {
	    connectTask.get(timeout, TimeUnit.MILLISECONDS);
	} catch (Exception e) {
	    Log.i("-->连接异常:" + e);
	    group.shutdownGracefully();
	    throw e;
	}
	
	if (channel == null)
	    return;
	
	// 发送mqtt协议连接报文
	doConnect0(channel, options, timeout);
	
	// 等待连接关闭的任务
	connectTask = new AsyncTask<String>() {
	    @Override
	    public String call() throws Exception {
	        try {
	            channel.closeFuture().sync();
	        } catch (Exception e) {
	            Log.i("-->连接断开异常:" + e);
	        } finally {
	            group.shutdownGracefully();
	            if (!isClosed()) {
	                // 非主动断开,可能源于服务器原因
	                Exception e = new ConnectException("Connection closed unexpectedly");
	                Log.i("-->连接断开:" + e);
	                onConnectLost(e);
	            } else {
	                Log.i("-->连接断开:主动");
	            }
	        }
	        return null;
	    }
	}.execute();
}

注:AsyncTask是封装的异步任务类,类似FutureTask
连接参数配置类:MqttConnectOptions.java

package io.x2ge.mqtt;

import io.netty.handler.codec.mqtt.MqttVersion;
import io.x2ge.mqtt.utils.StringUtils;

public class MqttConnectOptions {
    private String host;
    private int port;

    // 可变报头部分
    private MqttVersion mqttVersion = MqttVersion.MQTT_3_1_1;
    private boolean isWillRetain = false;
    private int willQos = 0;
    private boolean isWillFlag = false;
    private boolean isCleanSession = false;
    private int keepAliveTime = 60;

    // 有效载荷 :客户端标识符,遗嘱主 题,遗嘱消息,用户名,密码
    private String clientIdentifier = "";
    private String willTopic = "";
    private byte[] willMessage;
    private String userName = "";
    private byte[] password;


    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public MqttVersion getMqttVersion() {
        return mqttVersion;
    }

    public boolean isHasUserName() {
        return !StringUtils.isEmpty(userName);
    }

    public boolean isHasPassword() {
        return password != null && password.length > 0;
    }

    public boolean isWillRetain() {
        return isWillRetain;
    }

    public void setWillRetain(boolean willRetain) {
        this.isWillRetain = willRetain;
    }

    public int getWillQos() {
        return willQos;
    }

    public void setWillQos(int willQos) {
        this.willQos = willQos;
    }

    public boolean isWillFlag() {
        return isWillFlag;
    }

    public void setWillFlag(boolean willFlag) {
        this.isWillFlag = willFlag;
    }

    public boolean isCleanSession() {
        return isCleanSession;
    }

    /**
     * 如果清理会话(CleanSession)标志被设置为true,
     * 客户端和服务端在重连后,会丢弃之前的任何会话相关内容及配置
     *
     * @param cleanSession true 重连后丢弃相关数据
     */
    public void setCleanSession(boolean cleanSession) {
        this.isCleanSession = cleanSession;
    }

    public int getKeepAliveTime() {
        return keepAliveTime;
    }

    /**
     * @param keepAliveTime 维持连接时间,秒
     */
    public void setKeepAliveTime(int keepAliveTime) {
        this.keepAliveTime = keepAliveTime;
    }

    public String getClientIdentifier() {
        return clientIdentifier;
    }

    public void setClientIdentifier(String clientIdentifier) {
        this.clientIdentifier = clientIdentifier;
    }

    public String getWillTopic() {
        return willTopic;
    }

    public void setWillTopic(String willTopic) {
        this.willTopic = willTopic;
    }

    public byte[] getWillMessage() {
        return willMessage;
    }

    public void setWillMessage(byte[] willMessage) {
        this.willMessage = willMessage;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public byte[] getPassword() {
        return password;
    }

    public void setPassword(byte[] password) {
        this.password = password;
    }
}

发送连接报文

private void doConnect0(Channel channel, MqttConnectOptions options, long timeout) throws Exception {
    if (channel == null)
        return;

    try {
        connectProcessor = new ConnectProcessor();
        String s = connectProcessor.connect(channel, options, timeout);
        if (ProcessorResult.RESULT_SUCCESS.equals(s)) {
            Log.i("-->连接成功");
        } else {
            throw new CancellationException();
        }
    } catch (Exception e) {
        if (e instanceof CancellationException) {
            Log.i("-->连接取消");
        } else {
            Log.i("-->连接异常:" + e);
            throw e;
        }
    }
}

ConnectProcessor.java

package io.x2ge.mqtt.core;

import io.netty.channel.Channel;
import io.netty.handler.codec.mqtt.MqttConnAckMessage;
import io.netty.handler.codec.mqtt.MqttConnAckVariableHeader;
import io.netty.handler.codec.mqtt.MqttConnectMessage;
import io.x2ge.mqtt.MqttConnectOptions;
import io.x2ge.mqtt.utils.AsyncTask;
import io.x2ge.mqtt.utils.Log;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

public class ConnectProcessor extends AsyncTask<String> {

    private long timeout;
    private final AtomicBoolean receivedAck = new AtomicBoolean(false);
    private Exception e;

    @Override
    public String call() throws Exception {
        if (!isCancelled() && !receivedAck.get() && e == null) {
            synchronized (receivedAck) {
                receivedAck.wait(timeout);
            }
        }

        if (e != null) {
            throw e;
        }

        return receivedAck.get() ? ProcessorResult.RESULT_SUCCESS : ProcessorResult.RESULT_FAIL;
    }

    public String connect(Channel channel, MqttConnectOptions options, long timeout) throws Exception {
        this.timeout = timeout;

        MqttConnectMessage msg = ProtocolUtils.connectMessage(options);
        Log.i("-->发起连接:" + msg);
        channel.writeAndFlush(msg);
        return execute().get(timeout, TimeUnit.MILLISECONDS);
    }

    public void processAck(Channel channel, MqttConnAckMessage msg) {
        MqttConnAckVariableHeader mqttConnAckVariableHeader = msg.variableHeader();
        String errormsg = "";
        switch (mqttConnAckVariableHeader.connectReturnCode()) {
            case CONNECTION_ACCEPTED:
                synchronized (receivedAck) {
                    receivedAck.set(true);
                    receivedAck.notify();
                }
                return;
            case CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD:
                errormsg = "用户名密码错误";
                break;
            case CONNECTION_REFUSED_IDENTIFIER_REJECTED:
                errormsg = "clientId不允许链接";
                break;
            case CONNECTION_REFUSED_SERVER_UNAVAILABLE:
                errormsg = "服务不可用";
                break;
            case CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION:
                errormsg = "mqtt 版本不可用";
                break;
            case CONNECTION_REFUSED_NOT_AUTHORIZED:
                errormsg = "未授权登录";
                break;
            default:
                errormsg = "未知问题";
                break;
        }

        synchronized (receivedAck) {
            e = new IOException(errormsg);
            receivedAck.notify();
        }
    }
}

生成连接报文:

public static MqttConnectMessage connectMessage(MqttConnectOptions options) {
    MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.CONNECT,
            false,
            MqttQoS.AT_MOST_ONCE,
            false,
            10);
    MqttConnectVariableHeader variableHeader = new MqttConnectVariableHeader(
            options.getMqttVersion().protocolName(),
            options.getMqttVersion().protocolLevel(),
            options.isHasUserName(),
            options.isHasPassword(),
            options.isWillRetain(),
            options.getWillQos(),
            options.isWillFlag(),
            options.isCleanSession(),
            options.getKeepAliveTime());
    MqttConnectPayload payload = new MqttConnectPayload(
            options.getClientIdentifier(),
            options.getWillTopic(),
            options.getWillMessage(),
            options.getUserName(),
            options.getPassword());
    return new MqttConnectMessage(fixedHeader, variableHeader, payload);
}

在自定义的MqttHandler中,接收连接报文的应答:

class MqttHandler extends SimpleChannelInboundHandler<Object> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msgx) throws Exception {
        if (msgx == null) {
            return;
        }

        switch (mqttFixedHeader.messageType()) {
            case CONNACK:
            	// 连接报文的响应
                if (connectProcessor != null)
                    connectProcessor.processAck(ctx.channel(), (MqttConnAckMessage) msg);
                break;
            case SUBACK:
            	// 订阅报文的响应
                break;
            case UNSUBACK:
            	// 取消订阅报文的响应
                break;
            case PUBLISH:
            	 // 收到消息报文
                break;
            case PUBACK:
            	// 发布消息报文响应
                // qos = 1的发布才有该回应
                break;
            case PUBREC:
                // qos = 2的发布才参与
                break;
            case PUBREL:
                // qos = 2的发布才参与
                break;
            case PUBCOMP:
                // qos = 2的发布才参与
                break;
            case PINGRESP:
            	// ping报文响应
                break;
            default:
                break;
        }
    }
}

关于连接报文

客户端到服务端的网络连接建立后,客户端发送给服务端的第一个报文必须是CONNECT报文。
在一个网络连接上,客户端只能发送一次CONNECT报文。服务端必须将客户端发送的第二个CONNECT报文当作协议违规处理并断开客户端的连接 。
报文包含固定报头、可变报头、有效载荷三部分。有效载荷包含一个或多个编码的字段。包括客户端的唯一标识符,Will主题,Will消息,用户名和密码。除了客户端标识之外,其它的字段都是可选的,基于标志位来决定可变报头中是否需要包含这些字段。
注:完整的报文内容,此处不做赘述,请翻阅mqtt协议书以做了解。

项目源码

netty-mqtt-client

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是使用Netty-Mqtt-Client实现Mqtt客户端发布消息和订阅消息的核心Java代码,带注释说明: ```java import io.netty.buffer.Unpooled; import io.netty.handler.codec.mqtt.*; import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.codec.mqtt.MqttPublishMessage; import io.netty.handler.codec.mqtt.MqttSubscribeMessage; import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage; import io.netty.handler.codec.mqtt.MqttMessageBuilders.*; // 创建一个Mqtt客户端类 public class MqttClient { private final String clientId; // 客户端ID private final String serverHost; // 服务器主机名 private final int serverPort; // 服务器端口号 private final String username; // 用户名 private final String password; // 密码 private final int keepAlive; // 心跳间隔时间 private EventLoopGroup group; // Netty线程组 private MqttClientInitializer initializer; // Netty客户端初始化器 private Channel channel; // Netty通道 // 构造方法,初始化Mqtt客户端配置 public MqttClient(String clientId, String serverHost, int serverPort, String username, String password, int keepAlive) { this.clientId = clientId; this.serverHost = serverHost; this.serverPort = serverPort; this.username = username; this.password = password; this.keepAlive = keepAlive; } // 连接服务器 public void connect() { group = new NioEventLoopGroup(); // 创建Netty线程组 initializer = new MqttClientInitializer(clientId, username, password, keepAlive); // 创建Netty客户端初始化器 Bootstrap bootstrap = new Bootstrap(); // 创建Netty客户端启动器 bootstrap.group(group) .channel(NioSocketChannel.class) .remoteAddress(serverHost, serverPort) .handler(initializer); try { ChannelFuture future = bootstrap.connect().sync(); // 连接服务器,同步等待连接完成 if (future.isSuccess()) { // 连接成功 channel = future.channel(); // 获取Netty通道 } } catch (InterruptedException e) { e.printStackTrace(); } } // 断开连接 public void disconnect() { if (channel != null && channel.isActive()) { channel.close(); // 关闭Netty通道 } if (group != null) { group.shutdownGracefully(); // 关闭Netty线程组 } } // 发布消息 public void publish(String topic, String message, MqttQoS qos) { MqttFixedHeader header = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, false, 0); MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topic, 0); ByteBuf payload = Unpooled.buffer(); payload.writeBytes(message.getBytes()); MqttPublishMessage publishMessage = new MqttPublishMessage(header, variableHeader, payload); channel.writeAndFlush(publishMessage); // 发送Mqtt PUBLISH消息 } // 订阅主题 public void subscribe(String topic, MqttQoS qos) { MqttFixedHeader header = new MqttFixedHeader(MqttMessageType.SUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0); MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(1); MqttTopicSubscription topicSubscription = new MqttTopicSubscription(topic, qos); MqttSubscribePayload payload = new MqttSubscribePayload(Arrays.asList(topicSubscription)); MqttSubscribeMessage subscribeMessage = new MqttSubscribeMessage(header, variableHeader, payload); channel.writeAndFlush(subscribeMessage); // 发送Mqtt SUBSCRIBE消息 } // 取消订阅主题 public void unsubscribe(String topic) { MqttFixedHeader header = new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0); MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(1); MqttUnsubscribePayload payload = new MqttUnsubscribePayload(Arrays.asList(topic)); MqttUnsubscribeMessage unsubscribeMessage = new MqttUnsubscribeMessage(header, variableHeader, payload); channel.writeAndFlush(unsubscribeMessage); // 发送Mqtt UNSUBSCRIBE消息 } } ``` 以上代码中,我们创建了一个MqttClient类,该类通过Netty-Mqtt-Client实现Mqtt客户端发布消息和订阅消息的功能。具体实现细节如下: - connect()方法:连接Mqtt服务器,其中我们通过Netty创建了一个NioEventLoopGroup线程组、一个MqttClientInitializer客户端初始化器和一个Bootstrap客户端启动器,并将它们配置好后发起连接请求; - disconnect()方法:断开Mqtt服务器连接,关闭Netty通道和线程组; - publish()方法:发布Mqtt消息,其中我们使用了MqttFixedHeader、MqttPublishVariableHeader、ByteBuf和MqttPublishMessage等Netty-Mqtt-Client提供的类来构建Mqtt PUBLISH消息,并通过Netty通道将其发送给服务器; - subscribe()方法:订阅Mqtt主题,其中我们使用了MqttFixedHeader、MqttMessageIdVariableHeader、MqttTopicSubscription、MqttSubscribePayload和MqttSubscribeMessage等Netty-Mqtt-Client提供的类来构建Mqtt SUBSCRIBE消息,并通过Netty通道将其发送给服务器; - unsubscribe()方法:取消订阅Mqtt主题,其中我们使用了MqttFixedHeader、MqttMessageIdVariableHeader、MqttUnsubscribePayload和MqttUnsubscribeMessage等Netty-Mqtt-Client提供的类来构建Mqtt UNSUBSCRIBE消息,并通过Netty通道将其发送给服务器。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值