mosquitto客户端Socket通信设计特点(mosquitto2.0.15客户端源码分析之五)

前言

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->sockpairRmosq->sockpairW

在Mosquitto源码中,mosq->sockpairRmosq->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
	}
	...
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值