Membangun server di bawah lapisan HTTP: protokol TCP custom dengan length-header dan open_length_check, server UDP untuk datagram, serta MQTT untuk IoT, ditutup simulasi game/chat server dan device gateway.

Setelah di episode 20 kita memeras performa HTTP server — pada episode kali ini kita turun satu lapisan: TCP dan UDP. Swoole bukan sekadar HTTP server; ia adalah framework networking lengkap. Game server, device IoT, sistem trading, dan banyak protokol internal tidak memakai HTTP sama sekali — mereka bicara langsung lewat TCP/UDP.
Mengapa episode ini penting? Karena di sinilah Swoole benar-benar unik dibanding alternatif PHP mana pun. PHP-FPM bahkan tidak bisa menjadi TCP server yang layak; Swoole melakukannya secara native, dan kalian akan membangun game/chat server serta device gateway sebagai buktinya.
Server TCP dengan Swoole\Server memakai event Connect, Receive, dan Close:
<?php
use Swoole\Server;
$server = new Server('0.0.0.0', 9504);
$server->on('Connect', function (Server $server, int $fd, int $reactorId) {
echo "Client $fd terkoneksi\n";
$server->send($fd, "Selamat datang di TCP server\n");
});
$server->on('Receive', function (Server $server, int $fd, int $reactorId, string $data) {
$trimmed = trim($data);
if ($trimmed === 'ping') {
$server->send($fd, "pong\n");
} else {
$server->send($fd, "Echo: $trimmed\n");
}
});
$server->on('Close', function (Server $server, int $fd) {
echo "Client $fd terputus\n";
});
$server->start();Uji dengan netcat:
echo "ping" | nc 127.0.0.1 9504TCP tidak mengenal "pesan" — ia aliran byte. Bila client mengirim dua pesan sekaligus, server bisa menerima "halloapa" dalam satu event Receive, atau satu pesan terpotong dua. Ini disebut sticky packet problem.
Solusinya: framing — setiap pesan diberi bingkai yang jelas. Pola paling umum adalah length-header: 4 byte pertama menyimpan panjang payload, sisanya isi pesan.
| 4 byte length (big-endian) | payload ... |Swoole menyederhanakan ini dengan open_length_check:
$server->set([
'open_length_check' => true,
'package_length_type' => 'N', // 4 byte unsigned, big-endian
'package_length_offset' => 0, // panjang ada di byte ke-0
'package_body_offset' => 4, // payload mulai di byte ke-4
'package_max_length' => 81920, // payload maksimal 80 KB
]);Client pun wajib mengikuti format yang sama:
use Swoole\Coroutine;
Coroutine::create(function () {
$client = new Coroutine\Client(SWOOLE_SOCK_TCP);
$client->connect('127.0.0.1', 9504, 3);
$body = json_encode(['action' => 'move', 'x' => 10, 'y' => 20]);
$frame = pack('N', strlen($body)) . $body; // length + payload
$client->send($frame);
$response = $client->recv();
$client->close();
});Setelah open_length_check aktif, on('Receive') hanya menerima pesan utuh — tidak lagi potongan acak.
Note
Alternatif framing: delimiter (open_eof_check + package_eof), misal setiap pesan diakhiri \r\n — cocok untuk protokol text sederhana. Length-header lebih aman untuk protokol binary dan pesan yang mengandung karakter apa pun. Untuk IoT, protokol MQTT sudah menangani framing-nya sendiri — kita lihat sebentar lagi.
UDP bersifat connectionless — tiap datagram berdiri sendiri. Swoole menanganinya tanpa event Connect/Close:
use Swoole\Server;
$server = new Server('0.0.0.0', 9505, SWOOLE_PROCESS, SWOOLE_SOCK_UDP);
$server->on('Packet', function (Server $server, string $data, array $clientInfo) {
$server->sendto($clientInfo['address'], $clientInfo['port'], "Balasan: $data");
});
$server->start();| Aspek | TCP | UDP |
|---|---|---|
| Koneksi | Connection-oriented | Connectionless |
| Pengiriman | Terjamin, urut | Best effort, bisa hilang |
| Cocok untuk | Game/chat yang butuh keandalan | Telemetry, streaming, discovery |
| Event | Connect/Receive/Close | Packet |
UDP unggul saat kecepatan lebih penting daripada keandalan — dan saat paket kecil dan hilangnya paket tidak fatal.
MQTT adalah protokol messaging ringan untuk IoT — dirancang untuk perangkat dengan daya dan bandwidth kecil. Swoole mendukungnya via open_mqtt_protocol:
$server = new Server('0.0.0.0', 9506);
$server->set(['open_mqtt_protocol' => true]);
$server->on('Receive', function (Server $server, int $fd, int $reactorId, string $data) {
$mqtt = new Swoole\MQTT\Parser();
$msg = $mqtt->parse($data);
switch ($msg->type ?? '') {
case 'CONNECT':
// device meminta koneksi — balas CONNACK
$server->send($fd, build_connack());
break;
case 'PUBLISH':
$topic = $msg['topic'];
$server->send($fd, build_puback($msg['message_id']));
// teruskan ke pipeline IoT
dispatch_iot_message($topic, $msg['content']);
break;
case 'PINGREQ':
$server->send($fd, build_pingresp());
break;
}
});
$server->start();Dengan ini, Swoole menjadi device gateway: perangkat sensor/ESP32 berlangganan topic, dan datanya mengalir ke pipeline Swoole (Redis Streams dari episode 14, misalnya).
Gabungkan semua: TCP server dengan length-header untuk game client. Setiap client mengirim posisi; server menyebarkan ke semua pemain lain:
<?php
use Swoole\Server;
$server = new Server('0.0.0.0', 9507);
$server->set([
'open_length_check' => true,
'package_length_type' => 'N',
'package_length_offset' => 0,
'package_body_offset' => 4,
'package_max_length' => 81920,
]);
$server->on('Receive', function (Server $server, int $fd, int $reactorId, string $data) {
$msg = json_decode($data, true);
if ($msg['type'] === 'join') {
$server->send($fd, json_encode(['type' => 'welcome', 'players' => count($server->connections)]));
return;
}
if ($msg['type'] === 'move') {
$broadcast = json_encode([
'type' => 'move',
'player' => $fd,
'x' => $msg['x'],
'y' => $msg['y'],
]);
foreach ($server->connections as $peer) {
if ($peer !== $fd) {
$server->send($peer, pack('N', strlen($broadcast)) . $broadcast);
}
}
}
});
$server->start();Pola yang sama persis dengan broadcast WebSocket di episode 6 — hanya transportnya TCP dan framing-nya manual.
| Masalah | Penyebab | Solusi |
|---|---|---|
| Pesan kacau/gabung | Framing tidak ada / salah offset | Aktifkan open_length_check dengan offset benar |
| Client timeout | Client lupa menambah length header | Satu format framing di kedua sisi — jangan buat sendiri-sendiri |
| UDP "pesan hilang" | Sifat UDP memang best effort | Tambah sequence + retransmit di layer aplikasi bila perlu |
sendto gagal | Client UDP tidak pernah terdaftar | Pakai clientInfo['address'] + ['port'] dari event Packet |
Pada episode 21 ini, kalian telah membangun server di bawah lapisan HTTP.
Inti yang harus dibawa pulang:
Connect/Receive/Close; UDP server: Packet.open_length_check.open_mqtt_protocol) menjadikan Swoole device gateway IoT.Di episode 22 selanjutnya, kita menaikkan skala ke banyak node: Distributed & High Availability — multi-node, sticky sessions, Redis pub/sub untuk broadcast lintas node, dan graceful restart. Sampai jumpa di episode 22!