mqtt協(xié)議是時(shí)下比較好用的發(fā)布/訂閱模式協(xié)議。但是在具體使用的過程中,使用PHP的mqtt擴(kuò)展會(huì)遇到一些奇葩問題,這里是踩坑之后的總結(jié)。這里假設(shè)讀者初步了解mqtt協(xié)議。
問題
- 使用基礎(chǔ)用法,連接之后直接發(fā)布,有可能會(huì)出現(xiàn)消息沒有發(fā)出去的情況。
經(jīng)過抓包之后發(fā)現(xiàn),發(fā)送失敗是因?yàn)閜hp擴(kuò)展在發(fā)送connect請(qǐng)求后,沒有等待回應(yīng),直接發(fā)送publish請(qǐng)求然后斷開連接。mqtt服務(wù)端發(fā)送connect ack包后沒有收到tcp的ack包,當(dāng)作連接失敗,publish請(qǐng)求被拋棄。本地環(huán)境網(wǎng)絡(luò)環(huán)境好,所以mqtt服務(wù)端可以收到connect ack包,發(fā)送成功;但是互聯(lián)網(wǎng)環(huán)境就會(huì)有比較大的概率發(fā)送失敗了。
解決方法為在確認(rèn)收到connect ack包后再斷開連接:
<?php
$client = new Mosquitto\Client();
$client->isok=false;
$client->onConnect(function() use ($client) {
$client->isok=true;
});
$client->connect("127.0.0.1", 1883, 5);
$client->publish('/hello', "Hello from PHP at " . date('Y-m-d H:i:s'));
$client->publish('/hello', "Hello from PHP at " . date('Y-m-d H:i:s'));
while (!$client->isok) {
$client->loop();
}
$client->disconnect();
絕大多數(shù)情況下這樣寫就可以了,但是極端情況下還是可能會(huì)發(fā)送失敗。如果要求一定發(fā)送成功,需要修改qos為1或者2。
- 使用以上方法,發(fā)送qos>0的消息會(huì)失敗。
原因是qos>0的消息要求服務(wù)器回復(fù)publish ack包,而上述方法在確認(rèn)連接之后就斷開連接了,publish ack包沒有確認(rèn)收到,服務(wù)端就將此消息拋棄了。解決方案是累加消息次數(shù),收到ack包后將累加器減1,累加器到0了才斷開連接:
<?php
$client = new Mosquitto\Client();
$client->num=0;
$client->connect("127.0.0.1", 1883, 5);
$client->onPublish(function() use ($client) {
$client->num--;
});
$client->publish('/hello', "Hello from PHP1 at " . date('Y-m-d H:i:s'));
$client->publish('/hello', "Hello from PHP2 at " . date('Y-m-d H:i:s'),1);
$client->publish('/hello', "Hello from PHP3 at " . date('Y-m-d H:i:s'),2);
$client->num+=3;
while ($client->num>0) {
$client->loop();
}
$client->disconnect();
這種方法對(duì)qos=0的消息無效,qos=0時(shí)沒有ack包,onPublish函數(shù)會(huì)馬上執(zhí)行,可能連接還沒有建立就斷開了,出現(xiàn)問題1。
總結(jié)
大多數(shù)情況下,使用qos=0,使用相對(duì)簡(jiǎn)單的寫法就可以了。一般mqtt服務(wù)端和php服務(wù)器都在一個(gè)內(nèi)網(wǎng)中,異常情況基本可以忽略。
如果想寫個(gè)更可靠的或者說更好的程序,那么最好的解決方案是:連接后進(jìn)行阻塞,發(fā)一個(gè)消息后累加器+1,回調(diào)中-1,處理函數(shù)末尾阻塞確認(rèn)消息都發(fā)完了才結(jié)束。完整代碼如下:
<?php
$client = new Mosquitto\Client();
$client->num=0;
$client->isok=false;
$client->onConnect(function() use ($client) {
$client->isok=true;
});
$client->onPublish(function() use ($client) {
$client->num--;
});
while ($client->num>0) {
$client->loop();
}
$client->connect("127.0.0.1", 1883, 5);
$client->publish('/hello', "Hello from PHP1 at " . date('Y-m-d H:i:s'));
$client->publish('/hello', "Hello from PHP2 at " . date('Y-m-d H:i:s'),1);
$client->publish('/hello', "Hello from PHP3 at " . date('Y-m-d H:i:s'),2);
$client->num+=3;
while ($client->num>0) {
$client->loop();
}
$client->disconnect();