Bir PHP MQTT client, mevcut web uygulamanızın yığınınıza yeni bir dil eklemeden cihazlara komut göndermesini ve verilerine tepki vermesini sağlar. Bu eğitimde php-mqtt/client paketini kullanarak bir web isteğinden publish edecek, uzun ömürlü bir subscriber worker çalıştıracak, ikisini de TLS ile güvence altına alacak ve worker'ı üretimde canlı tutacaksınız.
Bir PHP MQTT Client için Birbirinden Çok Farklı İki Görev
PHP'nin istek modeli MQTT'yi nasıl kullanacağınızı belirler; bu yüzden iki durumu baştan ayırın:
| Görev | Ömür | Desen |
|---|---|---|
| Kullanıcı bir düğmeye tıkladığında komut publish etmek | Tek bir HTTP isteği | Bağlan, publish et, bağlantıyı kes |
| Cihazların gönderdiği her mesaja tepki vermek | Sonsuza kadar | Bloklayan döngüye sahip ayrı bir CLI worker |
Bir web isteği içinde subscribe olmaya çalışmak en yaygın hatadır. PHP-FPM worker'ları milisaniyeler içinde bitmek üzere tasarlanmıştır; bir subscription ise günlerce bağlı kalan bir süreç gerektirir. Subscriber'ları systemd, Supervisor veya container çalışma ortamınız tarafından denetlenen bir CLI sürecinde tutun, web isteklerini ise kısa publish'lerle sınırlayın.
php-mqtt/client Kurulumu
php-mqtt/client, TLS desteğine sahip, MQTT 3.1 ve 3.1.1'in saf PHP uygulamasıdır; GitHub'da sürdürülür ve Composer ile kurulur. Çoğu derlemede bulunan standart soket ve OpenSSL desteğinin ötesinde bir PHP eklentisine ihtiyaç duymaz.
composer require php-mqtt/client
Paketin README dosyası resmi php-mqtt/client dokümantasyonudur ve tüm bağlantı ayarlarını listeler. Diğer dillerdeki client'larla karşılaştırma için MQTT client kütüphaneleri rehberine bakın.
Ortak Bağlantı Ayarları
Bağlantı ayrıntılarını tek bir yerde toplayın. Örnek, broker.connectmq.com adresindeki public MQTT test broker'ını 8883 portunda TLS üzerinden hedefler ve ortam değişkenleri tanımlandığında otomatik olarak özel kimlik bilgilerine geçer.
mqtt.php:
<?php
require __DIR__ . '/vendor/autoload.php';
use PhpMqtt\Client\ConnectionSettings;
use PhpMqtt\Client\MqttClient;
const MQTT_HOST = 'broker.connectmq.com';
const MQTT_PORT = 8883;
function mqtt_topic_prefix(): string
{
return getenv('MQTT_TOPIC_PREFIX') ?: 'connectmq/demo/php-5e19c2';
}
function mqtt_client(string $role): MqttClient
{
$clientId = getenv('MQTT_CLIENT_ID') ?: sprintf('php-%s-%s', $role, bin2hex(random_bytes(4)));
return new MqttClient(MQTT_HOST, MQTT_PORT, $clientId, MqttClient::MQTT_3_1_1);
}
function mqtt_settings(bool $reconnect = false): ConnectionSettings
{
$settings = (new ConnectionSettings())
->setUseTls(true)
->setTlsVerifyPeer(true)
->setTlsVerifyPeerName(true)
->setConnectTimeout(10)
->setSocketTimeout(5)
->setKeepAliveInterval(30);
if ($user = getenv('MQTT_USERNAME')) {
$settings = $settings
->setUsername($user)
->setPassword(getenv('MQTT_PASSWORD') ?: null);
}
if ($reconnect) {
$settings = $settings
->setReconnectAutomatically(true)
->setMaxReconnectAttempts(10)
->setDelayBetweenReconnectAttempts(3000);
}
return $settings;
}
ConnectionSettings değiştirilemez (immutable) bir nesnedir: her setter yeni bir örnek döndürür; kodun $settings değişkenine yeniden atama yapmasının nedeni budur. TLS doğrulaması, ConnectMQ'nun sunduğu Let's Encrypt sertifikasına zaten güvenen sistem CA deposunu kullanır.
Bir Web İsteğinden Publish Etme
Bu uç nokta bir form gönderimini alır ve bir cihaza komut publish eder. QoS 1 ile broker'ın mesajı onaylaması gerekir ve php-mqtt/client bu onayı yalnızca kendi döngüsü içinde işler; bu yüzden kod, bağlantıyı kesmeden önce giden kuyruk boşalana kadar döngüyü çalıştırır.
public/command.php:
<?php
require __DIR__ . '/../mqtt.php';
use PhpMqtt\Client\Exceptions\MqttClientException;
use PhpMqtt\Client\MqttClient;
$device = preg_replace('/[^a-z0-9_-]/i', '', $_POST['device'] ?? '');
$action = in_array($_POST['action'] ?? '', ['on', 'off'], true) ? $_POST['action'] : null;
if ($device === '' || $action === null) {
http_response_code(400);
exit('Invalid device or action');
}
$topic = mqtt_topic_prefix() . "/devices/{$device}/set";
$payload = json_encode(['action' => $action, 'ts' => time()]);
try {
$mqtt = mqtt_client('web');
$mqtt->connect(mqtt_settings(), true);
$mqtt->publish($topic, $payload, MqttClient::QOS_AT_LEAST_ONCE);
$mqtt->loop(true, true); // PUBACK alınana kadar bekle
$mqtt->disconnect();
echo "Sent {$action} to {$device}";
} catch (MqttClientException $e) {
error_log('MQTT publish failed: ' . $e->getMessage());
http_response_code(502);
echo 'Device gateway unavailable';
}
device üzerindeki girdi temizlemeye dikkat edin. Kullanıcılar bir topic adına asla #, + veya / enjekte edememelidir; aksi halde kendilerine ait olmayan cihazlara erişebilirler.
İstek başına bir TLS bağlantısı gecikme ekler; tipik olarak bir veya iki handshake gidiş-dönüşü. Dakikada birkaç komut gönderen bir yönetim paneli için bu sorun değildir. PHP'den yüksek hacimli publish için işi bir kuyruğa (Redis, bir veritabanı tablosu, Laravel kuyrukları) aktarın ve tek bir MQTT bağlantısını tutan uzun ömürlü bir worker'ın kuyruğu boşaltmasına izin verin.
Uzun Ömürlü Bir PHP MQTT Subscriber
Subscriber bir CLI betiğidir. Bir wildcard ile subscribe olur, JSON'u çözer ve SIGTERM aldığında düzgünce durur; böylece systemd veya Docker, bir mesajı işlemenin ortasında kaybetmeden onu yeniden başlatabilir.
bin/subscriber.php:
<?php
require __DIR__ . '/../mqtt.php';
use PhpMqtt\Client\Exceptions\MqttClientException;
use PhpMqtt\Client\MqttClient;
pcntl_async_signals(true);
$mqtt = mqtt_client('worker');
$stop = function () use ($mqtt) {
fwrite(STDOUT, "Shutting down...\n");
$mqtt->interrupt();
};
pcntl_signal(SIGINT, $stop);
pcntl_signal(SIGTERM, $stop);
$filter = mqtt_topic_prefix() . '/devices/+/telemetry';
try {
$mqtt->connect(mqtt_settings(reconnect: true), true);
$mqtt->subscribe($filter, function (string $topic, string $message, bool $retained) {
$parts = explode('/', $topic);
$device = $parts[count($parts) - 2] ?? 'unknown';
$data = json_decode($message, true);
if (!is_array($data)) {
fwrite(STDERR, "Ignoring non-JSON payload on {$topic}\n");
return;
}
// Kendi kalıcılık katmanınızla değiştirin, ör. PDO insert veya bir framework modeli
printf("[%s] %s%s %s\n", date('c'), $device, $retained ? ' (retained)' : '', json_encode($data));
}, MqttClient::QOS_AT_LEAST_ONCE);
fwrite(STDOUT, "Subscribed to {$filter}\n");
$mqtt->loop(true);
$mqtt->disconnect();
} catch (MqttClientException $e) {
fwrite(STDERR, 'MQTT error: ' . $e->getMessage() . "\n");
exit(1);
}
Çalıştırın:
php bin/subscriber.php
Başka bir terminalde cihaz rolünü üstlenin:
mosquitto_pub -h broker.connectmq.com -p 1883 -i php-sim-pump1 \
-t "connectmq/demo/php-5e19c2/devices/pump1/telemetry" \
-m '{"flow": 12.4, "pressure": 2.1}'
pcntl eklentisi Linux'taki PHP CLI'da bulunur, ancak genellikle PHP-FPM altında veya Windows'ta bulunmaz; bu da subscriber'ı bir CLI süreci olarak tutmak için bir neden daha.
Worker'ı Üretimde Çalıştırma
/etc/systemd/system/php-mqtt-worker.service:
[Unit]
Description=PHP MQTT subscriber
After=network-online.target
Wants=network-online.target
[Service]
User=www-data
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/php bin/subscriber.php
EnvironmentFile=/etc/php-mqtt-worker.env
Restart=always
RestartSec=5
KillSignal=SIGTERM
[Install]
WantedBy=multi-user.target
/etc/php-mqtt-worker.env (mod 600) özel broker kimlik bilgilerini tutar:
MQTT_USERNAME=YOUR_USERNAME
MQTT_PASSWORD=YOUR_PASSWORD
MQTT_CLIENT_ID=YOUR_CLIENT_ID
MQTT_TOPIC_PREFIX=acme/site1
Uzun süre çalışan PHP süreçlerinin kendine özgü hata biçimleri vardır. Worker'a güvenmeden önce bu kontrol listesinden geçin:
- Bellek. Büyük dizileri unset edin, statik özelliklerde veri biriktirmekten kaçının ve systemd'nin temiz bir süreç başlatması için birkaç saat sonra çıkmayı düşünün.
- Veritabanı bağlantıları. PDO bağlantıları worker boştayken zaman aşımına uğrar; tek bir bağlantıyı sonsuza kadar tutmak yerine hata durumunda yeniden bağlanın.
- Benzersiz client ID'ler. Web publisher'ı ve worker farklı client ID'lere ihtiyaç duyar. İki süreç aynı ID'yi paylaşırsa broker eskisinin bağlantısını keser ve süreçler birbirlerini döngü halinde düşürür.
- Yeniden bağlanma limitleri.
setMaxReconnectAttempts(10)ile döngü eninde sonunda bir exception fırlatır; denetleyicinin onu yeniden başlatması için sıfır olmayan bir kodla çıkmasına izin verin. - Kalıcı oturumlar. Worker'ın yeniden başlatmalar sırasında mesaj kaçırmaması gerekiyorsa, clean session kapalı ve sabit bir client ID ile bağlanın ve QoS 1 ile subscribe olun. Kalıcı oturum rehberi bu ödünleşimi açıklar.
Özel bir ConnectMQ broker'ında client ID'ler size verilen kimlik bilgilerine bağlıdır; bu yüzden aynı anda bağlanan her süreç için bir kimlik bilgisi seti planlayın.
PHP'den QoS, Retain ve Last Will Seçimi
Bir web uygulamasından gönderilen komutlar için QoS 1 makul varsayılandır: broker alındıyı onaylar ve cihaz, arayüzün gerçekte ne olduğunu göstermesi için ortaya çıkan durumu kendi topic'inde raporlamalıdır. Komutlarda retain bayrağından kaçının. Retained bir on, cihaz her yeniden bağlandığında, belki saatler sonra, ona yeniden oynatılır; MQTT retained mesajları rehberinin açıkladığı gibi retain eylemler için değil durum içindir.
Subscriber worker kendi sağlık durumunu bir last will ile duyurabilir. Worker'ın bağlantı ayarlarına ->setLastWillTopic(mqtt_topic_prefix() . '/workers/php/status'), ->setLastWillMessage('offline'), ->setLastWillQualityOfService(1) ve ->setRetainLastWill(true) ekleyin, ardından bağlandıktan hemen sonra retained bir online publish edin. Worker çökerse veya ağını kaybederse broker onun adına offline publish eder; izleme sisteminiz de sessizlikten tahmin yürütmek yerine bu topic üzerinden uyarı verebilir. Ücretsiz ConnectMQ Serverless planı QoS 0 ve 1'i ve 256 KB'a kadar mesajları destekler; bu, bir PHP uygulamasından gelen komut ve telemetri payload'larını rahatça karşılar.
Laravel ve Symfony Notları
Aynı kod framework'lere de doğrudan uyar. Laravel'de subscriber'ı bir Artisan komutuna (php artisan mqtt:listen) sarın ve Supervisor altında çalıştırın; controller'dan değil, kuyruğa alınmış bir job'dan publish edin. Symfony'de aynı rolü bir console komutu üstlenir. Bu tarzı tercih ederseniz, topluluk paketi php-mqtt/laravel-client aynı kütüphanenin üzerine yapılandırma ve bir facade ekler.
Sorun Giderme
| Hata | Olası neden |
|---|---|
TLS hatasıyla ConnectingToBrokerFailedException |
TLS için yanlış port (8883 kullanın) veya sunucudaki CA paketi güncel değil |
| Hatalı kimlik bilgileriyle bağlantı reddedildi | Worker'ın ortamında MQTT_USERNAME veya MQTT_PASSWORD eksik |
| Publish dönüyor ama cihaz mesajı hiç almıyor | Betik, döngü QoS 1 onayını işlemeden önce sonlandı |
| Worker birkaç saniyede bir yeniden bağlanıyor | Başka bir süreç aynı client ID'yi kullanıyor |
Call to undefined function pcntl_signal |
FPM altında veya pcntl eklentisi olmadan çalışıyor |
ConnectMQ ile Başlayın
PHP uygulamanıza birkaç dakika içinde özel ve güvenli bir broker kazandırın. app.connectmq.com üzerinde kredi kartı gerekmeden ücretsiz bir Serverless broker oluşturun, üretilen kimlik bilgilerini ortam dosyanıza koyun ve publish'lerinizi test etmek için portalın tarayıcı içi client'ını kullanın. Yönetilen bir MQTT broker, TLS'i, ölçeklendirmeyi ve broker yükseltmelerini PHP ekibinizin üzerinden alır. Diğer backend dilleri için Ruby MQTT client eğitimi ve Paho MQTT C eğitimi yazılarına bakın.
Sık Sorulan Sorular
php-mqtt/client MQTT 5'i destekliyor mu?
Hayır. MQTT 3.1 ve 3.1.1'i destekler; ConnectMQ bunları MQTT 5.0 ile birlikte kabul eder. Web'den cihaza komut akışlarının çoğu için 3.1.1 yeterlidir.
Bir PHP web isteği içinde MQTT topic'lerine subscribe olabilir miyim?
Olabilirsiniz, ama olmamalısınız. Subscription'lar bağlı kalan bir süreç gerektirir; bu yüzden onları bir CLI worker'da çalıştırın ve web isteklerini kısa publish'lerle sınırlayın.
PHP'den yaptığım QoS 1 publish neden bazen ulaşmıyor?
Betik muhtemelen client broker'ın onayını işlemeden önce sonlandı. Kuyruğun disconnect() öncesinde boşalması için publish'ten sonra $mqtt->loop(true, true) çağırın.
PHP'yi TLS üzerinden MQTT'ye nasıl bağlarım?
Bağlantı ayarlarında setUseTls(true) seçeneğini etkinleştirin ve 8883 portuna bağlanın. Eş doğrulama, broker.connectmq.com gibi herkesçe güvenilen sertifikalara sahip broker'larla kutudan çıktığı gibi çalışır.



