文章目录
前言
Mosquitto 客户端的 socket 网络程序与 MQTT 协议紧密集成,实现了 MQTT 协议的各种控制报文的发送和接收,以及 QoS 等级、会话状态等功能。
Mosquitto 客户端在进行网络程序设计时有许多值得借鉴的技巧,比如发送队列、socketpair 等。以下是一些值得借鉴的关键技巧:
- 发送队列:Mosquitto 客户端使用发送队列(基于链表的数据结构)来存储待发送的 MQTT 控制报文。这种设计可以确保发送操作的高效执行,同时也方便对发送数据进行管理和调度。
- socketpair:Mosquitto 客户端使用 socketpair(一对互相连接的 socket)在主线程和子线程之间传递信息。这种设计允许子线程在需要唤醒主线程时向 sockpairW 写入数据,而主线程则在 sockpairR 上监听。当主线程接收到数据时,它将处理发送队列中的消息包并将其发送到服务器。
- I/O 多路复用:Mosquitto 客户端使用 select 或 pselect 进行 I/O 多路复用,能够同时监控多个文件描述符的状态,提高程序执行效率。
在本文中,我们详细探讨了 Mosquitto 客户端 Socket 通信的设计理念,旨在为阅读源码的读者提供一些启示和参考。通过深入了解 Mosquitto 客户端的实现细节,我们希望能帮助大家更好地理解 MQTT 协议的运作原理,以及如何在实际项目中应用这些设计思想。
1.发送队列的使用
1.1 struct mosquitto__packet
结构体链表
struct mosquitto__packet
的主要字段包括:
next:指向链表中下一个 mosquitto__packet 的指针。
command:表示 MQTT 控制报文的类型,如 CONNECT、PUBLISH、SUBSCRIBE 等。
remaining_count 和 remaining_mult:表示剩余长度字段的解码相关信息。
remaining_length:表示 MQTT 控制报文的剩余长度。
packet_length:表示 MQTT 控制报文的总长度。
payload:表示 MQTT 控制报文的有效载荷(这里是需要发送的数据报文)。
to_process 和 pos:表示已处理和待处理的字节数。
发送队列是由 struct mosquitto__packet 结构组成的链表。每个 mosquitto__packet 结构代表一个待发送的 MQTT 控制报文。
1.2 入队
mosquitto
在完成组包后,通常会先将数据包添加到发送队列(使用struct mosquitto__packet
结构体链表)。然后,在适当的时机(如主线程从select或pselect的阻塞中被唤醒时),主线程会从发送队列中取出数据包,并将其发送到服务器。这种机制可以有效地管理和优化数据包的发送,以提高性能和效率。
发送队列由 packet_queue()
函数管理,该函数将待发送的 MQTT 控制报文插入到链表中。
1.3 出队
在mosquitto
客户端源码中,出队操作是由mosquitto_loop()
函数执行的。在mosquitto_loop()
函数中,会调用mosquitto__packet_write()
函数来处理发送队列中的数据包。这个函数会从发送队列中取出数据包并尝试将它们发送到服务器。因此,可以说mosquitto__packet_write()是执行出队操作的函数。
2.两个特别的文件描述符mosq->sockpairR
和 mosq->sockpairW
在Mosquitto源码中,mosq->sockpairR
和mosq->sockpairW
是两个特殊的文件描述符,它们用于在主线程和子线程之间进行通信。它们通过socketpair()
函数创建,形成一个全双工的socket对。子线程可以通过写入mosq->sockpairW
向主线程发送信号,而主线程通过监听mosq->sockpairR
来检测是否有新的数据需要处理。
这种设计允许子线程在有新数据需要发送时唤醒主线程,从而确保及时的通信。当子线程需要发送数据时,它会向mosq->sockpairW
写入一个字节的数据。而主线程在select()
或poll()
系统调用中监听mosq->sockpairR
,当它检测到可读事件时,意味着子线程有新的数据需要处理。这时,主线程会处理发送队列中的数据包并将其发送给服务器。
通过这种机制,Mosquitto避免了潜在的阻塞和资源竞争问题,提高了通信的实时性。
2.1 创建:
在mosquitto_reinitialise()
函数中,会调用net__socketpair()函数来创建一对全双工的socket
,分别对应文件描述符mosq->sockpairR 和 mosq->sockpairW。mosquitto_reinitialise()是用于初始化或重新初始化mosquitto客户端实例的函数。
int mosquitto_reinitialise(struct mosquitto *mosq, const char *id, bool clean_start, void *userdata)
{
...
/* This must be after pthread_mutex_init(), otherwise the log mutex may be
* used before being initialised. */
if(net__socketpair(&mosq->sockpairR, &mosq->sockpairW)){
log__printf(mosq, MOSQ_LOG_WARNING,
"Warning: Unable to open socket pair, outgoing publish commands may be delayed.");
}
return MOSQ_ERR_SUCCESS;
}
int net__socketpair(mosq_sock_t *pairR, mosq_sock_t *pairW)
{
...
}
2.2 发送:
当消息包加入输出队列(使用struct mosquitto__packet结构体链表),packet__queue()函数会向mosq->sockpairW写入一个字节的数据。这将可能处于阻塞状态的主线程唤醒,以便主线程处理输出队列中的消息包。
int packet__queue(struct mosquitto *mosq, struct mosquitto__packet *packet)
{
...
/* Write a single byte to sockpairW (connected to sockpairR) to break out
* of select() if in threaded mode. */
if(mosq->sockpairW != INVALID_SOCKET){
#ifndef WIN32
if(write(mosq->sockpairW, &sockpair_data, 1)){
}
#else
send(mosq->sockpairW, &sockpair_data, 1, 0);
#endif
}
...
}