简介
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。
2. 通信方式
MQTT基于客户端-服务器方式,以消息订阅/发布方式传输数据。先说一下几个概念:
MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。 代理Broker :它就是MQTT的主体,实际上就是一个服务器,相当于整个MQTT的信息中转站,将发布者的信息转发给订阅者。在实际应用的过程中一般使用云服务器作为代理Broker. 发布者(Publish):顾名思义就是消息的发布者,它负责把消息通过(Publish)方式发送到Broker服务器上,剩下的它就不管了。 订阅者(Subscribe):订阅者则是消息接收方,当服务器接收到发布者发布的信息后,在服务器中查找如果发现有订阅者订阅了这条信息,那么服务器就把这条信息发送给订阅者。
那么这三者通过什么方式判断消息的区别那?那又引出来下一个概念主题(Topic)和负载(Payload). Topic:可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload) payload:可以理解为消息的内容,是指订阅者具体要使用的内容。
通俗上讲就是 :发布者和订阅者约定好一个标题(主题topic)并且把这个标题存储在服务器上,发布者把消息内容(负载(Payload))发布到服务器这个标题下面,订阅者从服务器订阅这个标题。 当服务器发现这个标题下面有内容了就开始查找谁订阅这个标题,查找到了之后就把消息内容发送给订阅者 。
esp-idf 中有专门介绍MQTT的相关例程,例程位于esp-idf/examples/protocols/mqtt中,乐鑫提供了多个例程
我们选择tcp例程讲解,其他例程原理上是一样,例程中连接的服务器是eclipse 专门提供的MQTT的免费服务器,用户可以使用这个服务器进行测试。
1. 主函数
void app_main()
{
/*输出log 相关配置 我们不需要关心*/
ESP_LOGI(TAG, "[APP] Startup..");
/*获取空闲内存大小*/
ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size());
/*打印当前idf的版本*/
ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
/*配置打印信息*/
esp_log_level_set("*", ESP_LOG_INFO);
esp_log_level_set("MQTT_CLIENT", ESP_LOG_VERBOSE);
esp_log_level_set("TRANSPORT_TCP", ESP_LOG_VERBOSE);
esp_log_level_set("TRANSPORT_SSL", ESP_LOG_VERBOSE);
esp_log_level_set("TRANSPORT", ESP_LOG_VERBOSE);
esp_log_level_set("OUTBOX", ESP_LOG_VERBOSE);
/*flash 初始化,tcp/ip通信时 需要使用flash*/
nvs_flash_init();
/*wifi初始化*/
wifi_init();
/*mqtt开始运行*/
mqtt_app_start();
}
2. wifi函数注释
static void wifi_init(void)
{
tcpip_adapter_init();//tcpip 协议栈初始化,使用网络时必须调用此函数
/*创建一个freeRTOS的事件标志组,用于当wifi没有连接时将程序停下,只有wifi连接成功了才能继续运行程序*/
wifi_event_group = xEventGroupCreate();
/*配置 wifi的回调函数,用于连接wifi*/
/*
* ESP_ERROR_CHECK检查函数返回值
*/
ESP_ERROR_CHECK(esp_event_loop_init(wifi_event_handler, NULL));
/*wifi配置*/
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
wifi_config_t wifi_config = {
.sta = {
.ssid = CONFIG_WIFI_SSID,
.password = CONFIG_WIFI_PASSWORD,
},
};
/*设置wifi 为sta模式*/
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
ESP_LOGI(TAG, "start the WIFI SSID:[%s]", CONFIG_WIFI_SSID);
/*开始运行wifi*/
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "Waiting for wifi");
/*等待事件标志,成功获取到事件标志位后才继续执行,否则一直等在这里*/
xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY);
}
/*wifi 的中断回调函数,检测wifi的事件标志位*/
static esp_err_t wifi_event_handler(void *ctx, system_event_t *event)
{
switch (event->event_id) {
case SYSTEM_EVENT_STA_START://开始执行station
esp_wifi_connect();//根据wifi配置,连接wifi
break;
case SYSTEM_EVENT_STA_GOT_IP://成功获取到ip,表示联网成功
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);//设置事件标志位,程序继续运行
break;
case SYSTEM_EVENT_STA_DISCONNECTED://station 已经断开了,重新连接wifi
esp_wifi_connect();
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
break;
default:
break;
}
return ESP_OK;
}
3. mqtt函数注释
static void mqtt_app_start(void)
{
/*配置mqtt ,broker 地址 和mqtt的事件回调函数*/
esp_mqtt_client_config_t mqtt_cfg = {
.uri = CONFIG_BROKER_URL,
.event_handle = mqtt_event_handler,
// .user_context = (void *)your_context
};
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);//出事mqtt的相关配置
esp_mqtt_client_start(client);//开始执行mqtt
}
static esp_err_t mqtt_event_handler(esp_mqtt_event_handle_t event)
{
esp_mqtt_client_handle_t client = event->client;
int msg_id;
// your_context_t *context = event->context;
switch (event->event_id) {
case MQTT_EVENT_CONNECTED://MQTT 已连接
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
/*发布主题/topic/qos1*/
msg_id = esp_mqtt_client_publish(client, "/topic/qos1", "data_3", 0, 1, 0);
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
/*订阅主题/topic/qos0 */
msg_id = esp_mqtt_client_subscribe(client, "/topic/qos0", 0);
ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
/*订阅主题/topic/qos1*/
msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1);
ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
/*取消订阅/topic/qos1*/
msg_id = esp_mqtt_client_unsubscribe(client, "/topic/qos1");
ESP_LOGI(TAG, "sent unsubscribe successful, msg_id=%d", msg_id);
break;
case MQTT_EVENT_DISCONNECTED://MQTT断开连接
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
break;
case MQTT_EVENT_SUBSCRIBED://MQTT收到订阅信息
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "data", 0, 0, 0);
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
break;
case MQTT_EVENT_UNSUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_PUBLISHED:
ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_DATA://
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);//主题名称
printf("DATA=%.*s\r\n", event->data_len, event->data);//负载信息
break;
case MQTT_EVENT_ERROR:
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
break;
default:
ESP_LOGI(TAG, "Other event id:%d", event->event_id);
break;
}
return ESP_OK;
}
写在最后
至此 MQTT的简单流程就介绍完了,MQTT之所以在物联网上应用比较广泛就是因为它的简单,易用在加上它数据精简正好适合物联网通信的要求。
现在国内外的一线大厂阿里,华为,百度 ,亚马逊 的服务器都支持MQTT协议,如果想在实际的应用使用MQTT的话,主要是研究各个厂家服务器上的其他设置,比如连接方式,检验机制,如何配置选项等,MQTT本身这一块没有什么东西。
希望此文对大家能有所帮助。
欢迎关注我的个人网站:zwww.zcxbb.com
知乎专栏:物联网开发入门 - 知乎 (zhihu.com)