Web Socket #
Komunikasi HTTP berjalan satu arah: klien mengirim request, server membalas, koneksi ditutup. Untuk aplikasi yang membutuhkan pembaruan real-time — chat, notifikasi live, dashboard harga saham, atau game multiplayer — pola ini tidak efisien karena klien harus terus-menerus mengirim request baru hanya untuk mengecek apakah ada data baru. WebSocket memecahkan masalah ini dengan membangun koneksi persisten dua arah antara klien dan server: setelah koneksi terbuka, keduanya bisa saling mengirim pesan kapan saja tanpa overhead request baru.
WebSocket vs HTTP #
Sebelum masuk ke implementasi, penting memahami perbedaan mendasar antara keduanya.
HTTP (Request-Response) WebSocket (Full-Duplex)
─────────────────────── ──────────────────────────
Klien → Request → Server Klien ←→ Server (dua arah)
Klien ← Response ← Server
Koneksi ditutup Koneksi tetap terbuka
Overhead per pesan: tinggi Overhead per pesan: rendah
(header HTTP ~200-800 byte) (frame header 2-14 byte)
Cocok untuk: Cocok untuk:
REST API, form submit Chat, live feed, gaming,
download file, SSR dashboard real-time,
kolaborasi dokumen
Proses Handshake #
WebSocket dimulai sebagai request HTTP biasa, lalu di-upgrade ke protokol WebSocket:
Klien → Server:
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Server → Klien:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Setelah ini: koneksi TCP tetap terbuka,
protokol berganti dari HTTP ke WebSocket frame.
Karena WebSocket dimulai dari HTTP, ia kompatibel dengan infrastruktur web yang sudah ada — firewall, load balancer, dan reverse proxy (seperti Nginx) umumnya mendukung WebSocket dengan konfigurasi minimal.
Instalasi #
pip install websockets
Library websockets berbasis asyncio — semua handler ditulis sebagai coroutine async. Ini memungkinkan satu server menangani ribuan koneksi secara bersamaan dengan efisiensi tinggi.
Server WebSocket Dasar #
Server paling sederhana: echo server yang memantulkan kembali setiap pesan yang diterima.
import asyncio
import websockets
async def handler(websocket):
"""Handler dipanggil sekali per koneksi klien."""
print(f"Klien terhubung: {websocket.remote_address}")
try:
async for pesan in websocket:
print(f"Terima: {pesan}")
await websocket.send(f"Echo: {pesan}")
except websockets.ConnectionClosedError as e:
print(f"Koneksi terputus paksa: {e}")
except websockets.ConnectionClosedOK:
print("Klien menutup koneksi dengan normal.")
finally:
print(f"Klien {websocket.remote_address} keluar.")
async def main():
# serve() menangani accept loop secara otomatis
async with websockets.serve(handler, "localhost", 8765):
print("Server WebSocket berjalan di ws://localhost:8765")
await asyncio.Future() # jalan selamanya
if __name__ == "__main__":
asyncio.run(main())
Hindariasyncio.get_event_loop().run_until_complete()yang banyak ditemukan di contoh lama — API ini deprecated sejak Python 3.10. Gunakanasyncio.run()sebagai entry point utama program async.
Klien WebSocket #
import asyncio
import websockets
async def klien():
uri = "ws://localhost:8765"
async with websockets.connect(uri) as ws:
pesan_list = ["Halo!", "Apa kabar?", "Sampai jumpa!"]
for pesan in pesan_list:
await ws.send(pesan)
respons = await ws.recv()
print(f"Server: {respons}")
if __name__ == "__main__":
asyncio.run(klien())
Mengirim dan Menerima JSON #
Hampir semua aplikasi WebSocket nyata bertukar data terstruktur, bukan plain string. Konvensi umumnya adalah membungkus payload dalam JSON dengan field type sebagai penanda jenis pesan.
import asyncio
import json
import websockets
async def handler(websocket):
async for pesan_raw in websocket:
try:
pesan = json.loads(pesan_raw)
except json.JSONDecodeError:
await websocket.send(json.dumps({
"type": "error",
"pesan": "Format pesan tidak valid — harus JSON."
}))
continue
tipe = pesan.get("type")
if tipe == "ping":
await websocket.send(json.dumps({"type": "pong"}))
elif tipe == "echo":
await websocket.send(json.dumps({
"type": "echo",
"data": pesan.get("data")
}))
else:
await websocket.send(json.dumps({
"type": "error",
"pesan": f"Tipe pesan tidak dikenal: {tipe}"
}))
async def main():
async with websockets.serve(handler, "localhost", 8765):
await asyncio.Future()
if __name__ == "__main__":
asyncio.run(main())
Klien yang mengirim JSON:
import asyncio
import json
import websockets
async def klien():
async with websockets.connect("ws://localhost:8765") as ws:
# kirim ping
await ws.send(json.dumps({"type": "ping"}))
respons = json.loads(await ws.recv())
print(f"Respons ping: {respons}") # {'type': 'pong'}
# kirim echo
await ws.send(json.dumps({"type": "echo", "data": {"nilai": 42}}))
respons = json.loads(await ws.recv())
print(f"Respons echo: {respons}") # {'type': 'echo', 'data': {'nilai': 42}}
if __name__ == "__main__":
asyncio.run(klien())
Server Broadcast — Chat Room #
Use case WebSocket yang paling umum adalah broadcast: satu pesan dari satu klien dikirim ke semua klien yang sedang terkoneksi. Ini dasar dari aplikasi chat, notifikasi live, atau pembaruan harga real-time.
import asyncio
import json
import websockets
# Set untuk melacak semua koneksi aktif
klien_aktif: set[websockets.WebSocketServerProtocol] = set()
async def broadcast(pesan: str):
"""Kirim pesan ke semua klien yang terkoneksi."""
if not klien_aktif:
return
# websockets.broadcast() lebih efisien dari loop manual
websockets.broadcast(klien_aktif, pesan)
async def handler(websocket):
# Daftarkan klien baru
klien_aktif.add(websocket)
nama = f"User-{len(klien_aktif)}"
print(f"[+] {nama} bergabung | Total: {len(klien_aktif)}")
await broadcast(json.dumps({
"type": "system",
"pesan": f"{nama} bergabung ke ruang chat."
}))
try:
async for pesan_raw in websocket:
try:
data = json.loads(pesan_raw)
teks = data.get("teks", "")
except json.JSONDecodeError:
teks = pesan_raw
print(f"[{nama}]: {teks}")
await broadcast(json.dumps({
"type": "chat",
"dari": nama,
"teks": teks
}))
except websockets.ConnectionClosedError:
pass
finally:
# Hapus klien dari daftar saat disconnect
klien_aktif.discard(websocket)
print(f"[-] {nama} keluar | Sisa: {len(klien_aktif)}")
await broadcast(json.dumps({
"type": "system",
"pesan": f"{nama} meninggalkan ruang chat."
}))
async def main():
async with websockets.serve(handler, "localhost", 8765):
print("Chat server berjalan di ws://localhost:8765")
await asyncio.Future()
if __name__ == "__main__":
asyncio.run(main())
Keepalive dengan Ping/Pong #
Koneksi WebSocket yang idle bisa diputus oleh firewall, load balancer, atau proxy yang menganggap koneksi sudah mati. Mekanisme ping/pong memastikan koneksi tetap hidup.
Library websockets menangani ping/pong secara otomatis — kamu cukup mengkonfigurasi intervalnya:
async def main():
async with websockets.serve(
handler,
"localhost",
8765,
ping_interval=20, # kirim ping setiap 20 detik
ping_timeout=10, # tunggu pong maksimal 10 detik
# jika tidak ada pong, koneksi dianggap mati
):
await asyncio.Future()
Untuk mengatur timeout koneksi secara keseluruhan:
async def klien_dengan_timeout():
async with websockets.connect(
"ws://localhost:8765",
ping_interval=20,
ping_timeout=10,
open_timeout=5, # timeout saat membuka koneksi
) as ws:
await ws.send("Halo!")
respons = await asyncio.wait_for(ws.recv(), timeout=5.0)
print(respons)
Kapan Menggunakan WebSocket vs Alternatif #
Gunakan WebSocket jika:
✓ Server perlu push data ke klien tanpa diminta (notifikasi, chat)
✓ Komunikasi dua arah dengan latensi rendah (game, kolaborasi)
✓ Banyak pembaruan kecil yang sering (harga saham, sensor IoT)
Pertimbangkan Server-Sent Events (SSE) jika:
✗ Hanya butuh server → klien (satu arah), bukan dua arah
✗ Ingin solusi lebih sederhana berbasis HTTP biasa
✗ Contoh: feed berita, progress bar, log streaming
Pertimbangkan HTTP Polling biasa jika:
✗ Pembaruan tidak terlalu sering (setiap beberapa menit)
✗ Tidak perlu real-time ketat
✗ Infrastruktur tidak mendukung koneksi persisten
Ringkasan #
- WebSocket adalah koneksi persisten dua arah — setelah handshake HTTP awal, klien dan server bisa saling mengirim pesan kapan saja tanpa overhead request baru.
- Gunakan
asyncio.run()bukanget_event_loop()— API lama sudah deprecated sejak Python 3.10.- Tangani
ConnectionClosedErrordanConnectionClosedOK— keduanya perlu ditangani agar server tidak crash saat klien disconnect.- Kirim data dalam format JSON dengan field
type— konvensi ini memudahkan routing pesan di sisi server maupun klien.- Untuk broadcast, gunakan
websockets.broadcast()— lebih efisien dari mengirim satu per satu dalam loop.- Aktifkan ping/pong dengan
ping_intervaldanping_timeoutagar koneksi idle tidak diputus firewall atau proxy.- WebSocket bukan pengganti HTTP — gunakan REST API untuk operasi CRUD biasa; WebSocket untuk komunikasi real-time yang sesungguhnya membutuhkan push dari server.