概述
MQTT协议的工作流程如下:
其实很简单,就是通过客户端向服务器发布订阅主题来实现信息的交互。这里我将用简单的代码实现客户端的功能。
MQTT服务器安装
这里我用的树莓派作为服务器,安装的服务器是mosquitto。
安装流程很简单
sudo apt-get update #获取更新
sudo apt-get install mosquitto #安装mosquitto服务器
mosquitto -v #启动mosquitto服务器
依次输入上面代码即可,当然,你也可以安装到其他地方,比如虚拟机,或者电脑都可以,下面这个网址有mosquitto在不同环境的安装包和教程
软件下载地址
电脑模拟抓包
服务器安装完成后,需要先分析以下客户端和服务器之间的通讯,先找电脑上装一个MQTT客户端软件,这里我装的是MQTT.fx,安装包网上有很多资源,可以自己去找,接下来打开软件,按照步骤输入以下内容,其中服务器默认端口是1883,所以一定要设置为1883。
然后打开wireshark,准备好了之后,点击MQTT.fx软件上的connect开始抓包,不会wireshark软件可参考我的一篇文章
wireshark抓包_yuansec的博客-CSDN博客
通过抓取的包,发现,MQTT协议是基于TCP/IP协议的,客户段连接服务器的动作,首先是tcp三次握手,接着发送连接请求命令。
继续抓包,新建一个发布,发送给服务器,这里使用的QoS0等级发送,可以看出,发送很简单,一条报文即可,解析出来的数据可以看出,这条报文包含了主题和内容
接着,新建一个订阅,抓取报文
最后,再次发布,可以打看到数据发送到了订阅,同时抓取的报文可以看到数据交互过程
代码实现
这里我在虚拟机实现客户端功能,linux环境
首先按照刚刚的步骤,先进行sock连接,进行tcp的三次握手
int connect_sock()
{
int sock;
struct sockaddr_in aa;
sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock == -1)
{
printf("sock errorn");
return -1;
}
aa.sin_family = AF_INET;
aa.sin_port = htons(1883);
aa.sin_addr.s_addr = inet_addr("192.168.0.10");
if(connect(sock, (struct sockaddr *)&aa, sizeof(aa)) < 0)
{
printf("connect errorn");
return -1;
}
printf("connect with destination host...n");
return sock;
}
接着,MQTT连接请求,这里参照抓包的格式,直接定义好每一个字节
int connect_pub(int fd)
{
char data[1024];
data[0] = 0x10;
data[1] = 14;
data[2] = 0x00;
data[3] = 0x04;
data[4] = 'M';
data[5] = 'Q';
data[6] = 'T';
data[7] = 'T';
data[8] = 4;
data[9] = 2;
data[10] = 0x00;
data[11] = 0x3c;
data[12] = 0x00;
data[13] = 0x02;
data[14] = 'f';
data[15] = 'e';
int sum = send(fd, data, 16, 0);
if(sum < 0)
{
printf("send errorn");
return -1;
}
printf("connect_pub send sum = %dn",sum);
return 0;
}
接着就是建立订阅连接
int connect_sub(int fd, char *topic)
{
char data[1024];
int i;
data[0] = 0x82;
data[2] = 0x00;
data[3] = 0x03;
int len = strlen(topic);
data[4] = len/256;
data[5] = len%256;
for(i = 0; i < len; i++)
data[6 + i] = topic[i];
data[6 + len] = 0x00;
data[1] = 5 + len;
int sum = send(fd, data, 7 + len, 0);
if(sum < 0)
{
printf("send errorn");
return -1;
}
printf("connect_sub send sum = %dn",sum);
return 0;
}
建立好订阅连接后,就可以发布内容了,同时创建一个线程,接收服务器发回来的数据
int pub(int fd, char *topic, char *str)
{
char data[1024];
int i;
data[0] = 0x30;
int t_len = strlen(topic);
data[2] = t_len/256;
data[3] = t_len%256;
for(i = 0; i < t_len; i++)
data[4 + i] = topic[i];
int s_len = strlen(str);
for(i = 0; i < s_len; i++)
data[4 + t_len + i] = str[i];
data[1] = 2 + t_len + s_len;
int sum = send(fd, data, 4 + t_len + s_len, 0);
if(sum < 0)
{
printf("send errorn");
return -1;
}
//printf("pub sum = %dn",sum);
return 0;
}
struct msg
{
int fd;
char *topic;
};
void *sub_rev(struct msg *msg_sub)
{
char data[1024];
char str[1024];
while(1)
{
int num = recv(msg_sub->fd, data, 1024, 0);
if(num < 0)
{
printf("recv errorn");
return 0;
}
if(num > 0)
{
if(data[0] == 0x30)
{
int len = data[2]*256 + data[3];
if(strncmp(data + 4, msg_sub->topic, len) == 0)
{
strncpy(str, data + 4 + len, num - 4 - len);
printf("recv data:%sn", str);
memset(data, '