使用系統版本Ubuntu14.04(該文章代碼嚴謹性並不高,主要用於瞭解MQTT)
先介紹一下MQTT:
MQTT(Message Queuing Telemetry Transport,消息隊列遙測傳輸)是IBM開發的一個即時通訊協議,有可能成爲物聯網的重要組成部分。該協議支持所有平臺,幾乎可以把所有聯網物品和外部連接起來,被用來當做傳感器和制動器(比如通過Twitter讓房屋聯網)的通信協議。
MQTT協議是爲大量計算能力有限,且工作在低帶寬、不可靠的網絡的遠程傳感器和控制設備通訊而設計的協議,它具有以下主要的幾項特性:
1、使用發佈/訂閱消息模式,提供一對多的消息發佈,解除應用程序耦合;
2、對負載內容屏蔽的消息傳輸;
3、使用 TCP/IP 提供網絡連接;
4、有三種消息發佈服務質量:
“至多一次”,消息發佈完全依賴底層 TCP/IP 網絡。會發生消息丟失或重複。這一級別可用於如下情況,環境傳感器數據,丟失一次讀記錄無所謂,因爲不久後還會有第二次發送。
“至少一次”,確保消息到達,但消息重複可能會發生。
“只有一次”,確保消息到達一次。這一級別可用於如下情況,在計費系統中,消息重複或丟失會導致不正確的結果。
5、小型傳輸,開銷很小(固定長度的頭部是 2 字節),協議交換最小化,以降低網絡流量;
6、使用 Last Will 和 Testament 特性通知有關各方客戶端異常中斷的機制;
使用客戶端庫的應用程序通常使用類似的結構:
1.創建一個客戶端對象;
2.設置連接MQTT服務器的選項;
3.如果多線程(異步模式)操作被使用則設置回調函數(詳見 Asynchronous vs synchronous client applications);
4.訂閱客戶端需要接收的任意話題;
5.重複以下操作直到結束:
a.發佈客戶端需要的任意信息;
b.處理所有接收到的信息;
6.斷開客戶端連接;
7.釋放客戶端使用的所有內存。
接下來開始談LinuxC編程簡單實現MQTT
在linux下用C語言實現MQTT通信,要用到一系列MQTT函數,這些函數在Linux自帶庫中是沒有的。
所以第一步:安裝Paho C庫
在git下下載paho C庫git clone https://github.com/eclipse/paho.mqtt.c.git
cd paho.mqtt.c
make//編譯
sudo make install//安裝
在編譯時可能出現某些頭文件的缺失,這裏我就不一一列舉了,問百度把哈哈哈
在執行make之後,在build/output下能看到這些動態庫,這些庫中有我們將要用到的函數的定義
zhanghang@Ubuntu-14:~/MQTT/paho.mqtt.c/build/output$ ls
libpaho-mqtt3a.so libpaho-mqtt3as.so libpaho-mqtt3c.so libpaho-mqtt3cs.so paho_c_version
libpaho-mqtt3a.so.1 libpaho-mqtt3as.so.1 libpaho-mqtt3c.so.1 libpaho-mqtt3cs.so.1 samples
libpaho-mqtt3a.so.1.0 libpaho-mqtt3as.so.1.0 libpaho-mqtt3c.so.1.0 libpaho-mqtt3cs.so.1.0 test
在這裏說一下這裏面的各個動態庫的作用:
paho-mqtt3a : 一般實際開發中就是使用這個,a表示的是異步消息推送(asynchronous)。
paho-mqtt3as : as表示的是 異步+加密(asynchronous+OpenSSL)。
paho-mqtt3c : c 表示的應該是同步(Synchronize),一般性能較差,是發送+等待模式。
paho-mqtt3cs : 同上,增加了一個OpenSSL而已。
在samples中還會有一些示例代碼;
zhanghang@Ubuntu-14:~/MQTT/paho.mqtt.c/build/output/samples$ ls
MQTTAsync_publish MQTTClient_publish MQTTClient_subscribe paho_cs_pub paho_c_sub
MQTTAsync_subscribe MQTTClient_publish_async paho_c_pub paho_cs_sub
現在給發佈端的C代碼:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
#include<unistd.h>
#include"MQTTClient.h"
int main(int argc,char **argv)
{
char *address="tcp://localhost:1883";
char *client_id="publish_client";
char *topic="mqtt_examples";
char buf[1024];
const int time_out=10000;
int rv;
int QOS=1;
MQTTClient client;
MQTTClient_connectOptions conn_opts=MQTTClient_connectOptions_initializer;
MQTTClient_message publish_msg=MQTTClient_message_initializer;
MQTTClient_deliveryToken token;
conn_opts.keepAliveInterval=60;
conn_opts.cleansession=1;
MQTTClient_create(&client,address,client_id,MQTTCLIENT_PERSISTENCE_NONE,NULL);
if((rv=MQTTClient_connect(client,&conn_opts))!=MQTTCLIENT_SUCCESS)
{
printf("MQTTClient_connect failure:%s\n",strerror(errno));
return 0;
}
publish_msg.qos=QOS;
publish_msg.retained=0;
while(1)
{
printf("enter the message you want to send\n");
fgets(buf,sizeof(buf),stdin);
publish_msg.payload=(void *)buf;
publish_msg.payloadlen=strlen(buf);
MQTTClient_publishMessage(client,topic,&publish_msg,&token);
printf("waiting for %d seconds for publication of %s on topic %s for client with CLIENTID :%s\n",time_out/1000,buf,topic,client_id);
rv=MQTTClient_waitForCompletion(client,token,time_out);
printf("Message with delivery token %d delivered\n",rv);
printf("%s\n",buf);//用於測試
sleep(3);
}
}
注意編譯時,如果沒將相應的頭文件和動態庫放到編譯器默認尋找的位置需要加上相應的鏈接選項,如下:
zhanghang@Ubuntu-14:~$ gcc mqtt_publish.c -o mqtt_publish -lpaho-mqtt3c -L ./MQTT/paho.mqtt.c/ -I ./MQTT/paho.mqtt.c/src/
運行如下
zhanghang@Ubuntu-14:~$ ./mqtt_publish
enter the message you want to send
採用交互式傳遞類似網絡soket的傳遞信息,便於理解
下面給出一個簡單的訂閱端程序:
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include"MQTTClient.h"
int main()
{
char *address="tcp://localhost:1883";
char *client_id="client_sub";
char *payload="mqtt_examples";
int rv,i;
char *ptr=NULL;
char *topic=NULL;
int topic_len;
MQTTClient client;
MQTTClient_connectOptions conn_opts=MQTTClient_connectOptions_initializer;
MQTTClient_deliveryToken token;
MQTTClient_message *receive_msg=NULL;
conn_opts.keepAliveInterval=60;
conn_opts.cleansession=1;
if((rv=MQTTClient_create(&client,address,client_id,MQTTCLIENT_PERSISTENCE_NONE,NULL))<0)
{
printf("MQTTClient_create failure:%s\n",strerror(errno));
return 0;
}
printf("MQTTClient_create successfully\n");
if((rv=MQTTClient_connect(client,&conn_opts))!=MQTTCLIENT_SUCCESS)
{
printf("MQTTClient_connect failure:%s\n",strerror(errno));
return 0;
}
printf("MQTTClient_connect successfuly\n");
MQTTClient_subscribe(client,payload,1);
/* if((rv=MQTTClient_receive(client,&topic,&topic_len,&receive_msg,5000))!=MQTTCLIENT_SUCCESS)
{
printf("MQTTClient_receive failure:%s\n",strerror(errno));
return 0;
}
printf("MQTTClient_receive successfully\n");*/
//receive 函數放在外面傳遞信息不會改變
while(1)
{
if((rv=MQTTClient_receive(client,&topic,&topic_len,&receive_msg,100000))!=MQTTCLIENT_SUCCESS)//最後一個參數是超時時間,單位是毫秒
{
printf("MQTTClient_receive failure:%s\n",strerror(errno));
break;
}
printf("MQTTClient_receive successfully\n");
ptr=receive_msg->payload;
printf("Topic:%s\nTopic_len:%d\nmsg:",topic,topic_len);
for(i=0;i<receive_msg->payloadlen;i++)
{
putchar(*ptr++);
}
printf("\nmsg_len:%d\nmsg_id:%d\n",receive_msg->payloadlen,receive_msg->msgid);
sleep(3);
}
printf("end\n");
MQTTClient_disconnect(client, 10000);
MQTTClient_destroy(&client);
return 0;
}
訂閱端訂閱了話題mqtt_examples,同樣採用循環不斷接受消息;
編譯:
zhanghang@Ubuntu-14:~$ gcc mqtt_subscribe.c -o mqtt_subscribe -lpaho-mqtt3c -L ./MQTT/paho.mqtt.c/build/output/
**注意:在運行時也有可能出現找不到動態庫,因爲編譯是默認動態編譯,在運行時纔會加載動態庫
解決辦法:假設需要的庫在/test/lib下則可以:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/test/lib //該方法重啓會失效
如果有sudo去權限也可以將這句話加到~/.bashrc中,重啓生效**
此時我們分別在兩個終端運行publish和subsceibe:
zhanghang@Ubuntu-14:~$ ./mqtt_publish
enter the message you want to send
zhanghang@Ubuntu-14:~$ ./mqtt_subscribe
MQTTClient_create successfully
MQTTClient_connect successfuly
在publish端發送信息,可在subscrib端收到信息:
zhanghang@Ubuntu-14:~$ ./mqtt_publish
enter the message you want to send
hello
waiting for 10 seconds for publication of hello
on topic mqtt_examples for client with CLIENTID :publish_client
Message with delivery token 0 delivered
hello
hienter the message you want to send
waiting for 10 seconds for publication of hi
on topic mqtt_examples for client with CLIENTID :publish_client
Message with delivery token 0 delivered
hi
enter the message you want to send
it is me
waiting for 10 seconds for publication of it is me
on topic mqtt_examples for client with CLIENTID :publish_client
Message with delivery token 0 delivered
it is me
enter the message you want to send
zhanghang@Ubuntu-14:~$ ./mqtt_subscribe
MQTTClient_create successfully
MQTTClient_connect successfuly
MQTTClient_receive successfully
Topic:mqtt_examples
Topic_len:13
msg:hello
msg_len:6
msg_id:1
MQTTClient_receive successfully
Topic:mqtt_examples
Topic_len:13
msg:hi
msg_len:3
msg_id:2
MQTTClient_receive successfully
Topic:mqtt_examples
Topic_len:13
msg:it is me
msg_len:9
msg_id:3
使用mosquitto和mqtt.fx軟件測試無論是publish還是subscribe都能通過
下一節詳細講解裏面用到的一些函數。
(在此處學習過程中遇到過一些問題:在安裝paho-mqtt.c後,編譯時可能會出現頭文件缺失等問題,這種問題百度上有一堆解答,但是介於MQTT系列函數等的特殊性,在網上有關mqttC編程的案例比較少,同時發生錯誤解答也會比較少,不像網絡socket等的一些函數,網上有一堆,所以還是要在以往的學習中積累一些經驗,遇到問題解決的辦法也會多一些,在成功編譯mqtt-paho.c後的make install 主要是將相應的庫和頭文件移到相應的編譯器默認尋找的地方,方便編譯和運行。但是由於這個命令需要sudo 權限,如果沒有權限或者不願意用權限,可以像我一樣用鏈接選項和export…
在建立publish和subscribe通信時,publish端發送的消息在subscribe端可能會出現多餘字符串“Packet.c”或者其一部分(在源字符串後面)(在直接打印receive_msg->payload時),使用mqtt.fx作爲publish也會出現,解決辦法(既然知道有效字符穿的長度->payloadlen,可以用循環字符打印,如上程序所示。)