1. HTTP vs WebSocket
WebSocket adalah protokol komunikasi dua arah (full-duplex) yang berjalan di atas TCP. Berbeda dengan HTTP yang bersifat request-response, WebSocket memungkinkan client dan server mengirim data kapan saja setelah koneksi terbuka — tanpa perlu membuat request baru setiap kali.
Dalam konteks IoT, WebSocket sangat penting ketika kita membutuhkan data sensor secara real-time tanpa harus melakukan polling berulang-ulang yang memboroskan bandwidth dan sumber daya server.
Koneksi terputus setelah respons
Unidirectional
Duplex, kirim kapan saja
Bidirectional
| Fitur | HTTP | WebSocket |
|---|---|---|
| Arah komunikasi | Satu arah (client → server) | Dua arah (full-duplex) |
| Koneksi | Baru setiap request | Persistent (tetap terbuka) |
| Overhead | Header besar (~800 byte) | Frame kecil (2-14 byte) |
| Latensi | Tinggi (polling) | Rendah (push) |
| Use case | API, website statis | Chat, game, IoT real-time |
| Protokol | HTTP/1.1, HTTP/2 | ws:// atau wss:// |
Gunakan WebSocket ketika Anda membutuhkan data real-time dari sensor, kontrol perangkat secara langsung, atau komunikasi dua arah yang konstan. Untuk data yang jarang berubah (misalnya konfigurasi), HTTP REST API tetap lebih cocok karena lebih sederhana dan mudah di-cache.
2. WebSocket Handshake
WebSocket menggunakan mekanisme upgrade protocol untuk mengubah koneksi HTTP biasa menjadi koneksi WebSocket. Proses ini disebut handshake dan hanya dilakukan sekali saat koneksi dibuka.
2.1 Proses Handshake
Client mengirim HTTP request dengan header Upgrade: websocket. Jika server mendukung WebSocket, server membalas dengan kode status 101 Switching Protocols. Setelah itu, komunikasi berlangsung dalam format frame WebSocket biner.
# === Client mengirim HTTP Upgrade Request ===
GET /ws/sensor HTTP/1.1
Host: 192.168.1.100:8080
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: http://dashboard.local
# === Server membalas dengan 101 Switching Protocols ===
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
# === Setelah ini, komunikasi dalam format WebSocket frame ===
# Client → Server: {"cmd": "subscribe", "topic": "suhu"}
# Server → Client: {"suhu": 28.5, "kelembaban": 65}
2.2 WebSocket Frame
Setiap pesan WebSocket dikirim dalam bentuk frame yang terdiri dari:
- FIN bit (1 bit): Menandakan apakah ini frame terakhir dari pesan
- Opcode (4 bit): Tipe frame — 0x1 (text), 0x2 (binary), 0x8 (close), 0x9 (ping), 0xA (pong)
- Mask bit (1 bit): Client ke server harus di-mask (wajib)
- Payload length: Panjang data (7 bit, atau 16/64 bit untuk data besar)
- Payload data: Isi pesan sesungguhnya
Selalu gunakan wss:// (WebSocket Secure) di lingkungan produksi. WSS mengenkripsi data dengan TLS/SSL sehingga aman dari serangan man-in-the-middle. Koneksi ws:// tanpa enkripsi hanya boleh digunakan untuk testing lokal.
3. WebSocket di ESP32
ESP32 dapat berperan sebagai WebSocket client (mengirim data sensor ke server) atau sebagai WebSocket server (menerima koneksi dari browser/dashboard). Library yang paling populer adalah WebSockets dari Markus Sattler.
3.1 Instalasi Library
Di Arduino IDE, buka Library Manager (Sketch → Include Library → Manage Libraries), cari "WebSockets" oleh Markus Sattler, lalu klik Install.
3.2 ESP32 sebagai WebSocket Client
ESP32 terhubung ke WebSocket server dan mengirim data sensor setiap 2 detik:
// esp32_ws_client.ino — ESP32 WebSocket Client
// BeebaneLabs - https://beebanelabs.pages.dev
#include <WiFi.h>
#include <WebSocketsClient.h>
#include <DHT.h>
// === Konfigurasi WiFi ===
const char* ssid = "WiFi-Rumah";
const char* password = "password123";
// === Konfigurasi WebSocket Server ===
const char* ws_host = "192.168.1.100";
const uint16_t ws_port = 8080;
const char* ws_path = "/ws/sensor";
// === Pin Sensor ===
#define DHT_PIN 4
#define DHT_TYPE DHT22
WebSocketsClient webSocket;
DHT dht(DHT_PIN, DHT_TYPE);
// Callback saat event WebSocket terjadi
void webSocketEvent(WStype_t type, uint8_t* payload, size_t length) {
switch (type) {
case WStype_CONNECTED:
Serial.println("[WS] Terhubung ke server!");
// Kirim pesan identifikasi
webSocket.sendTXT("{\"device\":\"esp32-001\",\"type\":\"sensor_node\"}");
break;
case WStype_DISCONNECTED:
Serial.println("[WS] Terputus dari server!");
break;
case WStype_TEXT:
Serial.printf("[WS] Pesan dari server: %s\n", payload);
// Proses perintah dari server
handleServerCommand((char*)payload);
break;
case WStype_ERROR:
Serial.printf("[WS] Error: %s\n", payload);
break;
}
}
void handleServerCommand(String cmd) {
// Contoh: server mengirim perintah kontrol LED
if (cmd.indexOf("\"led\":\"on\"") >= 0) {
digitalWrite(LED_BUILTIN, HIGH);
webSocket.sendTXT("{\"status\":\"led_on\"}");
} else if (cmd.indexOf("\"led\":\"off\"") >= 0) {
digitalWrite(LED_BUILTIN, LOW);
webSocket.sendTXT("{\"status\":\"led_off\"}");
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
dht.begin();
// Koneksi WiFi
WiFi.begin(ssid, password);
Serial.print("Menghubungkan WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi terhubung!");
Serial.println(WiFi.localIP());
// Inisialisasi WebSocket
webSocket.begin(ws_host, ws_port, ws_path);
webSocket.onEvent(webSocketEvent);
webSocket.setReconnectInterval(5000); // Reconnect setiap 5 detik
}
void loop() {
webSocket.loop();
// Kirim data sensor setiap 2 detik
static unsigned long lastSend = 0;
if (millis() - lastSend >= 2000) {
lastSend = millis();
float suhu = dht.readTemperature();
float kelembaban = dht.readHumidity();
if (!isnan(suhu) && !isnan(kelembaban)) {
// Format JSON
String json = "{\"device\":\"esp32-001\",";
json += "\"suhu\":" + String(suhu, 1) + ",";
json += "\"kelembaban\":" + String(kelembaban, 1) + ",";
json += "\"uptime\":" + String(millis() / 1000) + "}";
webSocket.sendTXT(json);
Serial.println("[DATA] Terkirim: " + json);
}
}
}
3.3 ESP32 sebagai WebSocket Server
ESP32 juga bisa menjadi server WebSocket — cocok untuk jaringan lokal tanpa server terpisah:
// esp32_ws_server.ino — ESP32 sebagai WebSocket Server
// BeebaneLabs - https://beebanelabs.pages.dev
#include <WiFi.h>
#include <WebSocketsServer.h>
const char* ssid = "WiFi-Rumah";
const char* password = "password123";
WebSocketsServer webSocketServer = WebSocketsServer(81);
void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) {
switch (type) {
case WStype_CONNECTED: {
IPAddress ip = webSocketServer.remoteIP(num);
Serial.printf("[WS] Client #%d terhubung dari %s\n", num, ip.toString().c_str());
// Kirim pesan selamat datang
webSocketServer.sendTXT(num, "{\"msg\":\"Terhubung ke ESP32!\"}");
break;
}
case WStype_TEXT:
Serial.printf("[WS] Client #%d: %s\n", num, payload);
// Echo kembali ke semua client
webSocketServer.broadcastTXT(payload);
break;
case WStype_DISCONNECTED:
Serial.printf("[WS] Client #%d terputus\n", num);
break;
}
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(500); }
Serial.printf("IP ESP32: %s\n", WiFi.localIP().toString().c_str());
webSocketServer.begin();
webSocketServer.onEvent(webSocketEvent);
}
void loop() {
webSocketServer.loop();
// Broadcast data sensor ke semua client setiap 3 detik
static unsigned long lastBroadcast = 0;
if (millis() - lastBroadcast >= 3000) {
lastBroadcast = millis();
float suhu = random(250, 350) / 10.0;
String data = "{\"suhu\":" + String(suhu) + ",\"source\":\"esp32-server\"}";
webSocketServer.broadcastTXT(data);
}
}
4. Node.js WebSocket Server
Node.js adalah platform yang sangat baik untuk menjalankan WebSocket server karena model event-driven dan non-blocking I/O yang mampu menangani ribuan koneksi simultan. Library ws adalah yang paling ringan dan populer.
4.1 Instalasi
# Buat project baru mkdir ws-iot-server && cd ws-iot-server npm init -y # Instal library WebSocket dan Express npm install ws express # (Opsional) Instal untuk development npm install -D nodemon
4.2 WebSocket Server Dasar
// server.js — WebSocket Server untuk IoT
// BeebaneLabs - https://beebanelabs.pages.dev
const WebSocket = require('ws');
const http = require('http');
const express = require('express');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server, path: '/ws/sensor' });
// Simpan koneksi client
const clients = new Map();
wss.on('connection', (ws, req) => {
const clientId = Date.now().toString(36);
const clientIP = req.socket.remoteAddress;
console.log(`[WS] Client terhubung: ${clientId} dari ${clientIP}`);
// Simpan client
clients.set(clientId, {
ws: ws,
connectedAt: new Date(),
device: null
});
// Kirim pesan selamat datang
ws.send(JSON.stringify({
type: 'welcome',
clientId: clientId,
msg: 'Terhubung ke BeebaneLabs IoT Server'
}));
// Handle pesan masuk
ws.on('message', (data) => {
try {
const msg = JSON.parse(data);
console.log(`[DATA] dari ${clientId}:`, msg);
// Identifikasi perangkat
if (msg.device) {
clients.get(clientId).device = msg.device;
}
// Broadcast ke semua client lain
broadcast(clientId, msg);
} catch (e) {
console.error('[ERROR] Pesan tidak valid:', e.message);
}
});
// Handle disconnect
ws.on('close', () => {
const client = clients.get(clientId);
console.log(`[WS] Client terputus: ${client?.device || clientId}`);
clients.delete(clientId);
});
// Handle error
ws.on('error', (err) => {
console.error(`[ERROR] Client ${clientId}:`, err.message);
});
});
// Fungsi broadcast ke semua client kecuali pengirim
function broadcast(senderId, data) {
const message = JSON.stringify(data);
clients.forEach((client, id) => {
if (id !== senderId && client.ws.readyState === WebSocket.OPEN) {
client.ws.send(message);
}
});
}
// REST endpoint untuk status
app.get('/api/status', (req, res) => {
res.json({
connectedClients: clients.size,
uptime: process.uptime(),
devices: Array.from(clients.values()).map(c => c.device).filter(Boolean)
});
});
// Mulai server
const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
console.log(`[SERVER] WebSocket server berjalan di port ${PORT}`);
console.log(`[SERVER] WebSocket endpoint: ws://localhost:${PORT}/ws/sensor`);
});
5. Broadcasting ke Semua Client
Broadcasting adalah mengirim satu pesan ke semua client yang terhubung. Ini sangat berguna untuk dashboard monitoring yang menampilkan data sensor dari beberapa ESP32 secara bersamaan.
5.1 Pola Broadcasting
// === Pola Broadcasting ===
// 1. Broadcast ke SEMUA client (termasuk pengirim)
function broadcastAll(data) {
const message = JSON.stringify(data);
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
}
// 2. Broadcast ke semua KECUALI pengirim
function broadcastExcept(sender, data) {
const message = JSON.stringify(data);
wss.clients.forEach((client) => {
if (client !== sender && client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
}
// 3. Broadcast berdasarkan ROOM (grup topik)
const rooms = new Map();
function joinRoom(clientId, roomName) {
if (!rooms.has(roomName)) rooms.set(roomName, new Set());
rooms.get(roomName).add(clientId);
}
function broadcastToRoom(roomName, data) {
const room = rooms.get(roomName);
if (!room) return;
const message = JSON.stringify(data);
room.forEach((clientId) => {
const client = clients.get(clientId);
if (client?.ws.readyState === WebSocket.OPEN) {
client.ws.send(message);
}
});
}
// Contoh penggunaan room:
// Client mengirim: {"action": "join", "room": "ruang_tamu"}
// Server menjalankan: joinRoom(clientId, "ruang_tamu")
// Broadcast ke room: broadcastToRoom("ruang_tamu", sensorData)
5.2 Client Browser — Menerima Broadcast
// browser-ws.js — Client WebSocket di browser
// BeebaneLabs - https://beebanelabs.pages.dev
const wsUrl = 'ws://192.168.1.100:8080/ws/sensor';
let ws = null;
function connectWebSocket() {
ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log('Terhubung ke WebSocket server');
document.getElementById('status').textContent = '🟢 Connected';
// Subscribe ke topik tertentu
ws.send(JSON.stringify({
action: 'subscribe',
topics: ['suhu', 'kelembaban']
}));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Data diterima:', data);
// Update dashboard
if (data.suhu !== undefined) {
document.getElementById('suhu').textContent = data.suhu + ' °C';
}
if (data.kelembaban !== undefined) {
document.getElementById('kelembaban').textContent = data.kelembaban + ' %';
}
};
ws.onclose = () => {
console.log('Koneksi terputus, reconnect dalam 3 detik...');
document.getElementById('status').textContent = '🔴 Disconnected';
setTimeout(connectWebSocket, 3000); // Auto reconnect
};
ws.onerror = (err) => {
console.error('WebSocket error:', err);
};
}
connectWebSocket();
6. Autentikasi WebSocket
WebSocket tidak memiliki standar autentikasi bawaan. Ada beberapa pendekatan untuk mengautentikasi client WebSocket:
6.1 Token via Query Parameter
// autentikasi.js — Autentikasi WebSocket dengan JWT
const jwt = require('jsonwebtoken');
const SECRET_KEY = 'rahasia-jangan-dibagikan';
// Saat upgrade connection
wss.on('connection', (ws, req) => {
// Ambil token dari query parameter
const url = new URL(req.url, 'http://localhost');
const token = url.searchParams.get('token');
if (!token) {
ws.close(4001, 'Token tidak ditemukan');
return;
}
try {
const decoded = jwt.verify(token, SECRET_KEY);
ws.user = decoded; // Simpan info user
console.log(`[AUTH] User ${decoded.username} terotentikasi`);
// Lanjutkan proses normal...
ws.send(JSON.stringify({ type: 'auth_ok', user: decoded.username }));
} catch (err) {
ws.close(4003, 'Token tidak valid atau expired');
return;
}
});
// Client terhubung dengan token:
// ws://server:8080/ws?token=eyJhbGciOiJIUzI1NiIs...
6.2 Autentikasi via Header (Cookie)
// Saat HTTP upgrade request
server.on('upgrade', (request, socket, head) => {
// Ambil cookie dari header
const cookies = parseCookies(request.headers.cookie);
const sessionToken = cookies['session_token'];
if (!sessionToken || !validateSession(sessionToken)) {
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
socket.destroy();
return;
}
// Lolos autentikasi, lanjutkan upgrade ke WebSocket
wss.handleUpgrade(request, socket, head, (ws) => {
ws.user = getUserFromSession(sessionToken);
wss.emit('connection', ws, request);
});
});
function parseCookies(cookieHeader) {
const cookies = {};
if (!cookieHeader) return cookies;
cookieHeader.split(';').forEach(cookie => {
const [name, value] = cookie.trim().split('=');
cookies[name] = decodeURIComponent(value);
});
return cookies;
}
Jangan pernah mengirim password atau API key langsung melalui URL WebSocket (ws://server?key=secret) karena URL bisa tercatat di log server dan browser history. Gunakan token JWT atau cookie yang sudah diatur sebelumnya melalui HTTP.
7. Scaling WebSocket
Ketika jumlah perangkat IoT bertambah, satu server WebSocket tidak lagi cukup. Dibutuhkan strategi scaling untuk menangani ribuan bahkan jutaan koneksi simultan.
7.1 Horizontal Scaling dengan Redis Pub/Sub
Ketika menjalankan beberapa instance server WebSocket, setiap instance hanya tahu tentang client yang terhubung langsung kepadanya. Redis Pub/Sub digunakan sebagai "jembatan" antar server agar pesan bisa di-broadcast ke semua client di semua server.
// scaled-server.js — Multi-instance WebSocket dengan Redis
const Redis = require('ioredis');
const WebSocket = require('ws');
// Dua koneksi Redis: satu subscribe, satu publish
const redisSub = new Redis({ host: 'redis-server', port: 6379 });
const redisPub = new Redis({ host: 'redis-server', port: 6379 });
const wss = new WebSocket.Server({ port: 8080 });
// Subscribe ke channel Redis
redisSub.subscribe('iot-broadcast');
redisSub.on('message', (channel, message) => {
// Terima pesan dari server lain, kirim ke local clients
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
wss.on('connection', (ws) => {
ws.on('message', (data) => {
// Publish ke Redis agar server lain juga menerima
redisPub.publish('iot-broadcast', data.toString());
});
});
// Jalankan beberapa instance:
// PORT=8080 node scaled-server.js (instance 1)
// PORT=8081 node scaled-server.js (instance 2)
// Load balancer (nginx) mendistribusikan koneksi
7.2 Nginx sebagai Reverse Proxy WebSocket
# /etc/nginx/sites-available/ws-iot
upstream ws_backend {
# Load balancing antar beberapa instance
server 127.0.0.1:8080;
server 127.0.0.1:8081;
server 127.0.0.1:8082;
}
server {
listen 443 ssl;
server_name iot.example.com;
ssl_certificate /etc/ssl/certs/iot.pem;
ssl_certificate_key /etc/ssl/private/iot.key;
location /ws/ {
proxy_pass http://ws_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400; # 24 jam
proxy_send_timeout 86400;
}
}
8. Real-time Dashboard
Berikut contoh lengkap membuat real-time dashboard yang menerima data sensor dari ESP32 melalui WebSocket dan menampilkannya di browser dengan Chart.js.
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>IoT Dashboard Real-time</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: Arial, sans-serif; background: #1a1a2e; color: #eee; }
.cards { display: flex; gap: 20px; margin: 20px; flex-wrap: wrap; }
.card {
background: #16213e; border-radius: 12px; padding: 20px;
flex: 1; min-width: 200px; text-align: center;
}
.card h2 { font-size: 2.5em; margin: 0; }
.card .label { color: #888; }
.chart-container { margin: 20px; background: #16213e; border-radius: 12px; padding: 20px; }
.status { padding: 8px 16px; border-radius: 20px; font-size: 0.9em; }
.status.online { background: #22c55e33; color: #22c55e; }
.status.offline { background: #ef444433; color: #ef4444; }
</style>
</head>
<body>
<h1 style="margin:20px">🌡️ IoT Real-time Dashboard</h1>
<div>
Status: <span id="status" class="status offline">🔴 Terputus</span>
</div>
<div class="cards">
<div class="card">
<div class="label">Suhu</div>
<h2 id="suhu">--</h2>
<div>°C</div>
</div>
<div class="card">
<div class="label">Kelembaban</h2>
<h2 id="kelembaban">--</h2>
<div>%</div>
</div>
<div class="card">
<div class="label">Uptime</div>
<h2 id="uptime">--</h2>
<div>detik</div>
</div>
</div>
<div class="chart-container">
<canvas id="chart" height="100"></canvas>
</div>
<script>
// Inisialisasi chart
const ctx = document.getElementById('chart').getContext('2d');
const maxDataPoints = 50;
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Suhu (°C)',
data: [],
borderColor: '#ef4444',
tension: 0.3,
fill: false
}, {
label: 'Kelembaban (%)',
data: [],
borderColor: '#3b82f6',
tension: 0.3,
fill: false
}]
},
options: {
responsive: true,
scales: {
x: { ticks: { color: '#888' } },
y: { ticks: { color: '#888' } }
},
plugins: { legend: { labels: { color: '#eee' } } }
}
});
// WebSocket connection
const ws = new WebSocket('ws://192.168.1.100:8080/ws/sensor');
ws.onopen = () => {
document.getElementById('status').textContent = '🟢 Terhubung';
document.getElementById('status').className = 'status online';
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
const time = new Date().toLocaleTimeString('id-ID');
// Update cards
if (data.suhu) document.getElementById('suhu').textContent = data.suhu;
if (data.kelembaban) document.getElementById('kelembaban').textContent = data.kelembaban;
if (data.uptime) document.getElementById('uptime').textContent = data.uptime;
// Update chart
chart.data.labels.push(time);
chart.data.datasets[0].data.push(data.suhu);
chart.data.datasets[1].data.push(data.kelembaban);
// Batasi jumlah data points
if (chart.data.labels.length > maxDataPoints) {
chart.data.labels.shift();
chart.data.datasets[0].data.shift();
chart.data.datasets[1].data.shift();
}
chart.update();
};
ws.onclose = () => {
document.getElementById('status').textContent = '🔴 Terputus';
document.getElementById('status').className = 'status offline';
setTimeout(() => location.reload(), 3000);
};
</script>
</body>
</html>
Untuk dashboard dengan banyak grafik, pertimbangkan menggunakan WebSocket binary frames (format MessagePack atau Protobuf) alih-alih JSON text untuk mengurangi ukuran payload. Selain itu, gunakan requestAnimationFrame untuk update DOM agar rendering lebih halus dan tidak membebani browser.
9. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang WebSocket: