首先照本宣科几句,MQTT(Message Queuing Telemetry Transport, 消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为远程连接设备提过实时可靠的消息服务,作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。MQTT是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(loT)。其在,通过卫星链路通信传感器、偶尔拨号的医疗设备、智能家居、及一些小型化设备中已广泛使用。
好了废话说完了,上面的文字大家也看也是白看,对于一个之前没有接触过mqtt的学者来说,总有点畏惧他,说实话我最开始也是一样的,总不知道它是干嘛的,面试的时候被问到他,没搞过的人也很懵逼,这个文章主要目的就是帮助大家从整体框架和细小的功能、源码分析上面让大家梳理清楚,这样不管是面试的时候还是遇到项目需要对接mqtt平台的时候,都不怕他就行了。
好了废话不多说,介绍下mqtt主要特点:
1、开放消息协议,简单易实现。
通俗说:为什么叫开放,因为第一源码开放,第二就是在你通信基础上加了点头尾帧,理解了不就是自定义协议吗,但是这个制定协议的人啊很牛逼,他制定出来后大家都拿来用,就变成规范了,为什么说是简单容易实现,因为你看完我文章,你再移植下不就简单了。
2、发布订阅模式,一对多消息发布。
通俗说:字面意思就是发布订阅,但是这样说90%的还是懵逼的,那我举个例子,我高中在上晚自习,我在玩手机,但是怕老师突击检查,那怎么办呢,我就告诉坐在教室门口的同学等下老师来了的话你就咳嗽一声,这就是订阅“老师来了”这个消息,过了十分钟坐门口的同学发现老师过来了,他就咳嗽了一声,这就是“发布”了“老师来了”。那“老师来了”这个东西是什么呢,这就是主题,我订阅了“老师来了”这个主题,门口同学咳嗽了,就是发布了这个主题,但是呢其他同学也想不老师发现自己在上自习玩手机,于是呢他们也想让那个同学通风报信,都知道了咳嗽就是=老师来了,这就是一对多消息发布。肯定不是全部的同学都关心来不来,这个就是没有订阅“老师来了”这个主题,所以门口同学咳嗽对他们来说,就是一声咳嗽,这就是对于没有订阅这个主题的同学来说,不会收到这个咳嗽的言外之意。大家还不懂这个发布订阅我就没办法了。
3、基于基于TCP/IP网络连接,提供有序,无损,双向连接。
通俗说:TCP/IP我就不多说,因为这个东西我也不知道该讲的有多深,但是mqtt的每一条消息,都是在tcp通信基础之上的,如果我要裸传输一个数据包是12345,那么tcp我要传输的原文是0x01 0x02 0x03 0x04 0x05(暂时不讨论tcp层的帧头尾),那么如果我要用mqtt呢,就是在这个0x01 0x02 0x03 0x04 0x05的头尾加上mqtt自己特有的东西,详见4。
4、在MQTT协议中,一个MQTT数据包由: 固定头(Fixed header)、可变头(Variable header)、消息体(payload)三部分构成。
这里我想了下,还是应该着重讲一下,不然大家又是懵逼的。
固定头(Fixed header):
byte1的bit7~4,他会组成多种值,不同值代表意思不同,我随便举几个,0001:客户端连接到服务器,0010:连接确认。
标志位:
byte1的bit3~0,少数报文才用byte1的这低四位来表示控制字段,劝大家江湖的事情少打听。
可变头(Variable Header)
可变头的意思是可变化的消息头部。有些报文类型包含可变头部有些报文则不包含。可变头部在固定头部和消息内容之间,其内容根据报文类型不同而不同。
消息体(Payload)
有些报文类型是包含Payload的,Payload的意思是消息载体的意思。
5、消息QoS支持,可靠传输保证
当我们设备网络环境不一定能够稳定的时候,这个时候单纯依靠tcp底层传输协议是很难保证消息的可靠性的,比如设备设计到扫码支付的问题,如果网络波动,这个时候如果是单纯的tcp通信,我们会自己建立重传的机制,这个qos其实就是官方的正式的重传机制,并且它定义了三个qos等级,分别是
- QoS 0,最多交付一次。
- QoS 1,至少交付一次。
- QoS 2,只交付一次。
其中,使用 QoS 0 可能丢失消息,使用 QoS 1 可以保证收到消息,但消息可能重复,使用 QoS 2 可以保证消息既不丢失也不重复。QoS 等级从低到高,不仅意味着消息可靠性的提升,也意味着传输复杂程度的提升。
好的,到现在为止,我想大部分新手应该对mqtt有一个初步的认识了,等于说我们已经开始入门了,接下来,我介绍下什么时候和什么硬件环境我们会去用这个mqtt。
首先mqtt只是个协议,所以我们用不用他首先是取决我们设备对接的服务器是不是这个协议框架,比如我们要对接一些大型企业或者公司的服务器,因为如果大型服务器没有一套标准的协议的话,那么后续新增设备上去是比较麻烦的,因为很多小公司的产品,都是自定义协议,他们也不会开放接口出来让其他设备去链接他们的服务器,所以基本上只有正规的大型的服务器有这个mqtt需求,其次就是并发量大的服务器也是需要用到mqtt。再明确一个误区,初学者认为只要我设备支持mqtt,就能和mqtt服务器通信了,这种想法是错误的,因为mqtt其实只是一个接入协议,具体的数据交互还要看payload也就是消息载体的具体通信协议,这就是我们常说的小服务器那种自定义协议,某个字段之的什么含义那种。
那什么时候我们整套硬件环境支持我们做一个mqtt的设备呢,首先肯定是要基于TCP/IP通信的,这是基础,在这个基础上就有大体两种,一是我们就是用以太网通信的硬件设备,它首先已经具备了tcp通信的要求,再其次就是通过嵌入式软件编程实现mqtt框架搭建,达到订阅发布的功能,一种是用现成的通信模组,不管是2g的sim800c,还是4g的ec20,或者是5g的rg200U等等,如果我们模组没有自带的mqtt通信功能,那么还是需要像第一种情况那样走模组的透传模式,然后再在上层搭建移植mqtt框架,如果想用现成的模组自带的mqtt框架,比如移远的EC20 4G模组就有这个功能,他们已经把mqtt集成在模组内部了,只需要简单的AT指令就可以连接到mqtt服务器了。这种方式比较简单快捷,我是比较推荐的,具体操作我就不展示了,直接看资料问移远的FAE就行。
着重讲一下基于lwip下的mqtt代码移植,首先我把移植需要的mqtt包贴出来,然后我们再慢慢分析。
这就是资源包里面大致的内容,我逐步讲解下怎么用它。
这是创建的一个任务,用来链接mqtt服务器:
void mqtt_thread(void)
{
SEGGER_RTT_printf(0, "mqtt_threadmqtt_threadmqtt_threadmqtt_threadmqtt_thread\r\n");
u8 MQTT_KeyPack[36];
int TempCont = 0;
unsigned char temp[4];
int sample_intv, upload_intv, plus_intv;
int threshold, upper_limit, lower_limit;
int i = 0;
unsigned char *p = NULL;
unsigned char *p_rcvHead = NULL;
unsigned char *p_rcvHead_temp = NULL;
unsigned char *p_rcvHead_temp2 = NULL;
int datalen = 0;
unsigned char TimeBuff[6];
unsigned char sensor_id[8];
MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
MQTTString receivedTopic;
MQTTString topicString = MQTTString_initializer;
OS_CPU_SR cpu_sr = 0;
int rc = 0;
unsigned char buf[500];
int buflen = sizeof(buf);
int mysock = 0;
int payloadlen_in;
unsigned char *payload_in;
unsigned short msgid = 1;
// unsigned char* msgid = 0;
int subcount;
int granted_qos = 0;
unsigned char sessionPresent, connack_rc;
unsigned short submsgid;
int len = 0;
int req_qos = 1;
unsigned char dup;
int qos;
unsigned char retained = 0;
uint32_t curtick;
uint32_t sendtick;
uint8_t connect_flag = 0; //连接标志
uint8_t msgtypes = CONNECT; //消息状态初始化
uint8_t t = 0;
char *PASSWORD;
unsigned char payload_out[500];
int payload_out_len = 0;
//cJSON *json, *json_params, *json_id, *json_led, *json_display;
char host[16];
int port = 0;
char connectbuf[sizeof(All_St.mqtt.MQTT_ID)+sizeof(All_St.mqtt.MQTT_Name)+sizeof(All_St.mqtt.MQTT_Key)+50];
sprintf((char *)connectbuf, "\"clientId\"%s\"deviceName\"%s\"productKey\"%s\"timestamp789\"",All_St.mqtt.MQTT_ID, All_St.mqtt.MQTT_Name, All_St.mqtt.MQTT_Key);
char DEVICE_SUBSCRIBEbuf[sizeof(All_St.mqtt.MQTT_Name)+11];
sprintf((char *)connectbuf, "\"$creq/%s/cmd\"",All_St.mqtt.MQTT_Name);
delay_ms(5000);
SEGGER_RTT_printf(0, "socket connect to server111\r\n");
host[15] = 0;//自己赋值mqtt ip
port = 0; //自己赋值mqtt port
mysock = transport_open((char *)host, port);
SEGGER_RTT_printf(0, "Sending to hostname:%s port:%d\r\n", host,port);
len = MQTTSerialize_disconnect((unsigned char *)buf, buflen);
rc = transport_sendPacketBuffer(mysock, (unsigned char *)buf, len);
SEGGER_RTT_printf(0, "****************************************len=%d rc =%d\r\n",len,rc);
if (rc == len) //
SEGGER_RTT_printf(0, "send DISCONNECT Successfully\r\n");
else
SEGGER_RTT_printf(0, "send DISCONNECT failed\r\n");
OSTimeDlyHMSM(0, 0, 2, 500);
SEGGER_RTT_printf(0, "socket connect to server2222\r\n");
mysock = transport_open((char *)host, port);
SEGGER_RTT_printf(0, "Sending to hostname222 %s port %d\r\n", host, port);
data.clientID.cstring = CLIENT_ID;
data.keepAliveInterval = 70; //心跳时长
data.cleansession = 1;
data.username.cstring = USER_NAME;
PASSWORD = mymalloc(SRAMEX, 500);
getPassword(DEVICE_SECRET, connectbuf, PASSWORD); //通过hmac_sha1算法得到password
SEGGER_RTT_printf(0, "\r\nPassWord222 = %s\r\n\r\n", PASSWORD);
data.password.cstring = PASSWORD;
data.MQTTVersion = 4;
myfree(SRAMEX, PASSWORD);
curtick = OSTimeGet();
sendtick = OSTimeGet();
//
char *ptemp = NULL;
u8 tesresult = 0;
float JBQJbuff[5];
float JBJSbuff[3];
u8 time_packbuf[23];
u16 QJ_1,QJ_2,QJ_3,QJ_4,QJ_5;
u16 JS_1,JS_2,JS_3;
while (1)
{
delay_ms(1000);
if ((OSTimeGet() - curtick) > (data.keepAliveInterval * 2000)) // uCosII 每秒200次tick
{
if (msgtypes == 0)
{
curtick = OSTimeGet();
msgtypes = PINGREQ;
}
}
if (connect_flag == 1)
{
#if 1
#endif
extern u8 MQTT_MacStateFreqFlag;
if (1)
{
if (ID != 0)
{
sprintf((char *)ptemp, "", );
datalen = strlen((const char *)ptemp);
payload_out[0] = 1; // 类型
payload_out[1] = datalen >> 8; // 长度高字节
payload_out[2] = datalen; // 长度低字节
datalen += 3;
payload_out_len = strlen((char *)ptemp)+3;
topicString.cstring = DEVICE_PUBLISH; //属性上报 发布
len = MQTTSerialize_publish((unsigned char *)buf, buflen, 0, req_qos, retained, msgid, topicString, payload_out, payload_out_len);
SEGGER_RTT_printf(0, "publish:%s\n", buf);
rc = transport_sendPacketBuffer(mysock, (unsigned char *)buf, len);
if (rc == len) //
SEGGER_RTT_printf(0, "send PUBLISH Successfully\r\n");
else
SEGGER_RTT_printf(0, "send PUBLISH failed\r\n");
}
delay_ms(20);
}
}
switch (msgtypes)
{
case CONNECT:
len = MQTTSerialize_connect((unsigned char *)buf, buflen, &data); //获取数据组长度 发送连接信息
rc = transport_sendPacketBuffer(mysock, (unsigned char *)buf, len); //发送 返回发送数组长度
SEGGER_RTT_printf(0, "-----------------------------------------len=%d rc =%d\r\n",len,rc);
if (rc == len)
{
PEout(0) = 1;
SEGGER_RTT_printf(0, "send CONNECT Successfully\r\n");
}
else{
SEGGER_RTT_printf(0, "send CONNECT failed\r\n");
break;
}
SEGGER_RTT_printf(0, "MQTT concet to server!\r\n");
msgtypes = 0;
break;
case CONNACK:
if (MQTTDeserialize_connack(&sessionPresent, &connack_rc, (unsigned char *)buf, buflen) != 1 || connack_rc != 0) //收到回执
{
SEGGER_RTT_printf(0, "Unable to connect, return code %d\r\n", connack_rc); //回执不一致,连接失败
}
else
{
SEGGER_RTT_printf(0, "MQTT is concet OK!\r\n"); //连接 成功
connect_flag = 1;
}
msgtypes = SUBSCRIBE; //连接成功 执行 订阅 操作
break;
case SUBSCRIBE:
//"$creq/86119304733965/cmd"
topicString.cstring = DEVICE_SUBSCRIBE;
len = MQTTSerialize_subscribe((unsigned char *)buf, buflen, 0, msgid, 1, &topicString, &req_qos);
rc = transport_sendPacketBuffer(mysock, (unsigned char *)buf, len);
if (rc == len)
SEGGER_RTT_printf(0, "send SUBSCRIBE Successfully\r\n");
else
{
SEGGER_RTT_printf(0, "send SUBSCRIBE failed\r\n");
t++;
if (t >= 10)
{
t = 0;
msgtypes = CONNECT;
}
else
msgtypes = SUBSCRIBE;
break;
}
SEGGER_RTT_printf(0, "client subscribe:[%s]\r\n", topicString.cstring);
msgtypes = 0;
break;
case SUBACK:
rc = MQTTDeserialize_suback(&submsgid, 1, &subcount, &granted_qos, (unsigned char *)buf, buflen); //有回执 QoS
SEGGER_RTT_printf(0, "granted qos is %d\r\n", granted_qos); //打印 Qos
msgtypes = 0;
break;
case PUBLISH:
rc = MQTTDeserialize_publish(&dup, &qos, &retained, &msgid, &receivedTopic, &payload_in, &payloadlen_in, (unsigned char *)buf, buflen); //服务器有推送信息
SEGGER_RTT_printf(0, "receivedTopic>>>>>: %s\r\n", buf);
topicString.cstring = DEVICE_PUBLISH; //属性上报 发布
p = (unsigned char *)strstr((char *)payload_in, "msgid") + 6;
SEGGER_RTT_printf(0, "\r\nmsgid:");
fnGetMsgid(p); //获取msgid
mesig_pack[32] = 0;
memcpy(MQTT_KeyPack, All_St.mqtt.MQTT_Key, sizeof(All_St.mqtt.MQTT_Key));
if ((u8 *)strstr((const char *)payload_in, "$cmd=reqtime"))
else if ((u8 *)strstr((const char *)payload_in, "$cmd=getstatus"))
else if ((u8 *)strstr((const char *)payload_in, "$cmd=getcmdversion"))
else if ((u8 *)strstr((const char *)payload_in, "$cmd=reboot"))
/*****************************************************************/
/*****************************************************************/
payload_out_len = strlen((char *)payload_out);
len = MQTTSerialize_publish((unsigned char *)buf, buflen, 0, req_qos, retained, msgid, topicString, payload_out, payload_out_len);
SEGGER_RTT_printf(0, "<<*****>>buf=%s,buflen=%d,req_qos=%d,retained=%d,msgid=%s,topicString=%s,payload_out=%s,payload_out_len=%d\n", buf, buflen, req_qos, retained, msgid, topicString, payload_out, payload_out_len);
rc = transport_sendPacketBuffer(mysock, (unsigned char *)buf, len);
if (rc == len) //
SEGGER_RTT_printf(0, "send PUBLISH Successfully\r\n");
else
SEGGER_RTT_printf(0, "send PUBLISH failed\r\n");
if (qos == 1)
{
SEGGER_RTT_printf(0, "publish qos is 1,send publish ack.\r\n"); // Qos为1,进行回执 响应
memset(buf, 0, buflen);
len = MQTTSerialize_ack((unsigned char *)buf, buflen, PUBACK, dup, msgid); // publish ack
rc = transport_sendPacketBuffer(mysock, (unsigned char *)buf, len); //
if (rc == len)
SEGGER_RTT_printf(0, "send PUBACK Successfully\r\n");
else
SEGGER_RTT_printf(0, "send PUBACK failed\r\n");
}
msgtypes = 0;
break;
case PUBACK:
SEGGER_RTT_printf(0, "PUBACK!\r\n"); //发布成功
msgtypes = 0;
break;
case PUBREC:
SEGGER_RTT_printf(0, "PUBREC!\r\n"); // just for qos2
msgtypes = 0;
break;
case PUBREL:
SEGGER_RTT_printf(0, "PUBREL!\r\n"); // just for qos2
break;
case PUBCOMP:
SEGGER_RTT_printf(0, "PUBCOMP!\r\n"); // just for qos2
break;
case PINGREQ:
len = MQTTSerialize_pingreq((unsigned char *)buf, buflen); //心跳
rc = transport_sendPacketBuffer(mysock, (unsigned char *)buf, len);
if (rc == len)
SEGGER_RTT_printf(0, "send PINGREQ Successfully\r\n");
else
SEGGER_RTT_printf(0, "send PINGREQ failed\r\n");
SEGGER_RTT_printf(0, "time to ping mqtt server to take alive!\r\n");
msgtypes = 0;
break;
case PINGRESP:
SEGGER_RTT_printf(0, "mqtt server Pong\r\n"); //心跳回执,服务有响应
msgtypes = 0;
break;
default:
break;
}
memset(buf, 0, buflen);
rc = MQTTPacket_read((unsigned char *)buf, buflen, transport_getdata); //轮询,读MQTT返回数据,
if (rc > 0) //如果有数据,进入相应状态。
{
msgtypes = rc;
}
}
transport_close(mysock);
SEGGER_RTT_printf(0, "mqtt thread exit.\r\n");
OSTaskDel(NULL);
}
1、首先初始化两个变量分别是host和port这是代表mqtt服务器的地址和端口号
2、mysock = transport_open((char *)host, port);该函数原型如下:
int transport_open(char* addr, int port)
{
int* sock = &mysock;
struct hostent *server;
struct sockaddr_in serv_addr;
static struct timeval tv;
int timeout = 1000;
fd_set readset;
fd_set writeset;
*sock = socket(AF_INET, SOCK_STREAM, 0);
if(*sock < 0)
SEGGER_RTT_printf(1,"[ERROR] Create socket failed\n");
server = gethostbyname(addr);
if(server == NULL)
SEGGER_RTT_printf(1,"[ERROR] Get host ip failed\n");
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
memcpy(&serv_addr.sin_addr.s_addr,server->h_addr,server->h_length);
if(connect(*sock,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)
{
SEGGER_RTT_printf(1,"[ERROR] connect failed\n");
return -1;
}
tv.tv_sec = 10; /* 1 second Timeout */
tv.tv_usec = 0;
setsockopt(mysock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout,sizeof(timeout));
return mysock;
}
重点是这里的 coonnect()函数,这里就是我们和lwip底层的通信相关联了,原型是
#define connect(a,b,c) lwip_connect(a,b,c),原型如下:
int lwip_connect(int s, const struct sockaddr *name, socklen_t namelen)
{
struct lwip_sock *sock;
err_t err;
const struct sockaddr_in *name_in;
sock = get_socket(s);
if (!sock) {
return -1;
}
/* check size, familiy and alignment of 'name' */
LWIP_ERROR("lwip_connect: invalid address", ((namelen == sizeof(struct sockaddr_in)) &&
((name->sa_family) == AF_INET) && ((((mem_ptr_t)name) % 4) == 0)),
sock_set_errno(sock, err_to_errno(ERR_ARG)); return -1;);
name_in = (const struct sockaddr_in *)(void*)name;
if (name_in->sin_family == AF_UNSPEC) {
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_connect(%d, AF_UNSPEC)\n", s));
err = netconn_disconnect(sock->conn);
} else {
ip_addr_t remote_addr;
u16_t remote_port;
inet_addr_to_ipaddr(&remote_addr, &name_in->sin_addr);
remote_port = name_in->sin_port;
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_connect(%d, addr=", s));
ip_addr_debug_print(SOCKETS_DEBUG, &remote_addr);
LWIP_DEBUGF(SOCKETS_DEBUG, (" port=%"U16_F")\n", ntohs(remote_port)));
err = netconn_connect(sock->conn, &remote_addr, ntohs(remote_port));
}
if (err != ERR_OK) {
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_connect(%d) failed, err=%d\n", s, err));
sock_set_errno(sock, err_to_errno(err));
return -1;
}
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_connect(%d) succeeded\n", s));
sock_set_errno(sock, 0);
return 0;
}
这里大家一直的时候注意下这个函数和你lwip底层的conncet函数一致就可以。
3、len = MQTTSerialize_disconnect((unsigned char *)buf, buflen);
rc = transport_sendPacketBuffer(mysock, (unsigned char *)buf, len);
if (rc == len) //
SEGGER_RTT_printf(0, "send DISCONNECT Successfully\r\n");
首先是断开连接,成功后再次连接mqtt服务器:
mysock = transport_open((char *)host, port);
然后设置data结构体里面的成员初始化:
data.clientID.cstring = CLIENT_ID;
data.keepAliveInterval = 70; //心跳时长
data.cleansession = 1;
data.username.cstring = USER_NAME;
4、关键的宏定义
//用户需要根据设备信息完善以下宏定义中的五元组内容
#define PRODUCT_KEY "xxxxxxxxxxxxxxxxxxxxxxxxxxx" //阿里云颁发的产品唯一标识,11位长度的英文数字随机组合
#define DEVICE_NAME "xxxxxxxxxx" //用户注册设备时生成的设备唯一编号,支持系统自动生成,也可支持用户添加自定义编号,产品维度内唯一
#define DEVICE_SECRET "xxxxxxxxxxxxxxxxxxxxxx" //设备密钥,与DeviceName成对出现,可用于一机一密的认证方案
#define CONTENT "clientId"DEVICE_NAME"deviceName"DEVICE_NAME"productKey"PRODUCT_KEY"timestamp789" //计算登录密码用
#define CLIENT_ID
DEVICE_NAME"|securemode=3,signmethod=hmacsha1,timestamp=789|" //客户端ID
#define USER_NAME DEVICE_NAME"&"PRODUCT_KEY
#define DEVICE_PUBLISH "xxxxx" //代表发布 不同服务器不一致
#define DEVICE_SUBSCRIBE "xxxxxxxx" //订阅的主题 不同服务器不一致
这里由于关系到涉密问题,所以全部用xxxxx代码,关于这几个东西不熟悉的,可以去阿里云开通一个免费账号注册一下,熟悉这几个宏的用处,这里就不详细解释。
5、接下来正式进入连接mqtt服务器:switch (msgtypes){}
5.1、case CONNECT:没什么好讲的 ,看注释就行
5.2、case CONNACK:没什么好讲的 ,看注释就行
5.3、case SUBSCRIBE:这个时候需要用到上面的宏定义了,topicString.cstring就是我们订阅的主题名字的字符串。
6、进入核心部分
rc = MQTTPacket_read((unsigned char *)buf, buflen, transport_getdata); //轮询,读MQTT返回数据,
if (rc > 0) //如果有数据,进入相应状态。
{
msgtypes = rc;
}
7、我们作为客户端,主要的功能订阅相关主题后,等待mqtt服务器发布相关主题的消息,这时候会进入case PUBLISH:这个状态机,会看到我有很多的if else,这是解析不同的payload,然后做出相对应的PUBLISH动作
关键函数:transport_sendPacketBuffer()
int transport_sendPacketBuffer(int sock, unsigned char* buf, int buflen)
{
int rc = 0;
rc = write(sock, buf, buflen);
return rc;
}
#define write(a,b,c) lwip_write(a,b,c)
int lwip_write(int s, const void *data, size_t size)
{
return lwip_send(s, data, size, 0);
}
int lwip_send(int s, const void *data, size_t size, int flags)
{
struct lwip_sock *sock;
err_t err;
u8_t write_flags;
size_t written;
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_send(%d, data=%p, size=%"SZT_F", flags=0x%x)\n",
s, data, size, flags));
sock = get_socket(s);
if (!sock) {
return -1;
}
if (sock->conn->type != NETCONN_TCP) {
#if (LWIP_UDP || LWIP_RAW)
return lwip_sendto(s, data, size, flags, NULL, 0);
#else /* (LWIP_UDP || LWIP_RAW) */
sock_set_errno(sock, err_to_errno(ERR_ARG));
return -1;
#endif /* (LWIP_UDP || LWIP_RAW) */
}
write_flags = NETCONN_COPY |
((flags & MSG_MORE) ? NETCONN_MORE : 0) |
((flags & MSG_DONTWAIT) ? NETCONN_DONTBLOCK : 0);
written = 0;
err = netconn_write_partly(sock->conn, data, size, write_flags, &written);
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_send(%d) err=%d written=%"SZT_F"\n", s, err, written));
sock_set_errno(sock, err_to_errno(err));
return (err == ERR_OK ? (int)written : -1);
}
这是发送函数的调用关系,应该可以看到最底层就是lwip的发送函数
总结:
到这,基于lwip的mqtt通信就大致讲完了,有些地方可能时间太久了导致我讲的有误可以帮我指出来,后面我再更新基于通信模组mqtt通信。
其实呢这篇文章主要目的还是让大家初识以下mqtt,想吃透mqtt可远远不够,但是大家不用灰心,你读完以后应该对mqtt大体的通信有了一个认识,万事开头难,很高兴大家已经迈出第一步了。