squirtle / websocket
PHP 8.2+ WebSocket server and client built on stream_select(), RFC 6455 compliant
Requires
- php: >=8.2
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
WebSocket, PHP 8.2 ve üzeri için bağımlılıksız bir WebSocket sunucu ve istemci
kütüphanesidir. RFC 6455 handshake ve frame kurallarını uygular; tek iş
parçacıklı stream_select() event loop'u üzerinde çalışır. Kanal aboneliği,
handshake kimlik doğrulaması, kanal yetkisi, akış kontrolü, hız sınırlama ve
ayrı backend süreçleri için token korumalı Management IPC içerir.
Kurulum
- PHP 8.2 veya üzeri CLI gerekir.
wss://istemci bağlantıları için OpenSSL eklentisi gerekir.
Uygulamanıza paketi Composer ile ekleyin:
composer require squirtle/websocket
Kütüphane deposunu kaynak koddan çalıştırmak için kökte composer install
çalıştırın. Sunucuyu PHP-FPM istek döngüsünde değil; ayrı CLI süreci,
supervisor veya container giriş noktası olarak çalıştırın.
Temel kullanım
<?php require __DIR__ . '/vendor/autoload.php'; use WebSocket\Connection; use WebSocket\Message; use WebSocket\Server; $server = new Server(['listen' => ['host' => '127.0.0.1', 'port' => 9501]]); $server->on('message', static function (Connection $connection, Message $message) use ($server): void { $server->send($message->payload); }); $server->run();
Composer, WebSocket\ ad alanını otomatik olarak src/ dizinine eşler.
Uygulamaya entegrasyon
WebSocket sunucusu HTTP uygulamasından ayrı bir süreçtir. Mevcut JWT, session
veya API token doğrulamanızı authenticator() callback'ine bağlayın. Callback
bir Identity döndürdüğünde bağlantı o kullanıcıyla otomatik indekslenir;
Server::user($id)->send() o kullanıcının bütün canlı bağlantılarına mesaj
gönderir.
Kanal yetkisi ayrı bir uygulama kuralıdır. Authenticated bir kullanıcının her
kanala abone olmasını engellemek için channels()->authorizer() kullanın.
Tarayıcı yayını gerekmiyorsa channel.publish: false bırakın. Aynı süreçte
uygulama olayı Server::publish() ile, ayrı HTTP controller veya queue worker
sürecinde ise Management IPC ile yayınlanır.
examples/integration.php, token doğrulama,
Identity, kullanıcıya ait kanal yetkisi, düz mesaj callback'i ve uygulama
olayı yayınının tek bir server giriş noktasında nasıl birleştiğini gösterir.
Örnek içindeki findUserByToken() fonksiyonunu mevcut kullanıcı doğrulama
katmanınıza bağlayın.
İstemci -> handshake + Bearer / query token -> authenticator() -> Identity
-> channel authorizer -> user.{id} aboneliği
Backend olayı -> Server::publish() veya Management::publish() -> aboneler
Olay ve kanal modeli
| Olay | Callback | Kullanım |
|---|---|---|
open |
fn(Connection $connection) |
Bağlantı açıldığında kayıt veya başlangıç durumu |
message |
fn(Connection $connection, Message $message) |
Kanal zarfı olmayan uygulama mesajları |
close |
fn(Connection $connection, Reason $reason) |
Bağlantı temizliği |
error |
fn(Throwable $error, ?Connection $connection) |
Hata kaydı ve gözlemlenebilirlik |
Kanal katmanı etkin olduğunda istemci normal WebSocket metni içinde şu zarfları gönderir:
{"type":"subscribe","channel":"user.42"}
{"type":"unsubscribe","channel":"user.42"}
{"type":"publish","channel":"user.42","data":{"type":"invoice.ready"}}
Server::publish('user.42', $data) abonelere aşağıdaki biçimde olay gönderir:
{"type":"event","channel":"user.42","data":{"type":"invoice.ready"}}
Kanal zarfları message callback'ine düşmez. Bu callback uygulamanın kendi
ham WebSocket mesajları içindir; zarf işlemesi Channel\Registry tarafından
yapılır.
Mimari
TCP stream
-> Runtime\Loop
-> Socket\Server / Socket\Client
-> Socket\Connection
-> Protocol\Handshake + Decoder + Assembler
-> Connection
-> Server veya Client
-> Channel\Registry (isteğe bağlı)
Ayrı PHP backend süreci
-> Management\Client
-> Unix socket veya 127.0.0.1 TCP
-> Management\Endpoint
-> çalışan Server / Channel\Registry
| Katman | Kaynak | Sorumluluk |
|---|---|---|
| Runtime | src/Runtime |
stream_select() watcher'ları, timer'lar ve future-tick kuyruğu |
| Socket | src/Socket |
Non-blocking TCP ve sınırlı yazma tamponu |
| Protocol | src/Protocol |
RFC 6455 handshake, frame doğrulama, kodlama/çözme, parçalı mesaj birleştirme |
| Connection | src/Connection.php |
Bağlantı durumu, ping/pong, close handshake ve öznitelikler |
| Server / Client | src/Server.php, src/Client.php |
Uygulama API'si ve yaşam döngüsü callback'leri |
| Channel | src/Channel |
Abonelik, yetki, seçici yayın ve olay zarfları |
| Management | src/Management |
Ayrı backend süreçleri için token korumalı IPC |
| Security | src/Security |
Origin, hız sınırı ve güvenilir proxy çözümleme |
Runtime\Loop, her turda future-tick'leri ve zamanı gelen timer'ları çalıştırır;
ardından okunabilir/yazılabilir stream'leri stream_select() ile işler.
Callback hatası bir error callback'ine iletilir; ayarlanmamışsa run()
çağrısından yükselir.
Yazma önce doğrudan stream'e denenir. Tam yazılamayan veri tamponlanır; yumuşak sınırda o bağlantının okuması durur, tampon boşalınca devam eder. Sert sınır aşıldığında bağlantı kapatılır. Böylece yavaş okuyucular tüm süreç belleğini tüketemez.
Management IPC
Management, genel ağa açılan bir WebSocket API'si değildir. HTTP controller,
queue worker veya cron gibi ayrı PHP süreçlerinden çalışan WebSocket sunucusuna
send, hedefli client/user mesajı, disconnect ve publish komutları
iletir. Producer her işlemde kısa bir yerel bağlantı açar, token ile doğrulanır,
komutu yollar ve kapanır; event loop çalıştırması gerekmez.
examples/management/server.php, yönetim
uç noktasını sunucuya ekler. examples/management/producer.php
ayrı süreçte Management\Client::publish() çağırır. Unix'te mutlak yollu Unix
socket ve 0600 izinleri kullanın. Windows'ta transport yalnızca
127.0.0.1 TCP'ye bağlanabilir. Token'ı kodda tutmayın; ortam değişkeni veya
gizli değer deposundan alın.
Güvenlik
WebSocket; hatalı HTTP Upgrade isteklerini, geçersiz frame'leri, büyük veya parçalı payload'ları, ping selini, yavaş okuyucuları, başarısız handshake'leri ve yetkisiz kanal işlemlerini sınırlamak için aşağıdaki kontrolleri uygular.
| Alan | Yerleşik koruma |
|---|---|
| Handshake | Geçerli Upgrade isteği zorunludur; header boyutu ve sayısı sınırlıdır. |
| Frame ve mesaj | Mask yönü, opcode, RSV bitleri, kontrol frame kuralları, UTF-8 ve boyutlar doğrulanır. |
| Kaynak | Handshake, frame, mesaj ve yazma tamponu için ayrı üst sınırlar bulunur. |
| Akış kontrolü | Yazma tamponu yumuşak sınırda okumayı durdurur; sert sınırda bağlantıyı kapatır. |
| Zaman aşımı | Handshake, boşta bağlantı, ping/pong ve close handshake süreleri izlenir. |
| Hız sınırı | Bağlantı başına mesaj ve byte tabanlı token bucket uygulanabilir; kontrol frame'leri de sınırlanır. |
| Hata yalıtımı | Bir bağlantının callback hatası diğer bağlantıları durdurmaz. |
Üretim için dinleme adresini, origin listesini ve kaynak sınırlarını açıkça tanımlayın:
$server = new \WebSocket\Server([ 'listen' => ['host' => '127.0.0.1', 'port' => 9501, 'connections' => 1000], 'origins' => ['app.example.com', '*.partner.example'], 'limit' => ['frame' => 2 * 1024 * 1024, 'message' => 10 * 1024 * 1024, 'handshake' => 8192], 'buffer' => ['soft' => 65536, 'hard' => 2 * 1024 * 1024], 'rate' => ['messages' => 100.0, 'bytes' => 262144.0], 'channel' => ['publish' => false], 'proxies' => ['127.0.0.1'], ]);
Varsayılan frame sınırı 2 MiB, mesaj sınırı 10 MiB, header bloğu 8 KiB, header
sayısı 64 ve sert yazma tamponu 2 MiB'dir. Boş origins listesi tüm
origin'leri kabul eden geliştirme varsayılanıdır. Üretimde izinli alan adlarını
açıkça yazın. *.example.com kök alan adını kapsamaz; gerekirse
example.com satırını da ekleyin.
Origin kontrolü kimlik doğrulama değildir. Origin başlığı olmayan native
istemciler kabul edilir; kullanıcı veya servis kimliği için authenticator
kullanın. Token Authorization: Bearer ... veya ?token= üzerinden alınır.
Authenticator null döndürürse handshake 401 ile reddedilir.
auth.anonymous: true, token göndermeyen bağlantıyı anonim kabul eder;
geçersiz token yine reddedilir.
X-Forwarded-For ve Forwarded başlıklarına varsayılan olarak güvenilmez.
Yalnızca doğrudan TCP peer adresi proxies listesinde tam IP olarak
varsa istemci IP'si çözülür. Reverse proxy'nin bu başlıkları temizlediğinden
veya güvenli biçimde yeniden yazdığından emin olun.
Sunucu TLS dinleyicisi sağlamaz. WSS/TLS'yi Nginx, Caddy veya benzeri bir reverse proxy'de sonlandırın; PHP sürecini sadece proxy'nin erişebildiği loopback veya özel ağ adresinde dinletin.
Çalışma ve ölçekleme
Event loop tek iş parçacıklıdır. CPU yoğun işlem, bloklayan veritabanı sorgusu, dosya işlemi veya uzak HTTP çağrısını callback içinde çalıştırmayın. Bu işleri queue worker'a devredin; sonucu Management IPC veya harici broker üzerinden yayınlayın. Bağlantılar ve kanal üyelikleri süreç belleğindedir. Birden çok sunucu süreci arasında yayın veya yatay ölçekleme için Redis, NATS, RabbitMQ veya uygulamanızın seçtiği başka bir dağıtım katmanı gerekir.
İleri seviye: sipariş bildirim akışı
examples/advanced/server.php, paketin birlikte kullanılabilen uçlarını üretime yakın bir akışta gösterir: token ile kimlik doğrulama, kullanıcı ve sipariş bazlı kanal yetkisi, tarayıcıdan yayını kapatma, rate ve bellek sınırları, yapılandırılabilir logger, anlık metrikler ve ayrı backend süreçleri için Management IPC. examples/advanced/producer.php ise bir queue worker veya HTTP controller'ın sipariş olayını nasıl ileteceğini gösterir. examples/advanced/client.php, üye istemcinin doğrudan bildirim ve yetkili sipariş kanalı olayını almasını gösterir.
İki ayrı terminal kullanın:
$env:WEBSOCKET_DEMO_TOKEN = 'change-this' php examples/advanced/server.php
$env:WEBSOCKET_DEMO_TOKEN = 'change-this' php examples/advanced/client.php
$env:WEBSOCKET_DEMO_TOKEN = 'change-this' php examples/advanced/producer.php 1001 shipped
İstemci ws://127.0.0.1:9503/?token=member-42 adresine bağlanıp önce
order.1001 kanalına abone olur. Producer çalıştığında kanal olayı ile
Management\Client::user(42)->send() üzerinden doğrudan bildirim aynı canlı
bağlantıya ulaşır. Örnek tokenları yalnızca gösterim içindir; üretimde tokenı
güvenli ortam değişkeninden veya gizli değer deposundan verin.
Sunucu tarafında client($id), user($id) ve channel($name) seçicileri
hedefli send() ve uygun olduğunda disconnect() işlemlerini sunar.
metrics()->array() ise bağlantı, handshake, mesaj, kanal ve hata sayaçlarının
güvenli anlık görüntüsünü verir.
Örnekler
Örneklerin çalıştırma yönergeleri dosya içi yorumlardadır.
| Dosya | Amaç |
|---|---|
examples/server.php |
Echo ve düz mesaj yayını yapan küçük sunucu |
examples/client.php |
Terminal istemcisi ve kanal aboneliği |
examples/publish.php |
Kısa ömürlü backend/webhook yayıncısı |
examples/channel.php |
Tek süreçte seçici kanal yayını |
examples/integration.php |
Kimlik doğrulama ve kanal yetkisiyle uygulama entegrasyonu |
examples/browser/client.html |
Tek sayfada bağlantı günlüğü, broadcast sohbeti, kanal aboneliği/yayını, periyodik olaylar, iki istemci simülasyonu ve ileri sipariş akışı |
examples/management/server.php |
WebSocket + özel Management IPC sunucusu |
examples/management/producer.php |
Ayrı PHP sürecinden güvenli kanal yayını |
examples/advanced/server.php |
Kimlik, yetki, limitler, metrikler ve Management IPC ile sipariş bildirimi sunucusu |
examples/advanced/client.php |
Üye istemciyle doğrudan bildirim ve sipariş kanalı olayını alma |
examples/advanced/producer.php |
Ayrı backend sürecinden sipariş olayı ve hedefli kullanıcı bildirimi |
Testler
Bağımlılıksız test paketini proje kökünde çalıştırın:
php tests/run.php
Lisans
MIT