前言
最近开始深耕TCP/IP相关开发,给自己定了个具体目标:用C语言写MQTT程序,实现两个客户端互发消息------A客户端发布消息到/test主题,B客户端能成功接收。作为MQTT初学者,踩了不少二进制报文拼接、TCP连接的坑,整理了这份入门笔记,帮和我一样的新手快速上手,跳过无效摸索,重点吃透核心规则,快速跑通最简Demo。
本文核心目标:掌握MQTT入门必备规则、C语言前置技能,成功用C语言实现MQTT客户端与公共服务器的连接,完成"连接-接收确认"的核心流程。
一、先吃透4个MQTT核心规则
MQTT是基于TCP的二进制协议,C语言手写MQTT程序,本质就是手动拼接二进制报文,所以先把底层通信规则摸透,后续写代码才不会无从下手。入门阶段,不用记太多复杂内容,重点掌握这4点即可:
-
底层依赖:MQTT = TCP连接 + 二进制报文流,默认非加密端口1883,所有通信都基于TCP socket,没有TCP连接,就没有MQTT通信。
-
报文结构(核心中的核心):所有MQTT指令(连接、发消息、订阅)都遵循统一格式------固定头部(1~2字节) + 可变头部 + 有效载荷(payload)。其中固定头部是基础:第一个字节是报文类型(比如CONNECT连接报文是0x10,PUBLISH发布报文是0x30),第二个字节是剩余长度(C语言必须手动计算,入门阶段先记"剩余长度=可变头部+有效载荷的总长度")。
-
入门优先用QoS0:QoS0是"最多一次"的消息传输模式,发出去就不管,没有回执、没有重发,是C语言手写最简洁的模式,QoS1、QoS2后续再逐步扩展。
-
通信模式:MQTT是严格的"请求-响应"模式,客户端发请求报文,服务器必须回响应报文,比如发CONNECT连接报文,就必须等服务器回CONNACK确认报文,否则后续操作都会失败。
二、C语言前置技能(硬性要求,缺一不可)
MQTT基于TCP,C语言写MQTT程序,必须先掌握TCP socket编程和二进制操作,这是基础中的基础,没掌握的话,写代码会全程卡壳。
1. Linux Socket基础(优先学Linux,Windows可兼容Winsock)
核心掌握4个函数,就是MQTT通信的"四大基石":
-
socket():创建套接字,相当于打开电脑的网络功能,返回一个文件描述符(sockfd),后续所有网络操作都依赖这个描述符。
-
connect():主动连接MQTT服务器,需要传入服务器IP和端口(默认1883),连接成功才算真正打通通信通道。
-
send():发送二进制报文,MQTT的所有指令(CONNECT、PUBLISH等),都是通过这个函数发送到服务器。
-
recv():接收服务器的响应报文,比如CONNACK、SUBACK等,必须通过这个函数读取服务器的回复,否则会导致缓冲区堆积,程序异常。
2. 二进制/字节操作
MQTT传输的是纯字节流,C语言中主要用3个函数拼接、清空报文:memcpy(拼接字节)、memset(清空缓冲区)、uint8_t(无符号char,存储单个字节,避免符号干扰)。
3. 网络字节序(大端序)
MQTT协议规定,所有数字(比如端口号、保活时间)必须用大端序传输,而C语言主机默认是小端序,所以必须用htons()函数进行转换(比如端口1883,要转换成htons(1883)才能传输)。
三、核心步骤:跑通最简MQTT Demo(入门成功的标志)
最简Demo的目标很简单:只做3件事,不添加任何多余功能,确保能成功连接MQTT服务器,接收服务器的连接确认。
1. 准备工具
-
公共MQTT服务器:test.mosquitto.org(免费、无门槛,无需注册账号,直接可用,默认端口1883)。
-
调试工具(可选):MQTTX(桌面端,用来验证服务器是否可用,后续可验证自己写的客户端是否正常)。
-
编译环境:Linux(Ubuntu最佳),gcc编译器。
2. 编写最简Demo代码(C语言,可直接复制运行)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MQTT_SERVER "test.mosquitto.org" // 公共MQTT服务器
#define MQTT_PORT 1883 // MQTT默认端口
int main() {
// 1. 创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket创建失败");
return -1;
}
// 2. 配置服务器地址
struct sockaddr_in addr;
addr.sin_family = AF_INET; // IPv4协议
addr.sin_port = htons(MQTT_PORT); // 端口转换为大端序
inet_pton(AF_INET, MQTT_SERVER, &addr.sin_addr); // 服务器IP转换
// 3. 连接MQTT服务器
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("连接服务器失败");
close(sockfd);
return -1;
}
printf("TCP连接服务器成功!\n");
// 4. 拼接并发送CONNECT连接报文(二进制)
// 报文解析:固定头(0x10=CONNECT, 0x13=剩余长度19) + 可变头 + 有效载荷(客户端ID)
unsigned char connect_pkt[] = {
0x10, 0x13, // 固定头
0x00,0x04,'M','Q','T','T', // 协议名(MQTT)+ 协议名长度
0x04, // 协议级别(3.1.1)
0x02, // 连接标志(Clean Session)
0x00, 60, // 保活时间60秒(大端序)
0x00,0x07,'c','l','i','e','n','t','0' // 客户端ID(自定义,唯一即可)
};
send(sockfd, connect_pkt, sizeof(connect_pkt), 0);
printf("已发送MQTT连接报文\n");
// 5. 接收服务器回复的CONNACK报文(必须接收,否则程序异常)
char buf[1024];
int recv_len = recv(sockfd, buf, sizeof(buf), 0);
if (recv_len <= 0) {
printf("未收到CONNACK报文,连接失败\n");
close(sockfd);
return -1;
}
// 判断连接是否成功(CONNACK报文:0x20=报文类型,0x00=连接成功返回码)
if (buf[0] == 0x20 && buf[3] == 0x00) {
printf("MQTT连接成功!✅\n");
} else {
printf("MQTT连接失败,返回码:%d\n", buf[3]);
close(sockfd);
return -1;
}
// 断开连接
unsigned char disconnect_pkt[] = {0xE0, 0x00}; // DISCONNECT报文
send(sockfd, disconnect_pkt, sizeof(disconnect_pkt), 0);
close(sockfd);
printf("已断开连接\n");
return 0;
}
3. 编译运行步骤
-
保存代码为mqtt_demo.c;
-
编译命令:gcc mqtt_demo.c -o mqtt_demo;
-
运行命令:./mqtt_demo;
-
成功标志:打印"TCP连接服务器成功!""MQTT连接成功!✅",说明最简Demo跑通。
四、入门避坑点(新手必看)
-
连接服务器失败:检查网络是否正常,test.mosquitto.org是否能ping通,端口1883是否被防火墙拦截。
-
未收到CONNACK报文:大概率是CONNECT报文拼接错误,重点检查"剩余长度"是否计算正确(剩余长度=可变头部+有效载荷的总长度)。
-
端口转换错误:忘记用htons()转换端口,会导致连接失败,记住所有网络传输的数字都要转大端序。
小结
入门MQTT,核心不是死记硬背API,而是吃透 "TCP连接+二进制报文" 的底层逻辑,跑通最简Demo就是入门成功的标志。下一篇我们将在此基础上,实现MQTT的发布(PUBLISH)和订阅(SUBSCRIBE) 功能,完成两个客户端的互发通信。
关注我,后续持续更新MQTT实战内容,一起从入门到精通!