MQTT协议在物联网中的应用

MQTT协议在物联网方面的应用

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分。该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和制动器(比如通过Twitter让房屋联网)的通信协议。

物联网(Internet of Things,IoT)最近曝光率越来越高。虽然HTTP是网页的事实标准,不过机器之间(Machine-to-Machine,M2M)的大规模沟通需要不同的模式:之前的请求/回答(Request/Response)模式不再合适,取而代之的是发布/订阅(Publish/Subscribe)模式。这就是轻量级、可扩展的MQTT(Message Queuing Telemetry Transport)可以施展拳脚的舞台。

MQTT应用领域

MQTT是基于二进制消息的发布/订阅编程模式的消息协议,最早由IBM提出的,如今已经成为OASIS规范。由于规范很简单,非常适合需要低功耗和网络带宽有限的IoT场景,比如:

遥感数据
汽车
智能家居
智慧城市
医疗医护

由于物联网的环境是非常特别的,所以MQTT遵循以下设计原则:

精简,不添加可有可无的功能。
发布/订阅(Pub/Sub)模式,方便消息在传感器之间传递。
允许用户动态创建主题,零运维成本。
把传输量降到最低以提高传输效率。
把低带宽、高延迟、不稳定的网络等因素考虑在内。
支持连续的会话控制。
理解客户端计算能力可能很低。
提供服务质量管理。
假设数据不可知,不强求传输数据的类型与格式,保持灵活性

服务质量

为了满足不同的场景,MQTT支持三种不同级别的服务质量(Quality of Service,QoS)为不同场景提供消息可靠性:

级别0:尽力而为。消息发送者会想尽办法发送消息,但是遇到意外并不会重试。
级别1:至少一次。消息接收者如果没有知会或者知会本身丢失,消息发送者会再次发送以保证消息接收者至少会收到一次,当然可能造成重复消息。
级别2:恰好一次。保证这种语义肯待会减少并发或者增加延时,不过丢失或者重复消息是不可接受的时候,级别2是最合适的。
服务质量是个老话题了。级别2所提供的不重不丢很多情况下是最理想的,不过往返多次的确认一定对并发和延迟带来影响。级别1提供的至少一次语义在日志处理这种场景下是完全OK的,所以像Kafka这类的系统利用这一特点减少确认从而大大提高了并发。级别0适合鸡肋数据场景,食之无味弃之可惜,就这么着吧。

消息类型

MQTT拥有14种不同的消息类型:

CONNECT:客户端连接到MQTT代理
CONNACK:连接确认
PUBLISH:新发布消息
PUBACK:新发布消息确认,是QoS 1给PUBLISH消息的回复
PUBREC:QoS 2消息流的第一部分,表示消息发布已记录
PUBREL:QoS 2消息流的第二部分,表示消息发布已释放
PUBCOMP:QoS 2消息流的第三部分,表示消息发布完成
SUBSCRIBE:客户端订阅某个主题
SUBACK:对于SUBSCRIBE消息的确认
UNSUBSCRIBE:客户端终止订阅的消息
UNSUBACK:对于UNSUBSCRIBE消息的确认
PINGREQ:心跳
PINGRESP:确认心跳
DISCONNECT:客户端终止连接前优雅地通知MQTT代理

具体的协议详解可移步到https://www.cnblogs.com/caca/p/mqtt.html

由于小编是android开发工程师,下面的例子就以android代码为例:

首先我们要搭建一个服务器

Apollo 安装配置

第一步下载安装

本地电脑Windows7 64位
下载地址:http://activemq.apache.org/apollo/download.html
下载Apollo服务器并解压,在CMD环境运行其工作目录下的 bin\apollo.cmd,命令后面带上参数「create mybroker」,创建服务器实例。这里需要Java环境,系统环境变量下要有JAVA_HOME。
创建实例之后会在bin目录下生成mybroker文件夹,其中 etc\apollo.xml 文件下是配置服务器信息的文件,
etc\users.properties 文件包含连接MQTT服务器时用到的用户名和密码,初始默认帐号是admin,密码password;
注意这里要设置APOLLO_HOME
这里写图片描述
启动服务:
在安装目录下执行: mybroker\bin\apollo-broker run
会出现如下界面:
这里写图片描述
其中我们要留意的:
MQTT服务器TCP连接端口:tcp://0.0.0.0:61613
后台登录接口:https://127.0.0.1:61681/http://127.0.0.1:61680/
进入后台管理界面:
登录服务器后,如果MQTT服务器有客户端连接,后台会显示如下

这里写图片描述
下面就写服务端的代码:首先要明确的是服务端是相对的,一个设备即是服务端也可能是客户端!

这里应该说是。发布/订阅的关系,一个硬件设备既要能发送消息,也能接收消息,这样才能对硬件的数据进行监控和远程操控

下面是小编整理的android端的发送和接受代码:

首先需要添加依赖包:

compile files(‘libs/org.eclipse.paho.client.mqttv3-1.0.1.jar’)
compile ‘org.eclipse.paho:org.eclipse.paho.android.service:1.1.0’
发送消息端(发布)的代码

>import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
/**
 * Created by Administrator on 2017/11/8/008.
 */
public class Serv {
    private static final long serialVersionUID = 1L;
    private MqttClient client;
    private String host = "tcp://192.168.1.5:61613";
    //  private String host = "tcp://localhost:1883";
    private String userName = "admin";
    private String passWord = "password";
    private MqttTopic topic;
    private MqttMessage message;
    private String myTopic = "test/topic";
    private String mes="123456";
    public static Serv serv;
    public static Serv getInstance(String clind){
        if (serv==null){
            serv=new Serv(clind);
        }
        return serv;
    }
    private Serv(String clindeId) {
        try {
            client = new MqttClient(host, clindeId,
                    new MemoryPersistence());
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }
    public void  open(Context context){
        if (client!=null){
            connect();
            Toast.makeText(context,"服务器打开",Toast.LENGTH_SHORT).show();
        }
    }
    public boolean isOpen(){
        boolean connected=false;
        if (client!=null){
             connected= client.isConnected();
        }
        return connected;
    }
    public void close(Context context){
        if (client.isConnected()){
            try {
                client.disconnect();
                serv=null;
                Toast.makeText(context,"服务器关闭连接",Toast.LENGTH_SHORT).show();
            } catch (MqttException e) {
                e.printStackTrace();
            }
        }
    }
    public void setMessage(String s){
        this.mes=s;
        MqttDeliveryToken token = null;
        try {
            message = new MqttMessage();
            message.setQos(1);
            message.setRetained(true);
            System.out.println(message.isRetained()+"------ratained状态");
            message.setPayload(mes.getBytes());
            token = topic.publish(message);
            token.waitForCompletion();
            System.out.println(token.isComplete()+"========");
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }
    private void connect() {
        MqttConnectOptions options = new MqttConnectOptions();
        options.setCleanSession(false);
        options.setUserName(userName);
        options.setPassword(passWord.toCharArray());
        // 设置超时时间
        options.setConnectionTimeout(10);
        // 设置会话心跳时间
        options.setKeepAliveInterval(20);
        try {
            client.setCallback(new MqttCallback() {
                @Override
                public void connectionLost(Throwable cause) {
                    System.out.println("connectionLost-----------");
                }
                @Override
                public void deliveryComplete(IMqttDeliveryToken token) {
                    System.out.println("deliveryComplete---------"+token.isComplete());
                }
                @Override
                public void messageArrived(String topic, MqttMessage arg1)
                        throws Exception {
                    System.out.println("messageArrived----------");
                }
            });
     topic = client.getTopic(myTopic);
            client.connect(options);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }}

2.订阅消息的代码`

public class ClindeActivity extends AppCompatActivity {
    private TextView resultTv;
    private String host = "tcp://192.168.1.5:61613";
    private String userName = "admin";
    private String passWord = "password";

    private Handler handler;

    private MqttClient client;

    private String myTopic = "test/topic";

    private MqttConnectOptions options;

    private ScheduledExecutorService scheduler;
    private Button viewById;
    EditText post;
    Button Link;
     Serv serv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        resultTv = (TextView) findViewById(R.id.tv);
        post= (EditText) findViewById(R.id.post);
        viewById = (Button) findViewById(R.id.sendMessage);
        Link= (Button) findViewById(R.id.Link);
        Link.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
               serv=Serv.getInstance(post.getText().toString().trim());
               if (serv.isOpen()){
                   serv.close(getApplicationContext());
                   resultTv.setText("服务器停止");
               }else {
                   serv.open(getApplicationContext());
                   resultTv.setText("服务器工作中");
               }
            }
        });
        final EditText et= (EditText) findViewById(R.id.edit);
        this.viewById.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String s=et.getText().toString();
                if (serv==null){
                    Toast.makeText(getApplicationContext(),"请先打开服务端",Toast.LENGTH_SHORT).show();
                    return;
                }else {
                    serv.setMessage(s);
                }
            }
        });
        init();
        handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if(msg.what == 1) {
                    Toast.makeText(ClindeActivity.this, (String) msg.obj,
                            Toast.LENGTH_SHORT).show();
                    System.out.println("-----------------------------");
                } else if(msg.what == 2) {
                    Toast.makeText(ClindeActivity.this, "连接成功", Toast.LENGTH_SHORT).show();
                    try {
                        client.subscribe(myTopic, 1);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } else if(msg.what == 3) {
                    Toast.makeText(ClindeActivity.this, "连接失败,系统正在重连", Toast.LENGTH_SHORT).show();
                }
            }
        };
        startReconnect();
    }

    private void startReconnect() {
        scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(new Runnable() {

            @Override
            public void run() {
                if(!client.isConnected()) {
                    connect();
                }
            }
        }, 0 * 1000, 10 * 1000, TimeUnit.MILLISECONDS);
    }

    private void init() {
        try {
            //host为主机名,test为clientid即连接MQTT的客户端ID,一般以客户端唯一标识符表示,MemoryPersistence设置clientid的保存形式,默认为以内存保存
            client = new MqttClient(host, "123456",
                    new MemoryPersistence());
            //MQTT的连接设置
            options = new MqttConnectOptions();
            //设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接
            options.setCleanSession(true);
            //设置连接的用户名
            options.setUserName(userName);
            //设置连接的密码
            options.setPassword(passWord.toCharArray());
            // 设置超时时间 单位为秒
            options.setConnectionTimeout(10);
            // 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
            options.setKeepAliveInterval(20);
            //设置回调
            client.setCallback(new MqttCallback() {
                @Override
                public void connectionLost(Throwable cause) {
                    //连接丢失后,一般在这里面进行重连
                    System.out.println("connectionLost----------");
                    resultTv.setText("服务器来链接中断");
                }
                @Override
                public void deliveryComplete(IMqttDeliveryToken token) {
                    //publish后会执行到这里
                    System.out.println("deliveryComplete---------"
                            + token.isComplete());
                }
                @Override
                public void messageArrived(String topicName, MqttMessage message)
                        throws Exception {
                    //subscribe后得到的消息会执行到这里面
                    System.out.println("messageArrived----------");
                    Message msg = new Message();
                    msg.what = 1;
                    msg.obj = topicName+"---"+message.toString();
                    handler.sendMessage(msg);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void connect() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    client.connect(options);
                    Message msg = new Message();
                    msg.what = 2;
                    handler.sendMessage(msg);
                } catch (Exception e) {
                    e.printStackTrace();
                    Message msg = new Message();
                    msg.what = 3;
                    handler.sendMessage(msg);
                }
            }
        }).start();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if(client != null && keyCode == KeyEvent.KEYCODE_BACK) {
            try {
                client.disconnect();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        try {
            scheduler.shutdown();
            client.disconnect();
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }`

这里写图片描述

  • 4
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值