PHP'de MQTT kullanmanın standart yolu php-mqtt/client kütüphanesidir: Composer ile kurar, bir MqttClient oluşturur, connect() çağırır, ardından her yerden publish() ve uzun ömürlü bir CLI döngüsü içinde closure ile subscribe() yaparsınız. TCP veya TLS üzerinden MQTT 3.1 ve 3.1.1 konuşur, varsayılanların ötesinde PHP eklentisi gerektirmez ve resmi bir Laravel sarmalayıcısı vardır.
Bu eğitim publish işlemini (çoğu kişinin gözden kaçırdığı QoS 1 ayrıntısı dahil), sağlam bir subscriber'ı, kimlik bilgileriyle TLS'i, Last Will'i ve her parçanın nasıl test edileceğini ele alıyor.
Kurulum
php-mqtt/client, PHP 8 ve Composer gerektirir:
composer require php-mqtt/clientPHP bir broker ile birbirinden çok farklı iki şekilde konuşur ve bunları karıştırmak sorunların çoğuna yol açar:
| Rol | Nerede çalışır | Ömrü | Örnek |
|---|---|---|---|
| Publisher | Web isteği (PHP-FPM, Apache), cron, kuyruk job'u | Milisaniyeler içinde bağlan, publish et, bağlantıyı kes | Kullanıcı bir düğmeye tıkladığında komut göndermek |
| Subscriber | CLI süreci (php subscriber.php) | Saatler veya günler, denetimli | Sensör ölçümlerini veritabanına kaydetmek |
PHP-FPM worker'ları geri dönüştürülür ve istekler zaman aşımına uğrar; bu yüzden asla bir web isteği içinde aboneliği açık tutmaya çalışmayın.
Mesaj publish etme
<?php
require __DIR__ . '/vendor/autoload.php';
use PhpMqtt\Client\ConnectionSettings;
use PhpMqtt\Client\MqttClient;
$clientId = 'testmqtt-php-' . bin2hex(random_bytes(4));
$mqtt = new MqttClient('broker.connectmq.com', 1883, $clientId, MqttClient::MQTT_3_1_1);
$settings = (new ConnectionSettings)
->setConnectTimeout(5)
->setKeepAliveInterval(30);
$mqtt->connect($settings, true); // true = clean session
$payload = json_encode(['temp' => 21.5, 'ts' => time()]);
$mqtt->publish('testmqtt/php/sensors/room1', $payload, MqttClient::QOS_AT_LEAST_ONCE);
$mqtt->loop(true, true); // wait for the PUBACK, then return
$mqtt->disconnect();
echo "published\n";İki ayrıntı önemlidir:
- Rastgele client ID. Her istek ayrı bir bağlantıdır. Eşzamanlı iki istek aynı sabit ID'yi kullansaydı broker ilkinin bağlantısını keserdi; bkz. yinelenen client ID. Client ID oluşturucu iyi formatlar gösterir.
- QoS 1 veya 2 publish'ten sonra
loop(true, true).publish()mesajı yalnızca kuyruğa ekler; onay döngü içinde işlenir. İkinci argüman, bekleyen bir şey kalmadığındaloop()'un dönmesini sağlar. QoS 0'da doğrudan bağlantıyı kesebilirsiniz. MQTT QoS seviyeleri bu dengeyi açıklar.
İstek başına bir bağlantı açmak ara sıra gelen olaylar için sorun değildir. Bir sayfa her ziyarette publish ediyorsa veya mesajları toplu halde gönderiyorsanız, işi tek bir bağlantıyı açık tutan bir kuyruk worker'ına devredin; bu, Laravel'de MQTT yazısında gösterilen kalıptır.
Durum için retained mesajlar
publish()'in dördüncü argümanı retain bayrağıdır. Retained bir mesaj broker'da saklanır ve her yeni subscriber'a anında iletilir; bu da onu, bir PHP yönetim panelinden düzenlenen cihaz yapılandırması gibi "güncel durum" topic'leri için ideal kılar:
$config = ['interval' => 60, 'unit' => 'C'];
$mqtt->publish('testmqtt/php/devices/42/config', json_encode($config), MqttClient::QOS_AT_LEAST_ONCE, true);
// An empty retained payload deletes the stored message
$mqtt->publish('testmqtt/php/devices/42/config', '', MqttClient::QOS_AT_LEAST_ONCE, true);
$mqtt->loop(true, true);Retain'i olaylar için değil, durum için kullanın: retained bir "düğmeye basıldı" mesajı, sonradan abone olan her istemciye yeniden iletilir. Payload'ları zaman damgalı kompakt JSON olarak tutun ki tüketiciler durumun ne kadar eski olduğunu anlayabilsin.
CLI worker'da subscribe
Bir subscriber her topic filtresi için bir closure kaydeder ve ardından loop(true) çağırır; bu çağrı siz kesene kadar bloklar ve mesajları dağıtır:
<?php
require __DIR__ . '/vendor/autoload.php';
use PhpMqtt\Client\ConnectionSettings;
use PhpMqtt\Client\MqttClient;
$mqtt = new MqttClient('broker.connectmq.com', 1883, 'testmqtt-php-worker', MqttClient::MQTT_3_1_1);
$mqtt->connect((new ConnectionSettings)->setKeepAliveInterval(30), true);
$mqtt->subscribe('testmqtt/php/sensors/+', function (string $topic, string $message, bool $retained) {
$data = json_decode($message, true);
if (!is_array($data)) {
fwrite(STDERR, "invalid JSON on $topic\n");
return;
}
printf("%s%s temp=%s\n", $topic, $retained ? ' (retained)' : '', $data['temp'] ?? '?');
}, MqttClient::QOS_AT_LEAST_ONCE);
// Stop cleanly on Ctrl+C or a SIGTERM from systemd / Docker
pcntl_async_signals(true);
pcntl_signal(SIGINT, fn () => $mqtt->interrupt());
pcntl_signal(SIGTERM, fn () => $mqtt->interrupt());
$mqtt->loop(true);
$mqtt->disconnect();+ joker karakteri tek bir topic seviyesiyle eşleşir; yani bu filtre testmqtt/php/sensors/room1 alır ama testmqtt/php/sensors/room1/battery almaz. Dağıtımdan önce filtrelerinizi topic matcher ile kontrol edin.
Bir terminalde php subscriber.php, diğerinde php publish.php çalıştırın. Ölçümün milisaniyeler içinde ekrana yazıldığını görmelisiniz.
Bağlantı kopmalarını atlatmak
Haftalarca çalışan bir worker er ya da geç bağlantısını kaybeder: broker yeniden başlatmaları, NAT zaman aşımları, deploy'lar. Bu olduğunda kütüphane loop() içinden bir exception fırlatır. En basit sağlam kalıp, back-off ile yeniden bağlanan ve tekrar abone olan bir dış döngüdür:
<?php
require __DIR__ . '/vendor/autoload.php';
use PhpMqtt\Client\ConnectionSettings;
use PhpMqtt\Client\Exceptions\MqttClientException;
use PhpMqtt\Client\MqttClient;
$settings = (new ConnectionSettings)
->setKeepAliveInterval(30)
->setLastWillTopic('testmqtt/php/worker/status')
->setLastWillMessage('offline')
->setLastWillQualityOfService(MqttClient::QOS_AT_LEAST_ONCE)
->setRetainLastWill(true);
$delay = 1;
while (true) {
try {
$mqtt = new MqttClient('broker.connectmq.com', 1883, 'testmqtt-php-worker', MqttClient::MQTT_3_1_1);
$mqtt->connect($settings, true);
$mqtt->publish('testmqtt/php/worker/status', 'online', MqttClient::QOS_AT_LEAST_ONCE, true);
$mqtt->subscribe('testmqtt/php/sensors/+', function (string $topic, string $message) {
echo "$topic $message\n";
}, MqttClient::QOS_AT_LEAST_ONCE);
$delay = 1; // connected: reset back-off
$mqtt->loop(true);
} catch (MqttClientException $e) {
fwrite(STDERR, 'MQTT error: ' . $e->getMessage() . "\n");
sleep($delay);
$delay = min($delay * 2, 30);
}
}Worker temiz bir bağlantı kesme olmadan ölürse Last Will offline (retained) publish eder; worker ise her bağlantıdan sonra bunu online ile ezer. Bu, diğer servislerin abone olabileceği ucuz bir sağlık göstergesidir. Ayrıntılar MQTT Last Will yazısında.
Burada clean session kullanmak bilinçli bir tercihtir: worker her bağlantıda yeniden abone olur, dolayısıyla broker'ın bir şey saklamasına bağımlı değildir. Worker çevrimdışıyken mesajların kuyrukta beklemesi gerekiyorsa sabit bir client ID ile connect($settings, false) kullanın ve bunu kendi özel broker'ınızda test edin; paylaşımlı broker.connectmq.com endpoint'i çevrimdışı oturum tutmaz. Bkz. kalıcı oturumlar.
php-mqtt/client ayrıca setMaxReconnectAttempts() ve setDelayBetweenReconnectAttempts() (milisaniye) ile birlikte setReconnectAutomatically(true) sunar; job'lar arasında bağlantıyı açık tutan publisher'lar için kullanışlıdır.
TLS ve kimlik doğrulama
Production broker'ları 8883 portunda TLS ve kimlik bilgileri ister. Yalnızca port ve ayarlar değişir:
<?php
require __DIR__ . '/vendor/autoload.php';
use PhpMqtt\Client\ConnectionSettings;
use PhpMqtt\Client\MqttClient;
$settings = (new ConnectionSettings)
->setUsername(getenv('MQTT_USER'))
->setPassword(getenv('MQTT_PASS'))
->setUseTls(true)
->setTlsVerifyPeer(true)
->setTlsVerifyPeerName(true)
->setTlsSelfSignedAllowed(false);
// ->setTlsCertificateAuthorityFile('/path/to/ca.pem') for a private CA
$mqtt = new MqttClient('broker.connectmq.com', 8883, 'testmqtt-php-' . bin2hex(random_bytes(4)), MqttClient::MQTT_3_1_1);
$mqtt->connect($settings, true);
$mqtt->publish('testmqtt/php/secure', 'hello over TLS', MqttClient::QOS_AT_LEAST_ONCE);
$mqtt->loop(true, true);
$mqtt->disconnect();Açık bir CA dosyası verilmezse PHP'nin OpenSSL'i sistemin güven deposunu (veya php.ini içindeki openssl.cafile) kullanır. Peer doğrulamasını açık tutun; kapatmak gerçek sertifika sorunlarını gizler.
Laravel'de kullanım
Aynı geliştiriciler, bağlantı ayarlarını config/mqtt-client.php ve .env dosyasından okuyan ve bir facade sunan php-mqtt/laravel-client paketini de sürdürüyor:
composer require php-mqtt/laravel-client
php artisan vendor:publish --provider="PhpMqtt\Client\MqttClientServiceProvider" --tag="config"use PhpMqtt\Client\Facades\MQTT;
MQTT::publish('testmqtt/php/laravel/hello', 'Hello from Laravel');Abone olmak için bir Artisan komutu içinde MQTT::connection() çağırır ve loop(true)'yu orada çalıştırırsınız. Kalıbın tamamı (publish için kuyruklu job'lar, subscribe için denetimli bir komut ve testler) Laravel'de MQTT yazısında.
Sık karşılaşılan hatalar
| Belirti | Neden | Çözüm |
|---|---|---|
"Establishing a connection to the MQTT broker failed" mesajlı ConnectingToBrokerFailedException | Yanlış host veya port, firewall, şifresiz portta TLS (veya tersi) | Port ile setUseTls() değerini eşleştirin: 1883 şifresiz, 8883 TLS |
| CONNECT sonrası bağlantı reddediliyor | Hatalı kimlik bilgileri veya yetkisiz | Kullanıcı adı, parola ve ACL'leri kontrol edin |
| Worker birkaç saniyede bir bağlantıyı kaybediyor | İki süreç aynı client ID'yi paylaşıyor | Süreç başına benzersiz, istek başına rastgele ID'ler |
| QoS 1 mesajı kayıp | Betik PUBACK'ten önce bağlantıyı kesti | disconnect() öncesinde loop(true, true) |
Call to undefined function pcntl_async_signals() | pcntl kullanılamıyor (Windows veya web SAPI) | Sinyal yönetimini yalnızca Unix'te CLI'da kullanın |
Test edin
PHP kodunuzun publish ettiklerini bir terminal subscriber'ı veya online MQTT istemcisi ile izleyin:
mosquitto_sub -h broker.connectmq.com -p 1883 -t 'testmqtt/php/#' -vArdından subscriber'ınıza, JSON kontrolünün çalıştığını kanıtlamak için geçersiz olanı da dahil olmak üzere mesaj gönderin:
mosquitto_pub -h broker.connectmq.com -p 1883 -t 'testmqtt/php/sensors/room1' -m '{"temp":22.1}' -q 1
mosquitto_pub -h broker.connectmq.com -p 1883 -t 'testmqtt/php/sensors/room1' -m 'not json' -q 1Son olarak worker'ı kill -9 ile sonlandırın ve retained offline durumunun testmqtt/php/worker/status üzerinde göründüğünü doğrulayın. Daha fazla komut satırı tarifi için mosquitto_pub ve mosquitto_sub örnekleri yazısına bakın.
PHP topic'lerini online istemcide izleyin
PHP'nin süreç modeline saygı gösterdiğinizde php-mqtt/client ile PHP'de MQTT oldukça kolaydır: istekler içinden benzersiz client ID'lerle ve QoS 1 için loop(true, true) ile publish edin, yalnızca kendi kendine yeniden bağlanan denetimli CLI worker'larından abone olun ve canlıya çıkmadan önce TLS ile kimlik bilgilerini etkinleştirin.
Sıkça sorulan sorular
PHP, MQTT topic'lerine abone olabilir mi?
Evet, ama normal bir web isteği içinde değil. Bir subscriber'ın bağlı kalması gerekir; bu yüzden onu $mqtt->loop(true) çağıran, systemd, Supervisor veya Docker tarafından denetlenen uzun ömürlü bir CLI betiği (php subscriber.php) olarak çalıştırın. Web istekleri yalnızca publish etmelidir.
php-mqtt/client MQTT 5'i destekliyor mu?
Bu yazının yazıldığı sırada MQTT 3.1 ve 3.1.1'i uygular; bunları yaygın broker'ların hepsi kabul eder. Reason code'lar veya user property'ler gibi MQTT 5 özelliklerine ihtiyacınız varsa sistemin o kısmını başka bir dilde çözün ya da projenin daha yeni sürümlerine bakın.
PHP'den gönderdiğim QoS 1 mesajı neden hiç ulaşmıyor?
publish(), QoS 1 ve 2 mesajlarını yalnızca kuyruğa ekler; onay loop() içinde işlenir. Publish'ten sonra $mqtt->loop(true, true) çağırın ki istemci bağlantıyı kesmeden önce PUBACK'i beklesin; aksi halde betik daha önce sonlanabilir.

