0%

【计算机网络】mqtt 通信协议入门(六)连接服务器实战-调试运行与 tcpdump 抓包分析

前面的文章中已经为 ”连接服务器“ 这一任务做好了足够的铺垫,这篇文章,我们在之前的基础上会构建一个简单的测试程序 和 Makefile 文件来编译程序,并让它成功运行起来。最后,为了验证我们的程序是不是真正的完成了要求的功能,我们还会使用 tcpdump 进行抓包验证。在这个过程中,代码出现 bug 时,我们使用 gdb 来进行调试和解决。下面让我们开始吧!

简单的测试程序

我们需要创建一个 main 函数,然后初始化 mqtt session 的参数,最后调用我们 已经实现的接口即可,该测试程序非常简单:

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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <sys/unistd.h>

#include "mqtt.h"

#define CLIENT_ID "clientid"
#define USERNAME "user"
#define PASSWORD "pass"

#define ADDRESS "broker-cn.emqx.io"
#define PORT "1883"

int main()
{
struct connect_params conn_params = {
.proto_version = 4,
.clean_session = 0,
.will_flag = 0,
.will_qos = 0,
.will_retain = 0,
.password_flag = 1,
.username_flag = 1,
.keep_alive_interval = 10,
.client_id = CLIENT_ID,
.username = USERNAME,
.password = PASSWORD,
.client_id_len = strlen(CLIENT_ID),
.username_len = strlen(USERNAME),
.password_len = strlen(PASSWORD),
};

struct network_t net_params = {
.addr = ADDRESS,
.port = PORT,
};

struct session_init_params init_params = {
.conn_params = conn_params,
.network_params = net_params,
.net_timeout = 10,
.read_buflen = 1024,
.write_buflen = 1024,
};

struct mqtt_session *session = malloc(sizeof(struct mqtt_session));
if (!session) {
printf("failed to alloc memory for session\n");
return -1;
}

// while (1) {
mqtt_init(session, &init_params);
sleep(1);

mqtt_connect(session);
sleep(1);

mqtt_pingreq(session);
sleep(1);

mqtt_disconnect(session);
sleep(1);
// }
return 0;
}

值得注意的是,我们在测试时需要连接到服务器,可以使用一些公共服务进行测试,我这里使用的是 EMQX 提供的。

Makefile

在创建 Makefile 前,我先展示一下我的项目目录,因为 Makefile 和此息息相关,当然仅作为参考:

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
├── Makefile
├── common
│ ├── include
│ │ └── errno.h
│ └── sub.mk
├── mqtt
│ ├── include
│ │ ├── mqtt.h
│ │ ├── packet.h
│ │ ├── serialize_conn.h
│ │ └── serialize_ping.h
│ ├── mqtt.c
│ ├── packet.c
│ ├── serialize_conn.c
│ ├── serialize_ping.c
│ └── sub.mk
├── out
│ ├── bin
│ │ └── mqtt
│ └── lib
├── rebuild.sh
├── system
│ ├── include
│ │ ├── memory.h
│ │ ├── network.h
│ │ └── timer.h
│ ├── linux
│ │ ├── memory.c
│ │ ├── network.c
│ │ └── timer.c
│ └── sub.mk
└── test
└── main.c

顶层 Makefile 如下,这里我没有追求高拓展和高复用性,仅仅作为一个能使用的版本作为参考:

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
PALTFORM ?= linux
CROSS_COMPLIER ?= x86_64-linux-gnu-

CC := $(CROSS_COMPLIER)gcc
CFLAGS := -Wall -O0 -g -ggdb

TARGET := mqtt
SUBDIRS := common mqtt system
INCLUDES := $(foreach dir, $(SUBDIRS), -I$(dir)/include)
SRCS :=

include $(foreach dir, $(SUBDIRS), $(dir)/sub.mk)
OBJS := $(sort $(SRCS:.c=.o))

OUTDIRS := out
PROJECT_ROOT_PATH := $(CURDIR)
LIBRARY_OUTPUT_PATH := $(PROJECT_ROOT_PATH)/$(OUTDIRS)/lib
BINARY_OUTPUT_APTH := $(PROJECT_ROOT_PATH)/$(OUTDIRS)/bin

.PHONY: all
all: $(LIBRARY_OUTPUT_PATH)/lib$(TARGET).a $(LIBRARY_OUTPUT_PATH)/lib$(TARGET).so \
$(BINARY_OUTPUT_APTH)/$(TARGET)

$(BINARY_OUTPUT_APTH)/$(TARGET) : $(OBJS) test/main.o
@mkdir -p $(BINARY_OUTPUT_APTH)
$(CC) -o $@ $(OBJS) test/main.o


$(LIBRARY_OUTPUT_PATH)/lib$(TARGET).a : $(OBJS)
@mkdir -p $(LIBRARY_OUTPUT_PATH)
ar rcs $@ $(OBJS)

$(LIBRARY_OUTPUT_PATH)/lib$(TARGET).so : $(OBJS)
@mkdir -p $(LIBRARY_OUTPUT_PATH)
$(CC) -shared -o $@ $(OBJS)

%.o: %.c
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@

.PHONY: clean
clean:
$(RM) $(OBJS)
$(RM) $(LIBRARY_OUTPUT_PATH)/lib$(TARGET).a
$(RM) $(LIBRARY_OUTPUT_PATH)/lib$(TARGET).so
$(RM) test/main.o

.PHONY: print
print:
@echo INCLUDES = $(INCLUDES)
@echo SRCS = $(SRCS)
@echo OBJS = $(OBJS)
@echo LIB_OUTPUT = $(LIBRARY_OUTPUT_PATH)

子文件夹下的 Makefile,名称为 sub.mk ,以 system/ 下的为例:

1
SRCS += $(wildcard system/$(PALTFORM)/*.c)

再次重申,Makefile 的书写因人而异,这里仅作参考。

解决 bug 与完善代码

segment fault

编译运行后,我们测试程序输出的错误信息如下:

1
Segmentation fault

这表示段错误,在 Linux 中这是一种常见的错误,通常由以下原因导致:

  • 内存访问越界:这是最常见的原因之一。例如,试图访问数组边界之外的内存,或者对已经释放的内存进行访问。 比如,定义了一个数组 int arr[5],但却尝试访问 arr[10]
  • 空指针解引用:当程序尝试对一个空指针(未初始化或已被赋值为 NULL 的指针)进行解引用操作时,会导致段错误。 例如: c int *ptr = NULL; *ptr = 10;
  • 栈溢出:如果函数调用层次过深,或者在函数内部使用了过大的局部变量,可能导致栈空间不足,引发段错误。
  • 非法的内存操作:例如,使用 free() 释放了一块内存后,又再次尝试访问或释放它。
  • 指针类型错误:将一种类型的指针强制转换为另一种不兼容的类型,并进行访问操作。

下面我们使用gdb来调试程序,看看详细的错误输出,使用 gdb 运行程序:

1
2
3
$gdb ./out/bin/mqtt
Reading symbols from ./out/bin/mqtt...
(gdb)

直接使用 run 运行程序:

1
2
3
4
5
6
7
(gdb) r
Starting program: /home/sunhuashan/workplace/my_mqttclient/ours/out/bin/mqtt

Program received signal SIGSEGV, Segmentation fault.
__memset_avx2_unaligned_erms () at ../sysdeps/x86_64/multiarch/memset-vec-unaligned-erms.S:259
259 ../sysdeps/x86_64/multiarch/memset-vec-unaligned-erms.S: No such file or directory.
(gdb)

根据上面的信息,可以看出确实是在 memset 的时候出错,可能是操作了非法内存,我们代码里用到了比较多的 memset ,不太容易直接排除,下面通过打断点的方式,进一步定位错误,首先在各个函数入口打断点,确认错误出现在哪个函数:

1
2
3
4
5
6
7
8
9
10
(gdb) b main
Breakpoint 1 at 0x30cb: file test/main.c, line 51.
(gdb) b mqtt_init
Breakpoint 2 at 0x182c: file mqtt/mqtt.c, line 135.
(gdb) b mqtt_connect
Breakpoint 3 at 0x1b30: file mqtt/mqtt.c, line 228.
(gdb) b mqtt_disconnect
Breakpoint 4 at 0x1cca: file mqtt/mqtt.c, line 280.
(gdb) b mqtt_pingreq
Breakpoint 5 at 0x1dbe: file mqtt/mqtt.c, line 312.

一个一个断点运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(gdb) r
Starting program: /home/sunhuashan/workplace/my_mqttclient/ours/out/bin/mqtt

Breakpoint 1, main () at test/main.c:51
51 {
(gdb) c
Continuing.

Breakpoint 2, mqtt_init (session=0x0, init_params=0x7fffffffdd00) at mqtt/mqtt.c:135
135 {
(gdb) c
Continuing.

Breakpoint 3, mqtt_connect (session=0x7ffff7eaddfe <__sleep+62>) at mqtt/mqtt.c:228
228 {
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
__memset_avx2_unaligned_erms () at ../sysdeps/x86_64/multiarch/memset-vec-unaligned-erms.S:259
259 ../sysdeps/x86_64/multiarch/memset-vec-unaligned-erms.S: No such file or directory.
(gdb)

第一次打断点我们成功的将问题定位到了 mqtt_connect 函数,下面在该函数中继续打断点,尝试确认错误位置:

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
(gdb) b mqtt_connect
Breakpoint 1 at 0x1b30: file mqtt/mqtt.c, line 228.
(gdb) r
Starting program: /home/sunhuashan/workplace/my_mqttclient/ours/out/bin/mqtt

Breakpoint 1, mqtt_connect (session=0x7ffff7eaddfe <__sleep+62>) at mqtt/mqtt.c:228
228 {
(gdb) l
223 memory_free(conn_opts);
224 return retval;
225 }
226
227 int mqtt_connect(struct mqtt_session *session)
228 {
229 int retval = 0;
230 unsigned char *wbuf;
231 unsigned int buflen;
232 // struct timer_t conn_timer = {0};
(gdb)
233 struct timer_t conn_timer;
234 struct connack_options ack_opts = {0};
235
236 if (!session)
237 return -1;
238
239 if (!timer_init(&conn_timer))
240 return -1;
241 timer_cutoff(&conn_timer, session->net_timeout);
242
(gdb)
243 wbuf = session->write_buf;
244 buflen = session->write_buflen;
245
246 retval = network_connect(session->network);
247 if (retval < 0)
248 goto destory_timer;
249
250 buflen = serialize_connect(wbuf, buflen, session->conn_opts);
251 retval = mqtt_send_packet(session, buflen, &conn_timer);
252 if (retval < 0)
(gdb)
253 goto destory_timer;
254
255 retval = wait_connack(session, &conn_timer);
256 if (retval < 0)
257 goto destory_timer;
258
259 retval = deserialize_connack(&ack_opts, session->read_buf, session->read_buflen);
260 if (!retval) {
261 retval = -1;
262 goto destory_timer;
(gdb)
263 }
264
265 if (ack_opts.return_code != ACK_RC_SUCCESS) {
266 printf("error:\tCONNACK return_code is:\t%d\n", ack_opts.return_code);
267 retval = -1;
268 goto destory_timer;
269 }
270
271 session->state = SESSION_CONNECTED;
272 printf("connect to server successful!\n");
(gdb) b 239
Breakpoint 2 at 0x555555555b6d: file mqtt/mqtt.c, line 239.
(gdb) b 246
Breakpoint 3 at 0x555555555bb2: file mqtt/mqtt.c, line 246.
(gdb) b 250
Breakpoint 4 at 0x555555555bcf: file mqtt/mqtt.c, line 250.
(gdb) c
Continuing.

Breakpoint 2, mqtt_connect (session=0x55555555b2a0) at mqtt/mqtt.c:239
239 if (!timer_init(&conn_timer))
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
__memset_avx2_unaligned_erms () at ../sysdeps/x86_64/multiarch/memset-vec-unaligned-erms.S:259
259 ../sysdeps/x86_64/multiarch/memset-vec-unaligned-erms.S: No such file or directory.
(gdb)

上面的具体操作是,重新启动了 gdb 调试,在 mqtt_connect 函数入口打上断点,然后运行程序,使用 list (l) 命令查看该函数的代码,选择一些合适的地方打上断点,然后 c 继续运行。功夫不负有心人,我们又进一步确认了问题函数 timer_init ,继续打断点调试,由于 timer_init 函数比较短,这次我们单步运行即可:

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
(gdb) b mqtt_connect
Breakpoint 1 at 0x1b30: file mqtt/mqtt.c, line 228.
(gdb) r
Starting program: /home/sunhuashan/workplace/my_mqttclient/ours/out/bin/mqtt

Breakpoint 1, mqtt_connect (session=0x7ffff7eaddfe <__sleep+62>) at mqtt/mqtt.c:228
228 {
(gdb) l
223 memory_free(conn_opts);
224 return retval;
225 }
226
227 int mqtt_connect(struct mqtt_session *session)
228 {
229 int retval = 0;
230 unsigned char *wbuf;
231 unsigned int buflen;
(gdb)
233 struct timer_t conn_timer;
234 struct connack_options ack_opts;
235
236 if (!session)
237 return -1;
238
239 if (!timer_init(&conn_timer))
240 return -1;
241 timer_cutoff(&conn_timer, session->net_timeout);
242
(gdb) b 239
Breakpoint 2 at 0x555555555b6d: file mqtt/mqtt.c, line 239.
(gdb) c
Continuing.

Breakpoint 2, mqtt_connect (session=0x55555555b2a0) at mqtt/mqtt.c:239
239 if (!timer_init(&conn_timer))
(gdb) s
timer_init (t=0xffffffffffffff80) at system/linux/timer.c:10
10 {
(gdb) n
11 struct timeval *timeval = (struct timeval*)t->timer;
(gdb) n
13 if (!timeval)
(gdb)
15 if (!timeval)
(gdb)
18 memset(timeval, 0, sizeof(struct timeval));
(gdb)

Program received signal SIGSEGV, Segmentation fault.
__memset_avx2_unaligned_erms () at ../sysdeps/x86_64/multiarch/memset-vec-unaligned-erms.S:259
259 ../sysdeps/x86_64/multiarch/memset-vec-unaligned-erms.S: No such file or directory.
(gdb)

ok,最终定位到了错误,在 timer_init 函数第 18 行,使用 memset 时报错。直接分析代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
int timer_init(struct timer_t *t)
{
struct timeval *timeval = (struct timeval*)t->timer;

if (!timeval)
timeval = memory_alloc(sizeof(struct timeval));
if (!timeval)
return 0;

memset(timeval, 0, sizeof(struct timeval));
t->timer = (void*)timeval;
return 1;
}

mqtt_connect 函数中,我们本想初始化一个 timer_t 对象,为里面真正的 timer 分配内存空间,但是根据 gdb 的执行路径,这里直接跳过了 memory_alloc 函数,这又是因为 if(!timer) 判断为否,即 t->timer != NULL ,这是为什么呢?简单一分析,我们就发现,在 memory_alloc 函数中的 conn_timer 没有被初始化!这就导致其指向了一些不知道是什么的位置(取决于分配的栈内存之前被谁使用)!!

只有问题找到了,解决起来就容易了,在定义conn_timer 时,我们同时将其初始为全 0 :

1
struct timer_t conn_timer = {0};

hello mqtt

解决完上面的问题,我们就可以编译运行了!运行结果如下:

1
2
3
4
$ ./out/bin/mqtt 
connect to server successful!
sent pingreq to server successful!
disconnect from server successful!

抓包分析

tcpdump

虽然我们的程序可以正常运行了,但是我们并不知道是否真的将数据发送出去并且收到了响应,这里我们为了进一步验证程序的正确性,使用 Linux 下的 tcpdump 工具进行抓包分析,该工具十分强大,有机会后面继续深入学习,今天我们简单使用一下,知道下面几个选项的含义即可

  • port 表示根据端口号过来数据包
  • -n 表示不要解析域名,直接显示 ip
  • -nn 不要解析域名和端口
  • -X 同时用 hex 和 ascii 显示报文的内容
  • -S 显示绝对的序列号(sequence number),而不是相对编号
  • -v, -vv, -vvv:显示更多的详细信息

下面我们先使用命令 tcpdump port 1883 -nnXS 启动 tcpdump。

修改测试程序,在 mqtt_connect 后休眠 60s,方便查看连接时的报文:

1
2
mqtt_connect(session);
sleep(60);

重新编译运行,查看 tcpdump 的输出,如下:

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
$tcpdump port 1883 -nnXS
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
15:00:56.809454 IP 172.28.166.23.54374 > 54.244.173.190.1883: Flags [S], seq 3452615905, win 64240, options [mss 1460,sackOK,TS val 2428129756 ecr 0,nop,wscale 7], length 0
0x0000: 4500 003c dcd7 4000 4006 26fe ac1c a617 E..<..@.@.&.....
0x0010: 36f4 adbe d466 075b cdca bce1 0000 0000 6....f.[........
0x0020: a002 faf0 3715 0000 0204 05b4 0402 080a ....7...........
0x0030: 90ba 51dc 0000 0000 0103 0307 ..Q.........
15:00:57.001812 IP 54.244.173.190.1883 > 172.28.166.23.54374: Flags [S.], seq 2597959144, ack 3452615906, win 2048, options [mss 1360,sackOK,TS val 1561261964 ecr 2428129756,nop,wscale 9], length 0
0x0000: 4500 003c 0000 4000 2e06 15d6 36f4 adbe E..<..@.....6...
0x0010: ac1c a617 075b d466 9ad9 b5e8 cdca bce2 .....[.f........
0x0020: a012 0800 1b08 0000 0204 0550 0402 080a ...........P....
0x0030: 5d0e f78c 90ba 51dc 0103 0309 ].....Q.....
15:00:57.001895 IP 172.28.166.23.54374 > 54.244.173.190.1883: Flags [.], ack 2597959145, win 502, options [nop,nop,TS val 2428129948 ecr 1561261964], length 0
0x0000: 4500 0034 dcd8 4000 4006 2705 ac1c a617 E..4..@.@.'.....
0x0010: 36f4 adbe d466 075b cdca bce2 9ad9 b5e9 6....f.[........
0x0020: 8010 01f6 370d 0000 0101 080a 90ba 529c ....7.........R.
0x0030: 5d0e f78c ]...
15:00:57.001981 IP 172.28.166.23.54374 > 54.244.173.190.1883: Flags [P.], seq 3452615906:3452615940, ack 2597959145, win 502, options [nop,nop,TS val 2428129949 ecr 1561261964], length 34
0x0000: 4500 0056 dcd9 4000 4006 26e2 ac1c a617 E..V..@.@.&.....
0x0010: 36f4 adbe d466 075b cdca bce2 9ad9 b5e9 6....f.[........
0x0020: 8018 01f6 372f 0000 0101 080a 90ba 529d ....7/........R.
0x0030: 5d0e f78c 1020 0004 4d51 5454 04c0 000a ].......MQTT....
0x0040: 0008 636c 6965 6e74 6964 0004 7573 6572 ..clientid..user
0x0050: 0004 7061 7373 ..pass
15:00:57.455919 IP 54.244.173.190.1883 > 172.28.166.23.54374: Flags [.], ack 3452615940, win 4, options [nop,nop,TS val 1561262169 ecr 2428129949], length 0
0x0000: 4500 0034 de97 4000 2e06 3746 36f4 adbe E..4..@...7F6...
0x0010: ac1c a617 075b d466 9ad9 b5e9 cdca bd04 .....[.f........
0x0020: 8010 0004 4fbe 0000 0101 080a 5d0e f859 ....O.......]..Y
0x0030: 90ba 529d ..R.
15:00:57.455953 IP 54.244.173.190.1883 > 172.28.166.23.54374: Flags [P.], seq 2597959145:2597959149, ack 3452615940, win 4, options [nop,nop,TS val 1561262366 ecr 2428129949], length 4
0x0000: 4500 0038 de98 4000 2e06 3741 36f4 adbe E..8..@...7A6...
0x0010: ac1c a617 075b d466 9ad9 b5e9 cdca bd04 .....[.f........
0x0020: 8018 0004 2deb 0000 0101 080a 5d0e f91e ....-.......]...
0x0030: 90ba 529d 2002 0100 ..R.....
15:00:57.456005 IP 172.28.166.23.54374 > 54.244.173.190.1883: Flags [.], ack 2597959149, win 502, options [nop,nop,TS val 2428130403 ecr 1561262366], length 0
0x0000: 4500 0034 dcda 4000 4006 2703 ac1c a617 E..4..@.@.'.....
0x0010: 36f4 adbe d466 075b cdca bd04 9ad9 b5ed 6....f.[........
0x0020: 8010 01f6 370d 0000 0101 080a 90ba 5463 ....7.........Tc
0x0030: 5d0e f91e ]...

在对报文进行解析前,先来看一下数据是如何封装的:

TCP/IP 数据格式

一图胜千言,这里我们的 MQTT 属于应用层。对于各协议的头部解析超出了本文章的讨论范围,所以这里我们仅仅为了方便找到 mqtt 数据,来看一下数据格式和各个协议头部的大小。

image-20240717151540338

  • IP 头部:20字节
  • TCP 头部 :20字节 + 选项字段(在头部中给出

我们以第一个报文为例分析一下各个部分(tcpdump 默认不显示 以太网帧头尾,需添加 -XX 标志):

1
2
3
4
0x0000:  4500 003c dcd7 4000 4006 26fe ac1c a617  E..<..@.@.&.....
0x0010: 36f4 adbe d466 075b cdca bce1 0000 0000 6....f.[........
0x0020: a002 faf0 3715 0000 0204 05b4 0402 080a ....7...........
0x0030: 90ba 51dc 0000 0000 0103 0307 ..Q.........
  • IP 头部 20字节
1
2
0x0000:  4500 003c dcd7 4000 4006 26fe ac1c a617  E..<..@.@.&.....
0x0010: 36f4 adbe ....
  • TCP 头部(20) + 可选字段(20) = 40字节
1
2
3
0x0010:            d466 075b cdca bce1 0000 0000  6....f.[........
0x0020: a002 faf0 3715 0000 0204 05b4 0402 080a ....7...........
0x0030: 90ba 51dc 0000 0000 0103 0307 ..Q.........

该报文为 TCP握手报文,所以没有应用层数据。

tcp 握手

前三个 TCP 报文为建立 TCP 连接所需要的,这不是我们的主题,有兴趣的可以自行看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
15:00:56.809454 IP 172.28.166.23.54374 > 54.244.173.190.1883: Flags [S], seq 3452615905, win 64240, options [mss 1460,sackOK,TS val 2428129756 ecr 0,nop,wscale 7], length 0
0x0000: 4500 003c dcd7 4000 4006 26fe ac1c a617 E..<..@.@.&.....
0x0010: 36f4 adbe d466 075b cdca bce1 0000 0000 6....f.[........
0x0020: a002 faf0 3715 0000 0204 05b4 0402 080a ....7...........
0x0030: 90ba 51dc 0000 0000 0103 0307 ..Q.........
15:00:57.001812 IP 54.244.173.190.1883 > 172.28.166.23.54374: Flags [S.], seq 2597959144, ack 3452615906, win 2048, options [mss 1360,sackOK,TS val 1561261964 ecr 2428129756,nop,wscale 9], length 0
0x0000: 4500 003c 0000 4000 2e06 15d6 36f4 adbe E..<..@.....6...
0x0010: ac1c a617 075b d466 9ad9 b5e8 cdca bce2 .....[.f........
0x0020: a012 0800 1b08 0000 0204 0550 0402 080a ...........P....
0x0030: 5d0e f78c 90ba 51dc 0103 0309 ].....Q.....
15:00:57.001895 IP 172.28.166.23.54374 > 54.244.173.190.1883: Flags [.], ack 2597959145, win 502, options [nop,nop,TS val 2428129948 ecr 1561261964], length 0
0x0000: 4500 0034 dcd8 4000 4006 2705 ac1c a617 E..4..@.@.'.....
0x0010: 36f4 adbe d466 075b cdca bce2 9ad9 b5e9 6....f.[........
0x0020: 8010 01f6 370d 0000 0101 080a 90ba 529c ....7.........R.
0x0030: 5d0e f78c ]...

connect 报文

1
2
3
4
5
6
7
8
9
10
11
12
15:00:57.001981 IP 172.28.166.23.54374 > 54.244.173.190.1883: Flags [P.], seq 3452615906:3452615940, ack 2597959145, win 502, options [nop,nop,TS val 2428129949 ecr 1561261964], length 34
0x0000: 4500 0056 dcd9 4000 4006 26e2 ac1c a617 E..V..@.@.&.....
0x0010: 36f4 adbe d466 075b cdca bce2 9ad9 b5e9 6....f.[........
0x0020: 8018 01f6 372f 0000 0101 080a 90ba 529d ....7/........R.
0x0030: 5d0e f78c 1020 0004 4d51 5454 04c0 000a ].......MQTT....
0x0040: 0008 636c 6965 6e74 6964 0004 7573 6572 ..clientid..user
0x0050: 0004 7061 7373 ..pass
15:00:57.455919 IP 54.244.173.190.1883 > 172.28.166.23.54374: Flags [.], ack 3452615940, win 4, options [nop,nop,TS val 1561262169 ecr 2428129949], length 0
0x0000: 4500 0034 de97 4000 2e06 3746 36f4 adbe E..4..@...7F6...
0x0010: ac1c a617 075b d466 9ad9 b5e9 cdca bd04 .....[.f........
0x0020: 8010 0004 4fbe 0000 0101 080a 5d0e f859 ....O.......]..Y
0x0030: 90ba 529d ..R.

第一个是我们发出的 mqtt 报文封装后的包,第二个是服务器的 ack 包,我们只关注 mqtt 报文,如下:

1
2
3
0x0030:			   1020 0004 4d51 5454 04c0 000a      ....MQTT....
0x0040: 0008 636c 6965 6e74 6964 0004 7573 6572 ..clientid..user
0x0050: 0004 7061 7373 ..pass
  • 固定头部:10 (0001 0000)-> CONNECT 报文

  • 可变长度:20 (0010 0000)-> 剩余长度 32 字节

  • 协议名长度:0004 -> 协议名长度为 4

  • 协议名: 5d51 5454 -> 协议名 ”MQTT“

  • 协议级别:04

  • 连接标志 :c0 (1100 0000) -> 只有 username 和 password flag 标记为 1,其余均为 0

  • 保持连接时间:000a -> 10s 的保持连接时间

  • 客户端 ID 长度:0008 -> 字符串长度为 8

  • 客户端 ID :636c 6965 6e74 6964 -> “clientid”

  • 用户名 长度:0004 -> 字符串长度为 4

  • 用户名 : 7573 6572 -> “user”

  • 密码 长度:0004 -> 字符串长度为 4

  • 密码 : 7061 7373 -> “pass”

这与我们在测试程序中的设置完全一致!!说明程序完成了连接功能的实现。

connack 报文

1
2
3
4
5
6
7
8
9
10
15:00:57.455953 IP 54.244.173.190.1883 > 172.28.166.23.54374: Flags [P.], seq 2597959145:2597959149, ack 3452615940, win 4, options [nop,nop,TS val 1561262366 ecr 2428129949], length 4
0x0000: 4500 0038 de98 4000 2e06 3741 36f4 adbe E..8..@...7A6...
0x0010: ac1c a617 075b d466 9ad9 b5e9 cdca bd04 .....[.f........
0x0020: 8018 0004 2deb 0000 0101 080a 5d0e f91e ....-.......]...
0x0030: 90ba 529d 2002 0100 ..R.....
15:00:57.456005 IP 172.28.166.23.54374 > 54.244.173.190.1883: Flags [.], ack 2597959149, win 502, options [nop,nop,TS val 2428130403 ecr 1561262366], length 0
0x0000: 4500 0034 dcda 4000 4006 2703 ac1c a617 E..4..@.@.'.....
0x0010: 36f4 adbe d466 075b cdca bd04 9ad9 b5ed 6....f.[........
0x0020: 8010 01f6 370d 0000 0101 080a 90ba 5463 ....7.........Tc
0x0030: 5d0e f91e ]...

第一条为 connack 报文,第二条为 tcp 的 ack 报文我们不关注:

  • 报文类型为:20 (0010 0000)CONNACK

  • 剩余长度:02 (0000 0010)

  • session present:1 (0000 0001)

  • 返回码为:00(0000 0000)

pingreq 和 pingresp 报文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
15:00:58.456300 IP 172.28.166.23.54374 > 54.244.173.190.1883: Flags [P.], seq 3452615940:3452615942, ack 2597959149, win 502, options [nop,nop,TS val 2428131403 ecr 1561262366], length 2
0x0000: 4500 0036 dcdb 4000 4006 2700 ac1c a617 E..6..@.@.'.....
0x0010: 36f4 adbe d466 075b cdca bd04 9ad9 b5ed 6....f.[........
0x0020: 8018 01f6 370f 0000 0101 080a 90ba 584b ....7.........XK
0x0030: 5d0e f91e c000 ].....
15:00:58.735682 IP 54.244.173.190.1883 > 172.28.166.23.54374: Flags [.], ack 3452615942, win 4, options [nop,nop,TS val 1561263644 ecr 2428131403], length 0
0x0000: 4500 0034 de99 4000 2e06 3744 36f4 adbe E..4..@...7D6...
0x0010: ac1c a617 075b d466 9ad9 b5ed cdca bd06 .....[.f........
0x0020: 8010 0004 4447 0000 0101 080a 5d0e fe1c ....DG......]...
0x0030: 90ba 584b ..XK
15:00:58.735712 IP 54.244.173.190.1883 > 172.28.166.23.54374: Flags [P.], seq 2597959149:2597959151, ack 3452615942, win 4, options [nop,nop,TS val 1561263644 ecr 2428131403], length 2
0x0000: 4500 0036 de9a 4000 2e06 3741 36f4 adbe E..6..@...7A6...
0x0010: ac1c a617 075b d466 9ad9 b5ed cdca bd06 .....[.f........
0x0020: 8018 0004 743c 0000 0101 080a 5d0e fe1c ....t<......]...
0x0030: 90ba 584b d000 ..XK..
15:00:58.735732 IP 172.28.166.23.54374 > 54.244.173.190.1883: Flags [.], ack 2597959151, win 502, options [nop,nop,TS val 2428131682 ecr 1561263644], length 0
0x0000: 4500 0034 dcdc 4000 4006 2701 ac1c a617 E..4..@.@.'.....
0x0010: 36f4 adbe d466 075b cdca bd06 9ad9 b5ef 6....f.[........
0x0020: 8010 01f6 370d 0000 0101 080a 90ba 5962 ....7.........Yb
0x0030: 5d0e fe1c ]...

心跳请求和心跳响应报文,有效数据部分只有 c000d000 ,比较简单。

disconnect 报文

1
2
3
4
5
15:00:59.456791 IP 172.28.166.23.54374 > 54.244.173.190.1883: Flags [P.], seq 3452615942:3452615944, ack 2597959151, win 502, options [nop,nop,TS val 2428132403 ecr 1561263644], length 2
0x0000: 4500 0036 dcdd 4000 4006 26fe ac1c a617 E..6..@.@.&.....
0x0010: 36f4 adbe d466 075b cdca bd06 9ad9 b5ef 6....f.[........
0x0020: 8018 01f6 370f 0000 0101 080a 90ba 5c33 ....7.........\3
0x0030: 5d0e fe1c e000 ].....

关闭连接报文,有效数据只有 e000

tcp 挥手

随后,服务器发起 TCP 连接的关闭请求,由于没有数据要继续发送,仅有三次挥手

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15:00:59.515847 IP 54.244.173.190.1883 > 172.28.166.23.54374: Flags [F.], seq 2597959151, ack 3452615942, win 4, options [nop,nop,TS val 1561264479 ecr 2428131682], length 0
0x0000: 4500 0034 de9b 4000 2e06 3742 36f4 adbe E..4..@...7B6...
0x0010: ac1c a617 075b d466 9ad9 b5ef cdca bd06 .....[.f........
0x0020: 8011 0004 3fea 0000 0101 080a 5d0f 015f ....?.......].._
0x0030: 90ba 5962 ..Yb
15:00:59.559487 IP 172.28.166.23.54374 > 54.244.173.190.1883: Flags [.], ack 2597959152, win 502, options [nop,nop,TS val 2428132506 ecr 1561264479], length 0
0x0000: 4500 0034 dcde 4000 4006 26ff ac1c a617 E..4..@.@.&.....
0x0010: 36f4 adbe d466 075b cdca bd08 9ad9 b5f0 6....f.[........
0x0020: 8010 01f6 370d 0000 0101 080a 90ba 5c9a ....7.........\.
0x0030: 5d0f 015f ].._
15:01:00.028082 IP 54.244.173.190.1883 > 172.28.166.23.54374: Flags [R], seq 2597959152, win 0, length 0
0x0000: 4500 0028 0000 4000 2e06 15ea 36f4 adbe E..(..@.....6...
0x0010: ac1c a617 075b d466 9ad9 b5f0 0000 0000 .....[.f........
0x0020: 5004 0000 4c6e 0000 P...Ln..