Socket #
Hampir semua komunikasi jaringan — HTTP, database, email, chat — dibangun di atas socket. Socket adalah abstraksi yang mewakili titik ujung sebuah koneksi jaringan: kamu bisa membuka socket, mengirim data ke dalamnya, dan membaca data yang datang darinya, persis seperti membaca dan menulis ke file. Python menyediakan modul socket yang memberi akses langsung ke API socket sistem operasi, memungkinkan kamu membangun server dan klien jaringan dari tingkat paling dasar.
Cara Kerja Socket #
Sebelum menulis kode, penting memahami lifecycle socket dan perbedaan peran antara server dan klien.
LIFECYCLE TCP (Server) LIFECYCLE TCP (Klien)
────────────────────── ─────────────────────
socket() ← buat socket socket() ← buat socket
│ │
bind() ← ikat ke IP:port connect() ← hubungkan ke server
│ │
listen() ← mulai mendengarkan send() ← kirim data
│ │
accept() ← terima koneksi klien recv() ← terima data
│ │
recv() ← baca data dari klien close() ← tutup koneksi
│
send() ← kirim respons
│
close() ← tutup koneksi klien
Dua protokol utama yang bekerja di atas socket:
TCP (SOCK_STREAM) UDP (SOCK_DGRAM)
───────────────── ────────────────
Koneksi persisten Connectionless (tanpa koneksi)
Urutan data terjaga Urutan tidak dijamin
Ada acknowledgment Tidak ada acknowledgment
Lebih lambat Lebih cepat
Cocok: HTTP, database, Cocok: streaming video, game
file transfer online, DNS
TCP Socket #
TCP menjamin data sampai dengan urutan yang benar. Server TCP menunggu koneksi dari klien, lalu keduanya bisa saling berkirim data hingga salah satu menutup koneksi.
Server TCP Dasar #
import socket
def jalankan_server(host="127.0.0.1", port=65432):
# AF_INET = IPv4, SOCK_STREAM = TCP
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
# SO_REUSEADDR: izinkan reuse port setelah server di-restart
# tanpa ini, restart akan gagal dengan "Address already in use"
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((host, port))
server.listen(5) # antrian maksimal 5 koneksi
print(f"Server TCP berjalan di {host}:{port}")
while True:
conn, alamat = server.accept()
print(f"Koneksi dari: {alamat}")
tangani_klien(conn, alamat)
def tangani_klien(conn, alamat):
with conn:
while True:
data = conn.recv(1024) # baca maksimal 1024 byte
if not data:
break # klien menutup koneksi
pesan = data.decode("utf-8")
print(f"[{alamat}] Terima: {pesan}")
balasan = f"Echo: {pesan}"
conn.sendall(balasan.encode("utf-8"))
print(f"Koneksi dari {alamat} ditutup.")
if __name__ == "__main__":
jalankan_server()
Klien TCP #
import socket
def jalankan_klien(host="127.0.0.1", port=65432):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as klien:
klien.connect((host, port))
print(f"Terhubung ke {host}:{port}")
pesan_list = ["Halo server!", "Ini pesan kedua", "Pesan terakhir"]
for pesan in pesan_list:
klien.sendall(pesan.encode("utf-8"))
data = klien.recv(1024)
print(f"Respons server: {data.decode('utf-8')}")
if __name__ == "__main__":
jalankan_klien()
SO_REUSEADDRwajib di server. Tanpa opsi ini, setelah server dihentikan, port akan tetap dalam statusTIME_WAITselama beberapa menit. Saat kamu mencoba menjalankan server lagi, akan muncul errorOSError: [Errno 98] Address already in use. Selalu tambahkansetsockopt(SOL_SOCKET, SO_REUSEADDR, 1)sebelumbind().
Server TCP Multi-Klien #
Server di atas hanya bisa melayani satu klien sekaligus — klien kedua harus menunggu hingga klien pertama selesai. Ini adalah anti-pattern yang paling umum dalam pemrograman socket.
# ANTI-PATTERN: server single-threaded
# klien kedua diblokir sampai klien pertama selesai
while True:
conn, alamat = server.accept()
tangani_klien(conn, alamat) # blokir di sini sampai selesai
# BENAR: spawn thread baru untuk setiap klien
import threading
while True:
conn, alamat = server.accept()
thread = threading.Thread(target=tangani_klien, args=(conn, alamat))
thread.daemon = True # thread mati saat program utama keluar
thread.start()
Implementasi lengkap server TCP multi-klien:
import socket
import threading
klien_aktif = {}
lock = threading.Lock()
def tangani_klien(conn, alamat):
with lock:
klien_aktif[alamat] = conn
print(f"[+] Klien baru: {alamat} | Total: {len(klien_aktif)}")
try:
with conn:
while True:
data = conn.recv(1024)
if not data:
break
pesan = data.decode("utf-8")
print(f"[{alamat}] {pesan}")
conn.sendall(f"Echo: {pesan}".encode("utf-8"))
except ConnectionResetError:
print(f"[!] Klien {alamat} terputus paksa.")
finally:
with lock:
klien_aktif.pop(alamat, None)
print(f"[-] Klien {alamat} keluar | Sisa: {len(klien_aktif)}")
def jalankan_server(host="127.0.0.1", port=65432):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((host, port))
server.listen()
print(f"Server multi-klien berjalan di {host}:{port}")
while True:
conn, alamat = server.accept()
thread = threading.Thread(target=tangani_klien, args=(conn, alamat))
thread.daemon = True
thread.start()
if __name__ == "__main__":
jalankan_server()
Timeout dan Penanganan Error #
Koneksi jaringan bisa tergantung tanpa batas — klien mogok, jaringan putus, atau server tidak merespons. Tanpa timeout, program bisa hang selamanya.
import socket
def klien_dengan_timeout(host="127.0.0.1", port=65432):
klien = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
klien.settimeout(5.0) # timeout 5 detik untuk operasi connect, recv, send
try:
klien.connect((host, port))
klien.sendall("Halo!".encode("utf-8"))
data = klien.recv(1024)
print(f"Respons: {data.decode('utf-8')}")
except socket.timeout:
print("Koneksi timeout — server tidak merespons dalam 5 detik.")
except ConnectionRefusedError:
print("Koneksi ditolak — server tidak berjalan atau port salah.")
except OSError as e:
print(f"Error jaringan: {e}")
finally:
klien.close()
klien_dengan_timeout()
Timeout juga bisa diset di sisi server agar koneksi klien yang idle tidak menggantung resource:
def tangani_klien_dengan_timeout(conn, alamat):
conn.settimeout(30.0) # klien harus kirim data dalam 30 detik
with conn:
try:
while True:
data = conn.recv(1024)
if not data:
break
conn.sendall(data)
except socket.timeout:
print(f"[!] Klien {alamat} idle terlalu lama, koneksi ditutup.")
UDP Socket #
UDP tidak membangun koneksi — server langsung menerima paket dari siapa saja, dan klien langsung mengirim tanpa handshake terlebih dahulu. Lebih cepat, tapi tidak ada jaminan data sampai.
Server UDP #
import socket
def server_udp(host="127.0.0.1", port=65433):
# SOCK_DGRAM = UDP
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as server:
server.bind((host, port))
print(f"Server UDP berjalan di {host}:{port}")
while True:
# recvfrom: terima data + alamat pengirim sekaligus
data, alamat_pengirim = server.recvfrom(1024)
pesan = data.decode("utf-8")
print(f"[UDP] Dari {alamat_pengirim}: {pesan}")
# sendto: kirim ke alamat spesifik (tidak perlu koneksi)
balasan = f"Diterima: {pesan}"
server.sendto(balasan.encode("utf-8"), alamat_pengirim)
if __name__ == "__main__":
server_udp()
Klien UDP #
import socket
def klien_udp(host="127.0.0.1", port=65433):
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as klien:
klien.settimeout(3.0)
alamat_server = (host, port)
pesan_list = ["Paket 1", "Paket 2", "Paket 3"]
for pesan in pesan_list:
klien.sendto(pesan.encode("utf-8"), alamat_server)
try:
data, _ = klien.recvfrom(1024)
print(f"Respons: {data.decode('utf-8')}")
except socket.timeout:
print(f"Timeout — paket '{pesan}' mungkin hilang.")
if __name__ == "__main__":
klien_udp()
Kapan Memilih TCP vs UDP #
Gunakan TCP jika:
✓ Data harus sampai lengkap dan berurutan
✓ Implementasi protokol aplikasi: HTTP, FTP, SSH, database
✓ Transfer file atau dokumen
✓ Komunikasi chat atau API
Gunakan UDP jika:
✓ Kecepatan lebih penting dari keandalan
✓ Kehilangan sedikit paket dapat ditoleransi
✓ Streaming audio/video real-time
✓ Game online (posisi pemain, status frame)
✓ DNS query (paket kecil, sekali kirim)
✓ Broadcasting ke banyak penerima sekaligus
Ringkasan #
- Socket adalah abstraksi titik ujung koneksi jaringan — kamu membuka, mengirim, menerima, dan menutupnya seperti file.
- Selalu tambahkan
SO_REUSEADDRsebelumbind()di server agar port bisa langsung dipakai kembali setelah restart.- Server single-threaded adalah anti-pattern — klien kedua akan diblokir; gunakan
threading.ThreadatauThreadPoolExecutoruntuk melayani banyak klien secara bersamaan.- Selalu set timeout dengan
socket.settimeout()— tanpanya, program bisa hang selamanya saat jaringan bermasalah.- TCP untuk komunikasi yang membutuhkan keandalan dan urutan data; UDP untuk kecepatan saat sedikit kehilangan paket bisa ditoleransi.
- Gunakan
with socket.socket(...) as s:agar socket selalu ditutup otomatis meski terjadi error.- Data socket adalah bytes, bukan string — selalu
.encode()saat mengirim dan.decode()saat menerima.