代码分析_libhv库中的MQTT协议实现

libhv库中的MQTT协议实现

连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/*
* MQTT_TYPE_CONNECT
* 2 + protocol_name + 1 protocol_version + 1 conn_flags + 2 keepalive + 2 + [client_id] +
* |--> clientID长度
* [2 + will_topic + 2 + will_payload] +
* [2 + username] + [2 + password]
*/
static int mqtt_client_login(mqtt_client_t* cli) {
int len = 2 + 1 + 1 + 2 + 2; // 先把能确定的长度,确定下来
unsigned short cid_len = 0, // 客户端ID长度
will_topic_len = 0, // 遗嘱主题长度
will_payload_len = 0, // 遗嘱负载长度
username_len = 0, // 用户名称长度
password_len = 0; // 密码长度
unsigned char conn_flags = 0; // 各种连接标志,一个字节搞定

// protocol_name_len
len += cli->protocol_version == MQTT_PROTOCOL_V31 ? 6 : 4; // 在构造函数中已经设置过默认V311
// 若要设置客户端ID,则计入客户端ID的长度
if (*cli->client_id) {
cid_len = strlen(cli->client_id);
} else {
// 不设置的话,随机写入一个客户端ID
cid_len = 20; // 20个字节长度,重复概率非常低
hv_random_string(cli->client_id, cid_len);
hlogi("MQTT client_id: %.*s", (int)cid_len, cli->client_id);
}
len += cid_len;
if (cid_len == 0) cli->clean_session = 1; // 出错啦
if (cli->clean_session) {
conn_flags |= MQTT_CONN_CLEAN_SESSION;
}
// 若客户端设置了遗嘱
if (cli->will && cli->will->topic && cli->will->payload) {
will_topic_len = cli->will->topic_len ? cli->will->topic_len : strlen(cli->will->topic);
will_payload_len = cli->will->payload_len ? cli->will->payload_len : strlen(cli->will->payload);
if (will_topic_len && will_payload_len) {
conn_flags |= MQTT_CONN_HAS_WILL; // 连接标识里说明有遗嘱
conn_flags |= ((cli->will->qos & 3) << 3);
if (cli->will->retain) {
conn_flags |= MQTT_CONN_WILL_RETAIN;
}
len += 2 + will_topic_len;
len += 2 + will_payload_len;
}
}
// 如果有名字,消息里将再多一些内容
if (*cli->username) {
username_len = strlen(cli->username);
if (username_len) {
conn_flags |= MQTT_CONN_HAS_USERNAME;
len += 2 + username_len;
}
}
// 如果有密码,消息里再多一些内容
if (*cli->password) {
password_len = strlen(cli->password);
if (password_len) {
conn_flags |= MQTT_CONN_HAS_PASSWORD;
len += 2 + password_len;
}
}
// 以上代码主要完成了:
// 1. 计算出了登录消息的长度,确定了连接标志
// 2. 采用随机方法设置了客户端ID
// 确定登录长度并不是必要的,对于C++来说,采用vector为底层实现的buffer即可直接append
// libhv的C++实际上只是对C接口的封装,所以需要先确定长度
// 接下来开始造包
mqtt_head_t head;
memset(&head, 0, sizeof(head));
head.type = MQTT_TYPE_CONNECT;
head.length = len; // 剩余长度
int buflen = mqtt_estimate_length(&head); // 确定了整个包的长度
unsigned char* buf = NULL;
HV_STACK_ALLOC(buf, buflen);
unsigned char* p = buf;
int headlen = mqtt_head_pack(&head, p); // 固定head与变长已经确定好了
p += headlen;
// TODO: Not implement MQTT_PROTOCOL_V5
if (cli->protocol_version == MQTT_PROTOCOL_V31) {
PUSH16(p, 6);
PUSH_N(p, MQTT_PROTOCOL_NAME_v31, 6);
} else {
PUSH16(p, 4);
PUSH_N(p, MQTT_PROTOCOL_NAME, 4);
}
PUSH8(p, cli->protocol_version); // 在构造函数中已经设置过了
PUSH8(p, conn_flags);
PUSH16(p, cli->keepalive); // 在构造函数中已经设置过了
PUSH16(p, cid_len); // 占2个字节,不管设不设client_id,都要append
if (cid_len > 0) {
PUSH_N(p, cli->client_id, cid_len);
}
if (conn_flags & MQTT_CONN_HAS_WILL) {
PUSH16(p, will_topic_len);
PUSH_N(p, cli->will->topic, will_topic_len);
PUSH16(p, will_payload_len);
PUSH_N(p, cli->will->payload, will_payload_len);
}
if (conn_flags & MQTT_CONN_HAS_USERNAME) {
PUSH16(p, username_len);
PUSH_N(p, cli->username, username_len);
}
if (conn_flags & MQTT_CONN_HAS_PASSWORD) {
PUSH16(p, password_len);
PUSH_N(p, cli->password, password_len);
}
// buf是按照最大变长(4字节)算的,实际消息可能只占一个字节,buf的尾部是空的
// 所以需要 p-buf
int nwrite = mqtt_client_send(cli, buf, p - buf);
HV_STACK_FREE(buf);
return nwrite < 0 ? nwrite : 0;
}

代码分析_libhv库中的MQTT协议实现
http://ziyangfu.github.io/2023/08/16/代码分析-libhv库中的MQTT协议实现/
作者
FZY
发布于
2023年8月16日
许可协议