Membangun WebSocket server dengan Swoole\WebSocket\Server, memahami event open message dan close, cara broadcast ke banyak client, heartbeat ping pong, serta praktik membangun chat app dan realtime notification.

Setelah di episode 5 kita menguasai HTTP routing dan middleware, pada episode kali ini kita masuk ke wilayah yang membuat Swoole berbeda dari PHP-FPM selamanya: WebSocket. Ini bukan "pemanis" — ini adalah salah satu alasan utama perusahaan memilih Swoole dibanding aplikasi PHP klasik.
Mengapa episode ini penting? Karena WebSocket membuka kunci aplikasi realtime: chat, notifikasi, kolaborasi dokumen, live dashboard, game, dan streaming harga. Di PHP-FPM, membangun realtime berarti menyewa server Node.js atau Go terpisah — dengan Swoole, server PHP kalian sendiri yang melakukannya, tanpa infra tambahan.
HTTP bersifat request-response: client meminta, server menjawab, koneksi selesai. WebSocket adalah koneksi dua arah yang persisten di atas TCP: setelah handshake, client dan server bisa saling mengirim pesan kapan saja, tanpa request.
Perbandingan cepat:
| Aspek | HTTP | WebSocket |
|---|---|---|
| Arah | Client → Server | Dua arah |
| Sifat | Stateless per request | Stateful, koneksi persisten |
| Overhead | Header tiap request | Handshake sekali, lalu pesan ringan |
| Penggunaan | REST API, halaman | Chat, notifikasi, game, live data |
Swoole menyediakan Swoole\WebSocket\Server yang meng-extend Swoole\Http\Server — jadi kalian tetap bisa menangani request HTTP biasa di server yang sama:
<?php
use Swoole\WebSocket\Server;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
$server = new Server('0.0.0.0', 9502);
$server->on('Open', function (Server $server, Request $request) {
echo "Client terhubung: fd={$request->fd}\n";
$server->push($request->fd, "Selamat datang!");
});
$server->on('Message', function (Server $server, Frame $frame) {
echo "Pesan dari fd={$frame->fd}: {$frame->data}\n";
$server->push($frame->fd, "Echo: {$frame->data}");
});
$server->on('Close', function (Server $server, int $fd) {
echo "Client {$fd} terputus\n";
});
$server->start();Tiga event inti WebSocket:
| Event | Dipicu saat | Argumen |
|---|---|---|
on('Open') | Koneksi WebSocket berhasil di-handshake | Server, Request (berisi $req->fd) |
on('Message') | Pesan teks/binary tiba | Server, Frame ($frame->fd, $frame->data, $frame->opcode) |
on('Close') | Koneksi ditutup | Server, int $fd |
$server->push($fd, $data) mengirim pesan ke client dengan fd (file descriptor / ID koneksi) tertentu.
Heartbeat dari hampir semua aplikasi realtime adalah broadcast: satu client mengirim, semua client lain menerima. Caranya dengan iterasi koneksi aktif:
<?php
use Swoole\WebSocket\Server;
use Swoole\WebSocket\Frame;
$server = new Server('0.0.0.0', 9502);
$server->on('Message', function (Server $server, Frame $frame) {
$msg = $frame->data;
foreach ($server->connections as $fd) {
if ($server->isEstablished($fd)) {
$server->push($fd, $msg);
}
}
});
$server->start();Poin pentingnya:
$server->connections — iterator koneksi aktif (termasuk yang belum handshake).isEstablished($fd) — cek apakah koneksi sudah benar-benar WebSocket (sudah handshake) sebelum push.$fd pengirim bila ingin broadcast "kecuali pengirim" (pola chat yang benar).Gabungkan semua: server mendengarkan di port 9502, dan client HTML sederhana di port yang sama melalui event Request:
<?php
use Swoole\WebSocket\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\WebSocket\Frame;
$server = new Server('0.0.0.0', 9502);
$server->on('Request', function (Request $req, Response $res) {
$res->header('Content-Type', 'text/html');
$res->end(file_get_contents(__DIR__ . '/chat.html'));
});
$server->on('Message', function (Server $server, Frame $frame) {
foreach ($server->connections as $fd) {
if ($fd !== $frame->fd && $server->isEstablished($fd)) {
$server->push($fd, "User{$frame->fd}: {$frame->data}");
}
}
});
$server->start();Client chat.html memakai API WebSocket bawaan browser:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Swoole Chat</title>
</head>
<body>
<input id="msg" placeholder="Tulis pesan..." />
<button onclick="send()">Kirim</button>
<ul id="list"></ul>
<script>
const ws = new WebSocket('ws://localhost:9502');
ws.onmessage = (e) => {
const li = document.createElement('li');
li.textContent = e.data;
document.getElementById('list').appendChild(li);
};
function send() {
const input = document.getElementById('msg');
ws.send(input.value);
input.value = '';
}
</script>
</body>
</html>Buka dua tab browser ke http://localhost:9502/ — kalian akan melihat pesan dari satu tab muncul di tab lain secara realtime.
Koneksi WebSocket bisa mati tanpa sepengetahuan server (misal client tiba-tiba kehilangan internet). Biarkan saja, koneksi ghost akan menumpuk. Solusinya: heartbeat ping/pong dari sisi server.
$server->on('WorkerStart', function (Server $server) {
Swoole\Timer::tick(30000, function () use ($server) {
foreach ($server->connections as $fd) {
if ($server->isEstablished($fd)) {
$server->ping($fd);
}
}
});
});ping() mengirim frame ping; client yang hidup membalas pong otomatis. Client yang tidak membalas dalam beberapa putaran bisa dianggap mati:
$server->on('Message', function (Server $server, Frame $frame) {
if ($frame->opcode === SWOOLE_WEBSOCKET_OPCODE_PONG) {
return;
}
// ... proses pesan biasa
});push ke fd yang belum established — selalu cek isEstablished() atau tangkap exception.push hanya valid dari dalam proses worker yang sama; untuk lintas worker/process, gunakan sendMessage atau Redis pub/sub (episode 10 & 22).json_decode($frame->data, true) dulu sebelum menyebar ulang.on('Close') cleanup — bila kalian menyimpan state per client (misal array user), hapus di on('Close') agar tidak bocor memory.Pada episode 6 ini, kalian telah membangun WebSocket server dan aplikasi chat realtime.
Inti yang harus dibawa pulang:
Swoole\WebSocket\Server = Http\Server + event Open/Message/Close.push($fd, $data) mengirim pesan; iterasi $server->connections untuk broadcast.ping/pong mencegah koneksi ghost menumpuk.isEstablished() sebelum push mencegah error.Di episode 7 selanjutnya, kita menggali fondasi paling penting Swoole: coroutines fundamentals — Coroutine::create, defer, scheduling, dan mengapa coroutine menghilangkan callback hell. Sampai jumpa di episode 7!