MQTT源码分析

目录

1、MQTT客户端功能

2、客户端软件实现过程

3、程序分层

4、情景分析

4.1 连接服务器

4.2 创建线程

4.3 发布消息

4.4 订阅消息

1、调用订阅消息函数

2、创建核心线程

3、处理数据

4、流程梳理

附录


1、MQTT客户端功能

以"记者-电视台-观众"的模式来理解,客户端具体的流程是这样的:

  • 客户端1:观众打电话到电视台:connect
  • 客户端1:观众向电视台订阅"财经新闻": Subscribe 某个 Topic
  • 客户端2:记者打电话到电视台:connect
  • 客户端2:记者向电视台发布"财经新闻":Public某个Topic的某个Playload
  • 服务器:电视台向"订阅了财经新闻的观众"发布"某条消息":Public某个Playload给Subscriber

整个过程中,电视台和记者、电视台和观众直接的电话要保存连接状态,还要时不时确认一下:

  • 记者要时不时给电视台喊一声"喂":确保电视台还正常
  • 观众要时不时给电视台喊一声"喂":确保电视台还正常

2、客户端软件实现过程

  • 连接服务器
  • 订阅:
    • 发布订阅请求,等待回应
    • 循环:读取Publish信息(得到订阅的信息),处理
  • 发布:
    • 发送数据包即可
  • PING
    • 循环:确保自己、对方还活着
    • mqtt_packet_handle > mqtt_keep_alive

需要一个循环!

3、程序分层

至少可以分为3层:

  • 最上层:APP
  • 中间层:MQTT
  • 平台层:实现多线程、定时器、网卡收发数据

4、情景分析

4.1 连接服务器

函数调用过程:

main
    client = mqtt_lease();
    mqtt_set_port(client, "1883");
    mqtt_set_host(client, "www.jiejie01.top");

	mqtt_connect(client);
		mqtt_connect_with_results(c);
		    rc = network_init(c->mqtt_network, c->mqtt_host, c->mqtt_port, NULL);
		    rc = network_connect(c->mqtt_network);
					nettype_tcp_connect(n);	
						platform_net_socket_connect//比如用WiFi模块 就需要提供此函数 用此函数通过串口发送AT指令给WiFi模块 才能成功将数据发送出去

4.2 创建线程

函数调用过程:

main
	mqtt_connect(client);
		mqtt_connect_with_results(c);
		    rc = network_init(c->mqtt_network, c->mqtt_host, c->mqtt_port, NULL);
		    rc = network_connect(c->mqtt_network);

            /* send connect packet */
            if ((rc = mqtt_send_packet(c, len, &connect_timer)) != MQTT_SUCCESS_ERROR)
                goto exit;

		    if (mqtt_wait_packet(c, CONNACK, &connect_timer) == CONNACK) {
            }

            /* connect success, and need init mqtt thread */
            c->mqtt_thread= platform_thread_init("mqtt_yield_thread", mqtt_yield_thread,c, ...);

图示解析如下:

4.3 发布消息

函数调用过程:

main
    res = pthread_create(&thread1, NULL, mqtt_publish_thread, client);
				mqtt_publish_thread
                    mqtt_publish(client, "topic1", &msg);

// 1. 构造消息
mqtt_message_t msg;

memset(&msg, 0, sizeof(msg));
msg.payload = (void *) buf;
msg.payloadlen = xxx;

mqtt_publish(client, "topic1", &msg);
	// 1.1 根据MQTT协议构造数据包

    // 1.2 根据平台相关的函数发送数据包(从最外层函数依次进到最底层)
    mqtt_send_packet
        network_write
        	nettype_tcp_write
        		platform_net_socket_write_timeout

4.4 订阅消息

消息何时到来?不知道!

所以,必定是某个内核线程不断查询网卡:

  • 读网卡数据

    • 得到数据的话就判断、处理

1、调用订阅消息函数
mqtt_subscribe(client, "topic1", QOS0, topic1_handler);

订阅消息:调用此函数 就可以去订阅某个消息 当接收到该主题的消息时 第四个参数的函数就会被调用

源代码解析如下: 

int mqtt_subscribe(mqtt_client_t* c, const char* topic_filter, mqtt_qos_t qos, message_handler_t handler)
{
    int rc = MQTT_SUBSCRIBE_ERROR;
    int len = 0;
    uint16_t packet_id;
    platform_timer_t timer;
    MQTTString topic = MQTTString_initializer;
    topic.cstring = (char *)topic_filter;
    message_handlers_t *msg_handler = NULL;//定义一个消息处理的结构体

    if (CLIENT_STATE_CONNECTED != mqtt_get_client_state(c))
        RETURN_ERROR(MQTT_NOT_CONNECT_ERROR);
    
    packet_id = mqtt_get_next_packet_id(c);
    
    platform_mutex_lock(&c->mqtt_write_lock);

    /* serialize subscribe packet and send it */
    len = MQTTSerialize_subscribe(c->mqtt_write_buf, c->mqtt_write_buf_size, 0, packet_id, 1, &topic, (int*)&qos);
    if (len <= 0)
        goto exit;
    
    if ((rc = mqtt_send_packet(c, len, &timer)) != MQTT_SUCCESS_ERROR)
        goto exit; 

    if (NULL == handler)
        handler = default_msg_handler;  /* if handler is not specified, the default handler is used */

    /* create a message and record it */
    msg_handler = mqtt_msg_handler_create(topic_filter, qos, handler);
    if (NULL == msg_handler) {
        rc = MQTT_MEM_NOT_ENOUGH_ERROR;
        goto exit;
    }

    rc = mqtt_ack_list_record(c, SUBACK, packet_id, len, msg_handler);//创建完毕的handler记录下来并放入一个链表中 

exit:

    platform_mutex_unlock(&c->mqtt_write_lock);

    RETURN_ERROR(rc);
}

消息处理结构体定义如下:

typedef struct mgtt message {
    mqtt_qos_t        qos;
    uint8_t           retained;
    uint8 t           dup;
    uint16_t          id;
    size_t            payloadlen;
    void              *payload;
}mgtt message t;

handler创建函数如下:

//handler创建函数
static message_handlers_t *mqtt_msg_handler_create(const char* topic_filter, mqtt_qos_t qos, message_handler_t handler)
{
    message_handlers_t *msg_handler = NULL;

    msg_handler = (message_handlers_t *) platform_memory_alloc(sizeof(message_handlers_t));//分配内存
    if (NULL == msg_handler)
        return NULL;
    
    mqtt_list_init(&msg_handler->list);
    
    msg_handler->qos = qos;//记录服务质量
    msg_handler->handler = handler;//记录处理函数     /* register  callback handler */ 
    msg_handler->topic_filter = topic_filter;//记录收到的主题

    return msg_handler;
}

第一步:创建一个消息处理的结构体 第二步:将收到的数据放入链表中 将链表中的数据与消息处理结构体进行逐一对比 如果主题匹配 则调用其中的处理函数

2、创建核心线程
//核心线程代码如下(主要看while循环那部分):
static void mqtt_yield_thread(void *arg)
{
    int rc;
    client_state_t state;
    mqtt_client_t *c = (mqtt_client_t *)arg;
    platform_thread_t *thread_to_be_destoried = NULL;
    
    state = mqtt_get_client_state(c);
        if (CLIENT_STATE_CONNECTED !=  state) {
            MQTT_LOG_W("%s:%d %s()..., mqtt is not connected to the server...",  __FILE__, __LINE__, __FUNCTION__);
            platform_thread_stop(c->mqtt_thread);    /* mqtt is not connected to the server, stop thread */
    }

    while (1) {
        rc = mqtt_yield(c, c->mqtt_cmd_timeout);
        if (MQTT_CLEAN_SESSION_ERROR == rc) {
            MQTT_LOG_W("%s:%d %s()..., mqtt clean session....", __FILE__, __LINE__, __FUNCTION__);
            network_disconnect(c->mqtt_network);
            mqtt_clean_session(c);
            goto exit;
        } else if (MQTT_RECONNECT_TIMEOUT_ERROR == rc) {
            MQTT_LOG_W("%s:%d %s()..., mqtt reconnect timeout....", __FILE__, __LINE__, __FUNCTION__);
        }
    }
    
exit:
    thread_to_be_destoried = c->mqtt_thread;
    c->mqtt_thread = (platform_thread_t *)0;
    platform_thread_destroy(thread_to_be_destoried);
}

那么该函数的执行过程如下:有一个线程 该线程主要执行一个死循环 死循环中调用读消息相关函数去读网络数据 如果读到的网络数据是一个主题的消息(Publish发过来的消息) 那么就会去分辨主题Topic 再调用对应函数。

该函数会做如下事情:1、读数据(Read Packet)并处理数据  2、若一直没读取到数据,则会时不时发送Ping给服务器,保持心跳  3、处理各种错误

3、处理数据
//得到数据后的处理
static int mqtt_packet_handle(mqtt_client_t* c, platform_timer_t* timer)
{
    int rc = MQTT_SUCCESS_ERROR;
    int packet_type = 0;
    
    rc = mqtt_read_packet(c, &packet_type, timer);//读取数据

    switch (packet_type) {
        case 0: /* timed out reading packet or an error occurred while reading data*/
            if (MQTT_BUFFER_TOO_SHORT_ERROR == rc) {
                MQTT_LOG_E("the client read buffer is too short, please call mqtt_set_read_buf_size() to reset the buffer size");
                /* don't return directly, you need to stay active, because there is data readable now, but the buffer is too small */
            }
            break;

        case CONNACK: /* has been processed */
            goto exit;

        case PUBACK:
        case PUBCOMP:
            rc = mqtt_puback_and_pubcomp_packet_handle(c, timer);
            break;

        case SUBACK:
            rc = mqtt_suback_packet_handle(c, timer);
            break;
            
        case UNSUBACK:
            rc = mqtt_unsuback_packet_handle(c, timer);
            break;

        case PUBLISH:
            rc = mqtt_publish_packet_handle(c, timer);
            break;

        case PUBREC:
        case PUBREL:
            rc = mqtt_pubrec_and_pubrel_packet_handle(c, timer);
            break;

        case PINGRESP:
            c->mqtt_ping_outstanding = 0;    /* keep alive ping success */
            break;

        default:
            break;
    }

    rc = mqtt_keep_alive(c);//发送心跳包

exit:
    if (rc == MQTT_SUCCESS_ERROR)//处理各种错误
        rc = packet_type;

    RETURN_ERROR(rc);
}
4、流程梳理

1、订阅消息:对什么主题感兴趣,收到该消息之后要调用处理函数进行相应操作

mqtt_subscribe(client, "topic1", QOS0, topic1_handler);

2、将收到的消息放入链表中记录并对比

rc = mqtt_ack_list_record(c, SUBACK, packet_id, len, msg_handler);//创建完毕的handler记录下来并放入一个链表中

3、创建一个线程

static void mqtt_yield_thread(void *arg);

4、在线程中创建循环并处理数据包

rc = mqtt_yield(c, c->mqtt_cmd_timeout);//存在于while循环中
rc = mqtt_packet_handle(c, &timer);//存在于mqtt_yield函数内部

5、读取数据包

rc = mqtt_read_packet(c, &packet_type, timer);//存在于mqtt_packet_handle函数内部

6、根据数据包类型处理数据

这里用Publsh数据类型处理数据做演示

        case PUBLISH:
            rc = mqtt_publish_packet_handle(c, timer);//存在于mqtt_read_packet函数内部
            break;

7、将数据传递出去

mqtt_deliver_message(c, &topic_name, &msg);//存在于mqtt_publish_packet_handle函数内部

8、根据主题找到之前的handler处理函数

msg_handler = mqtt_get_msg_handler(c, topic_name);//存在于mqtt_deliver_message函数内部
msg_handler->handler(c, &md); 

附录

分析源码:mqttclient\test\emqx\test.c

参考资料:

  • 18
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java MQTT 是一个 MQTT(Message Queue Telemetry Transport)协议的 Java 客户端实现。MqttClient 类是 Java MQTT 提供的核心类,用于与 MQTT 服务器建立连接并进行通信。 Java MQTT 源码主要包含以下几个部分: 1. MqttClient 类:实现了 MQTT 客户端的基本功能,可以与 MQTT 代理服务器建立连接,并提供订阅、发布消息、断开连接等操作。在 MqttClient 类中,通过 Socket 连接到 MQTT 服务器,并使用协议规定的固定报头和可变报头等进行通信。 2. MqttConnectOptions 类:用于设置与 MQTT 服务器建立连接的选项,包括客户端 ID、用户名、密码、遗嘱消息、保持活动时间间隔等。MqttConnectOptions 类可以用来定制 MQTT 客户端的连接行为。 3. MqttCallback 接口:用于接收 MQTT 客户端的回调通知,包括连接成功、连接失败、消息到达等事件。开发者可以实现 MqttCallback 接口,根据自己的业务逻辑处理接收到的 MQTT 消息。 4. MqttMessage 类:表示 MQTT 消息的内容和属性,包括负载(payload)、服务质量(QoS)、是否保留等。在使用 Java MQTT 发布和订阅消息时,可以通过 MqttMessage 类封装消息的相关信息。 5. MqttTopic 类:提供了订阅和取消订阅 MQTT 主题的方法,可以用来控制订阅和取消订阅操作。 Java MQTT 源码的设计目标是提供一个高效、易于使用的 MQTT 客户端实现,使 Java 开发者能够简单地与 MQTT 服务器进行通信。通过阅读源码,开发者可以了解其内部实现原理,以及如何使用 Java MQTT 构建 MQTT 应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值