Yuke Brilliant
  • HOME
  • PROJECTS
  • BLOG
  • CONTACT
Let's Talk
  1. Home
  2. /
  3. Blog
  4. /
  5. UDP, TCP, atau WebSocket untuk Telemetri Robot?
Networking
IDEN

UDP, TCP, atau WebSocket untuk Telemetri Robot?

Kalau data telat, masih berguna atau tidak? Cara saya memilah UDP, TCP, dan WebSocket untuk telemetri robot dari pengalaman RoboCup, lengkap dengan gateway yang bisa kamu jalankan sendiri.

Yuke Brilliant HestiavinยทOct 16, 2026ยท11 min read
Share:
Dua baris paket telemetri: TCP menunggu paket yang hilang, UDP tetap segar

On this page

  1. 1.Lupakan "mana yang tercepat", tanya ini dulu
  2. 2.TCP itu setia, dan itu masalahnya
  3. 3.UDP: bebas, tapi kamu yang beres-beres
  4. 4.WebSocket: tiket masuk ke browser
  5. 5.Tiga rencana yang kami siapkan sebelum berangkat
  6. 6.Hari pertama di Incheon: 2.4 GHz kolaps
  7. 7.Contekan: data apa pakai apa
  8. 8.Praktik: gateway UDP ke WebSocket, 60 baris aja
  9. 9.1. Robot tiruan
  10. 10.2. Gateway
  11. 11.3. Penonton
  12. 12.4. Jalankan dan buktikan
  13. 13.Rusakin dulu jaringanmu, sebelum venue yang melakukannya
  14. 14.Kebiasaan yang saya pegang sekarang
  15. 15.Referensi

Wi-Fi di venue RoboCup 2026 di Incheon jelek untuk hampir semua tim di sana. Latensi naik turun tanpa pola, koneksi putus lalu nyambung lagi sendiri, dan di tengah pertandingan itu robot-robot kami tetap harus saling tahu posisi satu sama lain. Robot soccer yang tidak tahu di mana temannya berdiri akan menabrak temannya sendiri ๐Ÿ’€ Atau mungkin planga-plongo wkwkwk...

Di situlah debat "UDP atau TCP?" yang di kampus kedengaran teoretis tiba-tiba jadi urusan serius. Saya mengerjakan software komunikasinya untuk IRIS ITS, dan sebelum berangkat kami sudah menyiapkan tiga rencana komunikasi yang berbeda. Pertanyaannya ujung-ujungnya satu: data mana yang boleh telat, dan data mana yang nggak boleh hilang?

Artikel ini merangkum cara saya memilah jawabannya. Isinya UDP, TCP, dan WebSocket untuk telemetri robot, lengkap dengan kode yang bisa kamu jalankan di laptop sendiri dan cara memaksa jaringanmu jadi jelek supaya kamu tahu desainmu tahan atau tidak.

Ikon buffering di layar
Wi-Fi venue pas robot lagi butuh tahu posisi temannya ๐Ÿ˜ญ

Lupakan "mana yang tercepat", tanya ini dulu

Hampir semua perbandingan UDP vs TCP berakhir di tabel yang sama: TCP andal tapi lambat, UDP cepat tapi bisa hilang. Betul sih. Cuma tabel itu nggak membantu kamu memutuskan apa-apa.

Pertanyaan yang lebih berguna: kalau data ini telat, masih ada gunanya nggak?

Posisi robot dua detik lalu? Nggak berguna. Robotnya udah pindah, dan kamu menghitung berdasarkan dunia yang sudah nggak ada. Perintah "stop" yang telat dua detik? Masih berguna banget, malah wajib sampai. Hilang total jauh lebih parah daripada telat.

Jadi telemetri robot sebenarnya dua jenis data yang butuh perlakuan berlawanan. Yang pertama state yang terus diperbarui: posisi, kecepatan, sensor, baterai. Yang penting nilai terbarunya, dan nilai lama gugur begitu yang baru datang. Yang kedua perintah dan event: start, stop, assign peran, konfigurasi. Tiap pesan penting, urutannya juga.

Satu protokol untuk keduanya? Hampir pasti salah satunya menderita.

TCP itu setia, dan itu masalahnya

TCP janji dua hal: semua byte sampai, dan sampai dengan urutan yang sama. Cara menepatinya: kirim ulang yang hilang, lalu tahan semua data sesudahnya sampai lubangnya terisi.

Bagian "tahan" itu yang bikin sakit buat telemetri. Namanya head-of-line blocking. Bayangin robot ngirim state nomor 1 sampai 6, lalu nomor 3 hilang di udara. Paket 4, 5, dan 6 sebenarnya sudah nongol di kernel penerima. Tapi aplikasi nggak boleh membacanya sebelum nomor 3 muncul lagi. Ya nunggu.

Head-of-line blocking pada TCP: paket 3 hilang, paket 4 sampai 6 ditahan kernel
Paket 3 hilang, jadi paket 4 sampai 6 menunggu di kernel meski sudah tiba

Begitu nomor 3 akhirnya datang, aplikasi menerima 3, 4, 5, 6 sekaligus dalam satu semburan. Padahal yang dia butuhkan cuma nomor 6. Tiga yang pertama itu sampah yang datang telat.

Nunggunya berapa lama? Di Linux, batas bawah timeout kirim ulang (RTO) adalah 200 ms, dan nilainya dikali dua tiap kali kirim ulang gagal. Di Wi-Fi yang lagi ramai, dua atau tiga kegagalan beruntun cukup untuk menahan state robot sampai hitungan detik. Buat robot yang bergerak beberapa meter per detik, itu jauh banget.

Ada dua hal kecil lagi yang sering kelewat. Pertama Nagle's algorithm, yang menggabungkan pesan-pesan kecil sebelum dikirim. Hemat bandwidth, tapi nambah jeda untuk pesan kecil macam telemetri. Matikan dengan TCP_NODELAY (di Node.js: socket.setNoDelay(true)). Kedua backoff kongesti: TCP menganggap paket hilang berarti jaringan penuh, lalu ngerem. Padahal di Wi-Fi paket lebih sering hilang karena sinyal, jalannya sendiri kosong. TCP ngerem di jalan lengang.

Catatan: TCP nggak salah apa-apa di sini. Itu harga dari janji "semua sampai, urut". Untuk transfer file atau perintah, harga itu pantas dibayar. Untuk posisi robot, nggak.

UDP: bebas, tapi kamu yang beres-beres

UDP itu gaya fire and forget. Kirim, lalu lupa. Dia nggak peduli paketnya nyampe atau nggak, nggak nunggu konfirmasi, pokoknya kirim terus. Kayak masukin surat ke kotak pos tanpa minta resi wkwkwk.

Kedengarannya ceroboh, tapi buat state yang diperbarui 20 kali per detik, itu justru fitur. Paket yang hilang bakal digantikan paket berikutnya 50 ms kemudian, jadi nggak ada gunanya repot mengejar yang lama. Paket juga bisa dobel atau datang terbalik, dan nggak ada yang kirim ulang. Itu yang bikin UDP cocok buat state: nggak ada yang menahan paket 6 demi paket 3.

Konsekuensinya, beberapa urusan pindah ke tanganmu.

Pertama, nomor urut. Kasih tiap paket seq yang naik terus. Penerima membuang paket yang seq-nya nggak lebih besar dari yang sudah diterima, dan paket terbalik maupun dobel beres sekaligus.

Kedua, kirim state lengkap, jangan selisih. Kalau paket 5 isinya "x nambah 0.1" lalu paket 5 hilang, penerima salah selamanya. Kalau tiap paket membawa x, y, dan heading utuh, hilang satu paket cuma berarti kamu kelewatan satu tick. Agak boros bandwidth, jauh lebih tahan banting.

Ketiga, deteksi diam. UDP nggak tahu lawan bicaranya sudah mati. Penerima harus punya jam sendiri: kalau nggak ada paket dari robot X selama sekian ratus milidetik, tandai datanya basi. Pakai jam penerima, jangan jam robot, soalnya jam dua mesin hampir pasti nggak sinkron.

Beberapa catatan praktis:

  • Jaga datagram di bawah sekitar 1400 byte. Lewat dari MTU, IP memecahnya jadi fragmen, dan hilang satu fragmen berarti seluruh datagram ikut hilang.
  • Multicast di Wi-Fi bergantung pada pengaturan access point. Alasan kami nggak memakainya ada di bagian tiga rencana di bawah.
  • UDP nggak punya kontrol kongesti. Kirim 1000 paket per detik ke jaringan yang cuma kuat 200, dia nurut aja dan menenggelamkan semuanya. Tentukan laju kirim yang masuk akal.

WebSocket: tiket masuk ke browser

Browser nggak bisa membuka socket UDP atau TCP mentah. Kalau kamu mau dashboard di browser, WebSocket pintunya.

WebSocket itu TCP dengan handshake HTTP di depan dan pembingkaian pesan di atasnya. Artinya semua sifat TCP tadi ikut kebawa: satu paket hilang, pesan sesudahnya tertahan. Efeknya di dashboard nggak enak dilihat. Layar beku beberapa detik, lalu robotnya loncat ke posisi baru karena semua pesan tertunda datang barengan.

Obatnya ada di sisi server. Jangan teruskan setiap paket dari robot ke browser. Simpan state terbaru per robot di sebuah gateway, lalu dorong snapshot ke browser dengan laju tetap. Kalau kliennya lambat dan antrean kirimnya numpuk, lewati tick itu. Klien yang lambat cuma melihat data lebih jarang, tapi nggak pernah melihat data yang lebih tua dari seharusnya.

Base Station kami punya server Node yang menerima UDP lalu meneruskannya ke UI Vue lewat WebSocket. Versi di artikel ini menambahkan dua kebiasaan: simpan state terbaru saja, dan lewati klien yang lambat. Server Node itu sendiri cuma dipakai di salah satu rencana kami, dan dua rencana lainnya nggak membutuhkannya. Ceritanya ada di bagian berikutnya.

Gateway menerima state robot lewat UDP dan mengirim snapshot terbaru ke browser lewat WebSocket
Gateway menyimpan state terbaru per robot dan mendorong snapshot ke browser dengan laju tetap

Kalau kamu butuh jalur nggak andal langsung ke browser, WebRTC DataChannel bisa dikonfigurasi unordered dengan maxRetransmits: 0. Itu urusan lain yang layak artikel sendiri, dan gateway di atas sudah cukup untuk kebanyakan dashboard.

Tiga rencana yang kami siapkan sebelum berangkat

Sedikit konteks stack dulu. Robot kami jalan di C++ dengan ROS1, Base Station pakai Node.js dan Vue. Di sisi ROS, kami subscribe banyak topic (pose robot dan kawan-kawannya), lalu publish ke topic bs2pc yang isinya data dari semua robot peer. Soal bagaimana data itu berpindah antar robot dan ke Base Station, kami punya tiga rencana.

Rencana Cara kerja Masalah utama Hasil
UDP multicast, peer to peer Setiap robot menyiarkan state ke satu alamat multicast. Server Node di Base Station menerima UDP dan meneruskan ke UI lewat WebSocket Bergantung pada pengaturan AP, termasuk beacon interval Tidak dipilih
UDP unicast, peer to peer Antar robot langsung lewat UDP unicast. UI Base Station langsung pakai roslib Tidak bergantung pada perilaku multicast AP Dipilih
Full roslib Semua komunikasi lewat roslib, UI juga langsung pakai roslib Terlalu banyak hop, round trip panjang Tidak dipilih

Multicast kelihatan paling elegan di atas kertas. Sekali kirim, semua robot dengar, dan nggak ada daftar alamat yang harus dirawat. Masalahnya Wi-Fi. Access point menahan frame multicast untuk klien yang lagi hemat daya dan baru melepasnya setelah beacon tertentu, jadi jedanya ikut pengaturan beacon interval di AP. Pengaturan itu dipegang penyelenggara venue, dan kami nggak bisa menyentuhnya. Rencana yang performanya bergantung pada konfigurasi yang nggak bisa kami ubah itu berisiko, seelegan apa pun desainnya. Di rencana ini, Base Station memakai server Node tadi: terima UDP, teruskan ke UI lewat WebSocket.

Full roslib kebalikannya. Semua lewat satu jalur yang rapi, tapi hop-nya kebanyakan dan round trip-nya panjang banget untuk data yang cepat basi. Roslib juga jalan di atas WebSocket, jadi sifat TCP yang kita bahas tadi ikut kebawa. UI-nya langsung pakai roslib.

UDP unicast antar robot yang paling oke. Tiap robot mengirim state langsung ke robot lain tanpa perantara. Nggak ada hop tambahan, dan nggak ada urusan dengan cara AP memperlakukan multicast. Untuk Base Station tetap roslib, dan karena UI bisa langsung memakai roslib, server Node tadi nggak diperlukan lagi.

Ini hasil untuk jaringan, AP, dan robot kami. Venue lain bisa memberi jawaban lain, makanya kebiasaan terakhir di artikel ini adalah ukur dulu.

Hari pertama di Incheon: 2.4 GHz kolaps

Rencana di atas kertas itu satu hal. Lapangan punya cerita sendiri.

Sebelum MSL main, semuanya aman. Waktu kami latihan di lapangan utama, komunikasi jalan mulus, jir. Baru pas Falcons dan Toulon ikut masuk ke lapangan, langsung kerasa. Jaringan yang tadinya lega mendadak sesak.

Hari pertama lomba jauh lebih parah. Cabang lomba lain ikut bertanding, venue penuh perangkat, dan lag-nya parah. Aturan lama memakai 2.4 GHz, jadi hari itu kami masih di band yang sama dengan seisi gedung. Delay robot kami sempat tembus 2000 sampai 3000 ms. Dua sampai tiga detik. Robot Falcons malah nggak gerak sama sekali.

Untungnya saya sudah menyiapkan antena Wi-Fi eksternal. Firasat sih wkwkwkwk ๐Ÿ˜…

Hari berikutnya, frekuensi diganti ke 5 GHz dan kami memakai channel DFS supaya nggak rebutan dengan perangkat lain di venue. Hasilnya komunikasi kami lancar. Yang kasihan Falcons: programnya sudah telanjur diubah, jadi komunikasi mereka kacau sampai hari terakhir.

Kenapa 2.4 GHz segampang itu kolaps? Di band itu cuma ada tiga channel yang tidak saling tumpang tindih (1, 6, dan 11), dan semua perangkat di gedung berbagi tiga jalur itu: HP, laptop, access point, Bluetooth. Channel DFS di 5 GHz berbagi spektrum dengan radar, jadi AP wajib pindah kalau mendeteksi radar. Banyak perangkat menghindarinya, makanya biasanya lebih sepi. Harganya: kalau radar terdeteksi, AP harus pindah channel dan ada jeda singkat.

Pelajarannya, protokol sebagus apa pun nggak menolong kalau medianya sudah jenuh. Sekarang urutan debugging saya: radio dulu (band, channel, antena), baru protokol.

Contekan: data apa pakai apa

Ringkasan yang saya pakai sebagai titik awal:

Jenis data Contoh Pilihan Alasannya
State yang terus diperbarui posisi, kecepatan, sensor UDP Nilai lama gugur saat nilai baru datang, tidak ada yang perlu ditahan
Heartbeat sinyal "saya masih hidup" UDP Yang penting ada atau tidaknya, hilang satu tidak masalah
Perintah kritis start, stop, assign peran TCP Setiap pesan harus sampai dan berurutan
Konfigurasi dan file parameter, log, peta TCP Ukuran besar dan harus utuh
Dashboard di browser Base Station, monitoring WebSocket lewat gateway (atau roslib kalau sudah pakai ROS) Browser tidak bisa UDP, gateway menyaring data basi
Video kamera robot Protokol berbasis UDP (RTP, WebRTC) Frame lama tidak berguna, kualitas boleh turun

Polanya kebaca: state pakai UDP, perintah pakai TCP, browser pakai WebSocket lewat gateway. Di RefBox kami, jalur perintah memakai TCP dan WebSocket, karena pesannya sedikit tapi tiap pesan wajib sampai dan berurutan.

Praktik: gateway UDP ke WebSocket, 60 baris aja

Kita bikin tiga file dalam satu folder: robot tiruan yang mengirim state lewat UDP, gateway yang menyimpan state terbaru, dan penonton yang membaca dari WebSocket. Robot aslinya C++ dengan ROS1. Di sini saya pakai Node supaya kamu bisa menjalankannya tanpa ROS.

Prasyarat: Node.js 22 atau lebih baru (klien WebSocket bawaan dipakai di langkah terakhir).

BASH
mkdir telemetry-demo && cd telemetry-demo
npm init -y
npm pkg set type=module
npm i ws

1. Robot tiruan

File robot.mjs. Variabel LOSS mensimulasikan paket yang hilang di jalan.

JAVASCRIPT
import dgram from 'node:dgram';

const socket = dgram.createSocket('udp4');
const ID = process.env.ROBOT_ID ?? 'r1';
const LOSS = Number(process.env.LOSS ?? 0); // 0..1, simulasi paket hilang
let seq = 0;

setInterval(() => {
  seq += 1;
  if (Math.random() < LOSS) return; // paket "hilang di jalan"

  const state = {
    id: ID,
    seq,
    x: Math.cos(seq / 20),
    y: Math.sin(seq / 20),
    heading: (seq * 3) % 360,
  };

  socket.send(JSON.stringify(state), 9000, '127.0.0.1');
}, 50); // 20 Hz

Perhatiin tiap paket membawa state utuh, bukan selisih.

2. Gateway

File gateway.mjs. Tiga aturan dari bagian UDP tadi ada di sini: buang yang lebih tua, tandai yang basi pakai jam gateway, dan lewati klien yang lambat.

JAVASCRIPT
import dgram from 'node:dgram';

import { WebSocketServer } from 'ws';

const STALE_MS = 500; // lewat ini, data dianggap basi
const RESET_MS = 2_000; // robot diam selama ini, anggap restart
const PUSH_MS = 100; // 10 Hz ke browser
const MAX_BUFFERED = 64 * 1024; // klien lambat, lewati tick ini

/** robotId -> { state, recvAt } */
const latest = new Map();

const udp = dgram.createSocket('udp4');

udp.on('message', (msg) => {
  let state;
  try {
    state = JSON.parse(msg);
  } catch {
    return; // paket rusak, buang saja
  }

  const now = Date.now();
  const prev = latest.get(state.id);

  // Datagram bisa tiba terbalik. Yang lebih tua dari yang sudah ada dibuang.
  // Kalau robot diam lama, seq-nya mungkin reset karena restart, jadi terima.
  const restarted = prev && now - prev.recvAt > RESET_MS;
  if (prev && !restarted && state.seq <= prev.state.seq) return;

  latest.set(state.id, { state, recvAt: now });
});

udp.bind(9000, () => console.log('udp :9000'));

const wss = new WebSocketServer({ port: 8080 }, () =>
  console.log('ws  :8080')
);

setInterval(() => {
  const now = Date.now();
  const snapshot = JSON.stringify(
    [...latest.values()].map(({ state, recvAt }) => ({
      ...state,
      ageMs: now - recvAt,
      stale: now - recvAt > STALE_MS,
    }))
  );

  for (const client of wss.clients) {
    if (client.readyState !== client.OPEN) continue;
    if (client.bufferedAmount > MAX_BUFFERED) continue;
    client.send(snapshot);
  }
}, PUSH_MS);
Catatan: blok restarted menangani kasus yang gampang kelewat. Robot yang di-restart mulai lagi dari seq 1. Tanpa pengecualian itu, gateway bakal membuang semua paket barunya sebagai "lebih tua" sampai seq menyusul angka lama.

3. Penonton

File viewer.mjs, pengganti dashboard browser.

JAVASCRIPT
const ws = new WebSocket('ws://127.0.0.1:8080');
let n = 0;

ws.onmessage = (e) => {
  const robots = JSON.parse(e.data);
  for (const r of robots) {
    console.log(`${r.id} seq=${r.seq} age=${r.ageMs}ms stale=${r.stale}`);
  }
  if (++n >= 12) process.exit(0);
};

4. Jalankan dan buktikan

Di tiga terminal berbeda:

BASH
node gateway.mjs
LOSS=0.3 node robot.mjs
node viewer.mjs

Cek apakah sudah berhasil: viewer.mjs harus mencetak baris seperti di bawah. Perhatiin seq yang loncat (tiga dari sepuluh paket hilang itu wajar dengan LOSS=0.3) sementara age tetap kecil. Kalau nggak ada baris sama sekali, gateway belum menerima apa-apa. Cek robot.mjs jalan dan portnya 9000.

TEXT
r1 seq=30 age=75ms stale=false
r1 seq=33 age=23ms stale=false
r1 seq=34 age=73ms stale=false
r1 seq=36 age=72ms stale=false
r1 seq=39 age=20ms stale=false

Inilah inti desainnya. Paket hilang 30 persen pun, data yang sampai ke penonton tetap segar. Nggak ada antrean numpuk, nggak ada loncatan semburan.

Sekarang matikan robot.mjs (Ctrl+C), lalu jalankan viewer.mjs lagi:

TEXT
r1 seq=54 age=367ms stale=false
r1 seq=54 age=1075ms stale=true
r1 seq=54 age=1480ms stale=true

seq mandek di 54 dan age terus naik. Begitu lewat 500 ms, statusnya jadi stale=true (sebagian baris saya potong). Dashboard yang bagus bisa membedakan "robotnya diam di tempat" dari "kita sudah nggak dengar kabar dari robot".

Robot kartun jatuh
State yang telat datang, robot sudah terlanjur menabrak ๐Ÿ’€

Rusakin dulu jaringanmu, sebelum venue yang melakukannya

Simulasi LOSS tadi cuma menjatuhkan paket di pengirim. Biar lebih jujur, di Linux kamu bisa merusak interface jaringan sungguhan dengan tc netem:

BASH
# tambah delay 100 ms (jitter 30 ms) dan buang 5% paket di interface eth0
sudo tc qdisc add dev eth0 root netem delay 100ms 30ms loss 5%

# kembalikan seperti semula
sudo tc qdisc del dev eth0 root
Peringatan: jalankan ini di mesin yang kamu pegang langsung. Kalau kamu menerapkannya di interface yang dipakai sesi SSH-mu, sesi itu ikut lemot atau putus.

Lalu bandingkan dua pendekatan dengan data yang sama: kirim state lewat TCP (atau WebSocket langsung dari robot) dan lewat gateway UDP. Di jaringan bersih keduanya kelihatan sama. Begitu netem menyala, kamu bakal lihat bedanya sendiri. Tes macam ini lebih meyakinkan daripada argumen apa pun di artikel ini.

Kebiasaan yang saya pegang sekarang

Beberapa kebiasaan yang menentukan apakah sistemnya selamat saat jaringan memburuk:

  • Kasih batas waktu di setiap penerima. Data tanpa umur bisa berbohong, jadi tandai basi dan jangan disembunyikan.
  • Tentukan perilaku aman saat sunyi. Kalau robot nggak dengar perintah selama sekian waktu, dia ngapain? Berhenti, lanjut? Jawabannya harus keputusan, bukan kebetulan.
  • Ukur di jaringan jelek, jangan di meja kerja. Di meja kerja semua protokol kelihatan bagus.
  • Pisahkan jalur. Perintah kritis jangan antre di belakang aliran telemetri.
  • Periksa lapisan radio sebelum menyalahkan protokol: band, channel, dan antena. Di Incheon, pindah dari 2.4 GHz ke 5 GHz DFS yang memperbaiki keadaan.
  • Siapkan cadangan perangkat keras. Antena eksternal itu murah, dan kami butuh banget.

Saya nggak mengklaim susunan ini optimal untuk semua robot. Tim lain punya kebutuhan beda, dan beberapa liga punya aturan komunikasi sendiri yang membatasi pilihan. Tapi pertanyaan awalnya berlaku di mana saja: kalau data ini telat, masih berguna atau nggak? Kalau jawabannya nggak, jangan kasih dia protokol yang bersedia menunggu.

Referensi

  • RFC 768, User Datagram Protocol
  • RFC 9293, Transmission Control Protocol
  • RFC 6455, The WebSocket Protocol
  • Node.js dgram
  • ws, WebSocket library untuk Node.js
  • tc-netem, network emulator

Comments

  • About
  • Contact
  • Projects
  • Blog

Here We Go

  • Designed withFigma
  • Developed withNext.js
  • Backend withHygraph