Belajar Swoole - WebSocket Server
Episode 6 of 26

Belajar Swoole - WebSocket Server

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.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

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.

Apa Itu WebSocket dan Mengapa Berbeda dari HTTP

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:

AspekHTTPWebSocket
ArahClient → ServerDua arah
SifatStateless per requestStateful, koneksi persisten
OverheadHeader tiap requestHandshake sekali, lalu pesan ringan
PenggunaanREST API, halamanChat, notifikasi, game, live data

WebSocket Server Pertama

Swoole menyediakan Swoole\WebSocket\Server yang meng-extend Swoole\Http\Server — jadi kalian tetap bisa menangani request HTTP biasa di server yang sama:

ws-server.php - WebSocket server minimal
<?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:

EventDipicu saatArgumen
on('Open')Koneksi WebSocket berhasil di-handshakeServer, Request (berisi $req->fd)
on('Message')Pesan teks/binary tibaServer, Frame ($frame->fd, $frame->data, $frame->opcode)
on('Close')Koneksi ditutupServer, int $fd

$server->push($fd, $data) mengirim pesan ke client dengan fd (file descriptor / ID koneksi) tertentu.

Broadcast ke Semua Client

Heartbeat dari hampir semua aplikasi realtime adalah broadcast: satu client mengirim, semua client lain menerima. Caranya dengan iterasi koneksi aktif:

Broadcast ke semua client
<?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.
  • Lewati $fd pengirim bila ingin broadcast "kecuali pengirim" (pola chat yang benar).

Chat App Sederhana

Gabungkan semua: server mendengarkan di port 9502, dan client HTML sederhana di port yang sama melalui event Request:

chat.php - chat server + client
<?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:

chat.html - client WebSocket
<!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.

Heartbeat: Menjaga Koneksi Tetap Hidup

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.

Heartbeat ping tiap 30 detik
$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:

Hapus koneksi yang tidak membalas
$server->on('Message', function (Server $server, Frame $frame) {
    if ($frame->opcode === SWOOLE_WEBSOCKET_OPCODE_PONG) {
        return;
    }
    // ... proses pesan biasa
});

Common Pitfalls

  • push ke fd yang belum established — selalu cek isEstablished() atau tangkap exception.
  • Mengirim pesan dari luar context serverpush hanya valid dari dalam proses worker yang sama; untuk lintas worker/process, gunakan sendMessage atau Redis pub/sub (episode 10 & 22).
  • Broadcast memakai data yang belum di-decode — untuk JSON, json_decode($frame->data, true) dulu sebelum menyebar ulang.
  • Lupa on('Close') cleanup — bila kalian menyimpan state per client (misal array user), hapus di on('Close') agar tidak bocor memory.

Penutup

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.
  • Chat app = broadcast ke semua koneksi kecuali pengirim.
  • Heartbeat ping/pong mencegah koneksi ghost menumpuk.
  • isEstablished() sebelum push mencegah error.

Di episode 7 selanjutnya, kita menggali fondasi paling penting Swoole: coroutines fundamentalsCoroutine::create, defer, scheduling, dan mengapa coroutine menghilangkan callback hell. Sampai jumpa di episode 7!

Belajar Swoole - WebSocket Server | Belajar Swoole