MQTT协议
第1步:搭建MQTT服务器,选择的是EMQ服务器,下载ErlangMqtt_win7_v2.3.9.zip服务器源码包,解压后如下图所示:
第2步,按下win+R键输入cmd进入命令终端控制台,之后进入解压后的bin目录下,输入emqttd console命令会出现Erlang窗口。
第3步:输入emqttd.cmd start命令启动,启动后输入emqttd_ctl status命令查看服务器是否属于运行状态。如若停止服务器可以输入emqttd stop 命令。
第4步,进入浏览器,输入http://本地ip:18083进入EMQ服务器登陆界面。
输入用户名:admin 密码:public;进入到EMQ服务器界面。到这一步服务器就搭建完成。
接下来就利用客户端测试程序验证EMQ 服务器是否搭建成功。
第1步:下载通信猫调试工具comnet.zip,进行安装。
选择网络下的MQTT协议。输入服务器的IP和port:1883,因为MQTT基于tcp通信,服务器配置文件中tcp传输所默认的端口为1883.
第2步:点击启用,可以选择订阅/发布,进行数据传输。
下面进行MQTT客户端在下位机上运行,而EMQ服务器在pc机上运行,能够达到下位机与上位机能够正确的通信。
第1步,下载MQTT的源码包MQTTPacket,将目录下的src下的所有文件以及samples下的transport.c和transport.h放入工程目录下。
samples目录下:
第2步,根据源码提供的接口函数完成在下位机的订阅和发布的测试代码。
点击提取MQTT资料
提取码:y8w2
通过在transport.c中,完成对移植过程中一些测试代码的编写,包括与tcp进行连接、订阅测试、发布测试、检查与服务器保持连接的任务函数。
void pingTask(void)
{
unsigned char buf[10] = {0};
int bufSize = sizeof(buf);
int len = 0;
while(1)
{
taskDelay(30000);
len = MQTTSerialize_pingreq(buf, bufSize);
transport_sendPacketBuffer(mysock, buf, bufSize);
}
}
#if 0
int transport_open(char *addr,int port)
{
int *sock = &mysock;
struct sockaddr_in serv_addr;
static struct timeval tv;
int timeout = 1000;
*sock = socket(AF_INET, SOCK_STREAM, 0);
if(*sock < 0)
{
printf("socket failed.\n");
}
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = inet_addr(addr);
if(connect(*sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
{
printf("connect failed.\n");
return -1;
}
tv.tv_sec = 10;
tv.tv_usec = 0;
setsockopt(mysock, SOL_SOCKET,SO_RCVTIMEO, (char *)&timeout,sizeof(timeout));
return mysock;
}
#else
void TcpConnect(const char *host, int port)
{
struct sockaddr_in serv_addr;
mysock = socket(AF_INET, SOCK_STREAM, 0);
if(mysock < 0)
{
printf("socket failed.\n");
}
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = inet_addr(host);
int res = connect(mysock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if(res < 0)
{
printf("%s,Tcp connected failed.\n",__FUNCTION__);
}
else
{
printf("%s,Tcp connected success.\n",__FUNCTION__);
}
}
#endif
void subscribeTest()
{
int taskId = 0;
char *host = "192.168.1.8";
int port = 1883;
#if 0
mysock = transport_open(host, port);
#else
TcpConnect(host, port);
#endif
//初始化连接参数
unsigned char buf[256] = {0};
int bufSize = sizeof(buf);
MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
data.clientID.cstring = "zj"; //客户ID
data.keepAliveInterval = 30; //心跳数,保持存活时间
data.cleansession = 1; //清除会话
data.username.cstring = "admin"; //用户名
data.password.cstring = "public";//用户密码
data.MQTTVersion = 4; //MQTT版本号
//打包connect请求数据(报文)
int len = MQTTSerialize_connect(buf, bufSize, &data);
//发送请求报文
int rc = transport_sendPacketBuffer(mysock, (unsigned char *)buf, len);
if(rc == len)
{
printf("c->s send CONNECT success.\n");
//printf("len = %d,bufSize = %d\n",len,bufSize);
}
else
{
printf("c->s send CONNECT failed.\n");
exit(-1);
}
//等待服务器的CONNACK,等待服务器接到请求后,返回的请求应答数据
if(MQTTPacket_read(buf, bufSize, transport_getdata) == CONNACK)
{
unsigned char sessionPresent,connack_rc;
if(MQTTDeserialize_connack(&sessionPresent, &connack_rc, buf, bufSize) != 1 || connack_rc != 0)
{
printf("Unable to connect,return code %d\n",connack_rc);
}
else
{
printf("Able to connect.\n");
}
}
//创建ping任务,30秒ping一次
taskId = taskSpawn("pingtask", 200, 0, 0x30000, (FUNCPTR)pingTask, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
//订阅主题
MQTTString topicString = MQTTString_initializer;
int msgId = 1; //消息ID
int req_qos = 0; //消息质量
topicString.cstring = "hello"; //消息主题
//打包subscribe数据
len = MQTTSerialize_subscribe(buf, bufSize, 0, msgId, 1, &topicString, &req_qos);
//发送subscribe数据
rc = transport_sendPacketBuffer(mysock, (unsigned char *)buf, len);
while(1)
{
len = transport_getdatanb(&mysock, buf, bufSize);
if(buf[0] > 0)
{
if((buf[0] & 0xF0) == 0x30)
{
//解析publish数据
unsigned char dup; //重发标志
int qos; //服务质量等级
unsigned char retained; //保留标志
unsigned short msgid;
int payloadlen_in;
unsigned char *payload_in; //指向payload
MQTTString recvTopic;
MQTTDeserialize_publish(&dup, &qos, &retained, &msgid, &recvTopic, &payload_in, &payloadlen_in, buf, bufSize);
printf("arrived message %.*s\n",payloadlen_in,payload_in);
}
}
}
}
void publishTest()
{
char *host = "192.168.1.8";
int port = 1883;
mysock = transport_open(host,port);
unsigned char buf[256] = {0};
int bufSize = sizeof(buf);
MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
data.clientID.cstring = "jz";
data.keepAliveInterval = 30;
data.username.cstring = "admin";
data.password.cstring = "public";
//打包connect请求数据(报文)
int len = MQTTSerialize_connect(buf, bufSize, &data);
//发送请求报文
int rc = transport_sendPacketBuffer(mysock, (unsigned char *)buf, len);
if(rc == len)
{
printf("c->s send CONNECT success.\n");
//printf("len = %d,bufSize = %d\n",len,bufSize);
}
else
{
printf("c->s send CONNECT failed.\n");
exit(-1);
}
//等待服务器的CONNACK,等待服务器接到请求后,返回的请求应答数据
if(MQTTPacket_read(buf, bufSize, transport_getdata) == CONNACK)
{
unsigned char sessionPresent,connack_rc;
if(MQTTDeserialize_connack(&sessionPresent, &connack_rc, buf, bufSize) != 1 || connack_rc != 0)
{
printf("Unable to connect,return code %d\n",connack_rc);
}
else
{
printf("Able to connect.\n");
}
}
int taskId = 0;
//创建ping任务,30秒ping一次
taskId = taskSpawn("pingtask", 200, 0, 0x30000, (FUNCPTR)pingTask, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
MQTTString topicString = MQTTString_initializer;
topicString.cstring = "topic";
char *payload = "work";
while(1)
{
taskDelay(4000);
len = MQTTSerialize_publish(buf, bufSize, 0 , 0, 0, 0, topicString, (unsigned char *)payload, strlen(payload));
transport_sendPacketBuffer(mysock, buf, len);
memset(buf, 0, len);
printf("publish send work\n");
}
}
个人总结:
在打包数据时,需考虑大端还是小端模式,在移植时tcp已经能够连接,但一直无法与服务器连接,最终通过利用网络调试助手获取能够连接服务器的connect报文和不能连接的connect报文,将两者数据进行比较,发现在打包数据时,是要用到大端模式,所以在MQTTPacket.h中需要将union这个共用体中的第二个结构体注释,使用第一个结构体:大端模式.