文章目录
前言
mosquitto_main_loop
函数是 Mosquitto MQTT 代理服务器的核心函数之一,它用于监听来自客户端的消息,并将消息分发给订阅者。这个函数实现了 MQTT 协议的所有功能,包括消息发布和订阅、QoS质量等级、保留消息、持久化订阅、消息存储、安全认证等。可以说mosquitto_main_loop
函数支撑起了客户端从连接到销毁的整个生命周期的活动。对于开发人员而言,了解这个函数的源代码和原理是非常有帮助的。
本文基于mosquitto2.0.15源码,详细分析mosquitto服务器主循环函数mosquitto_main_loop
与客户端交互的主要过程。
为了简化问题,我们分析时编译选项选择或设置文件参数设置基于下面的前提:
服务器运行在linux操作系统
;
采用支持更多客户端并发连接的epool机制
;
通信协议只支持MQTT协议
;
不包含SSL/TLS 安全认证支持
;
不支持服务器桥接
。
一、函数功能概述
mosquitto_main_loop
函数是mosquitto2.0.15 MQTT代理服务器的主循环函数,它用于监听来自客户端的消息,并将消息分发给订阅者。
整个程序的入口函数是"main"函数,存放在src/mosquitto.c文件中,程序首先对命令行参数进行解析,然后进行MQTT代理的初始化和配置,包括权限控制、数据库的打开、安全模块的初始化等。接着程序调用mosquitto_main_loop
函数进入主循环,函数定义如下:
int mosquitto_main_loop(struct mosquitto__listener_sock *listensock, int listensock_count)
函数的参数 listensock
是指向一个监听套接字结构体的指针,listensock_count
是监听套接字的数量。函数的主要功能是执行以下操作:
-
从监听套接字接收网络连接,并将连接分配给新的客户端。
-
从已连接的客户端读取MQTT消息。
-
对读取到的MQTT消息进行处理,包括解析消息头、分发消息、处理ACK等。
-
将需要发送的消息发送给对应的客户端。
-
处理客户端的
PINGREQ
和PINGRESP
消息,保持心跳连接。 -
处理客户端的
DISCONNECT
消息,关闭连接并从客户端列表中删除。 -
处理客户端的
WILL
消息,将遗嘱消息发送给订阅者。 -
处理客户端的
SUBSCRIBE
和UNSUBSCRIBE
消息,更新客户端的订阅信息。 -
处理客户端的
CONNECT
消息,进行身份验证和会话管理。 -
处理客户端的
PUBLISH
消息,将消息发送给订阅者。 -
处理客户端的
RESUME
消息,恢复会话并发送缓存的消息。 -
处理客户端的
AUTH
消息,进行身份验证。 -
处理客户端的
GET
或PUT
消息,用于管理持久化订阅和消息存储。 -
处理客户端的
PUBREL
消息,确认QoS2
的消息。 -
处理客户端的
ACK
消息,确认QoS1
的消息。
函数会一直循环执行上述操作,直到程序接收到终止信号才会退出循环。如果在循环中出现错误,函数会返回错误码,并将错误信息输出到日志中。
二、与客户端交互主要流程
1.监听
...
int main(int argc, char *argv[])
{
...
if(listeners__start()) return 1;
rc = mux__init(listensock, listensock_count);
if(rc) return rc;
...
run = 1;
rc = mosquitto_main_loop(listensock, listensock_count);
...
}
listensock_count
表示监听套接字的数量,数值在服务器启动前就确定了。通常情况下,由配置文件中监听端口的个数决定(严格的说,这个数量只包含监听打开成功的套接字数量),配置文件默认名称 mosquitto.conf
,文件位于 /etc/mosquitto/
目录下。
监听程序开始在进入主循环之前。绑定监听后,调用mux__init函数,实际上是在调用 mux_epoll__init
函数,函数接收两个参数:
listensock
是一个指向struct mosquitto__listener_sock
类型的指针,它指向一个存储有多个监听器信息的数组,每个元素都描述了一个监听器的 IP 地址、端口号等信息。listensock_count
是一个整数,表示listensock
数组中包含的监听器数量。
这个函数的功能是初始化一个 epoll
实例,并将所有监听器套接字添加到该实例中。这样,就可以在主循环中监听和处理来自客户端各种请求,同时,函数里面还初始化了一个存放事件struct epoll_event结构体数组,将数组每个成员的ptr指针关联到需要监听的struct mosquitto__listener_sock 结构对象,同时将这个对象的ident成员初始化为监听器–id_listener
。
源码中枚举定义了一个名为
struct_ident
的枚举类型,它包含四个枚举常量:
id_invalid
,值为 0,表示标识无效。id_listener
,值为 1,表示标识是监听器(listener)。id_client
,值为 2,表示标识是客户端(client)。id_listener_ws
,值为 3,表示标识是 WebSocket 监听器。这个枚举的作用是为 Mosquitto MQTT 代理服务器内部的各个对象和实体分配唯一的标识符,以便在代码中进行区分和操作。其中
id_invalid
可以用于表示一个未知的或未定义的标识符,而其他三个常量则分别表示 Mosquitto
服务器中三种不同类型的对象(监听器、客户端和 WebSocket 监听器)的标识符。
2.等待客户端事件
...
int mosquitto_main_loop(struct mosquitto__listener_sock *listensock, int listensock_count)
{
...
while (run)
{
...
rc = mux__handle(listensock, listensock_count);
if (rc)
return rc;
...
}
...
}
mosquitto_main_loop
函数核心是使用一个无限循环来处理客户端的连接,循环中调用了而mux__handle函数,而mux__handle又调用了mux_epoll__handle。mux_epoll__handle函数中调用 epoll_wait
函数等待事件发生。如果有事件发生,则对事件进行处理,如下所示。
...<