Database lokal di Chapter sebelumnya menyelesaikan penyimpanan di satu mesin; namun begitu program harus berbicara dengan mesin lain — mengambil data dari API, menjadi server, atau sekadar memastikan layanan lain masih hidup — kita memasuki wilayah networking. Fondasi hampir semua jaringan di Unix dan Linux adalah BSD sockets: API C yang sudah ada sejak 1983 dan sampai hari ini menjadi lapisan paling bawah yang dipakai semua library jaringan modern. Lingkungan course ini menyediakan header <sys/socket.h> milik Linux, jadi kita bisa menjalankan siklus hidup socket sungguhan — membuat, mengikat, mendengarkan, menutup — dan juga membuktikan bagaimana kegagalan koneksi dilaporkan secara deterministik. Chapter ini menjadi pintu masuknya; jalur lengkapnya — TCP, UDP, framing protokol, hingga HTTP dan Asio — menunggu pada Section Network Programming.
Anatomi Socket TCP #
Socket adalah endpoint komunikasi: satu ujung jalur bicara yang diidentifikasi pasangan alamat IP dan port. Dari sudut pandang program, socket hanyalah bilangan file descriptor — sumber daya sistem operasi yang dibuat, dipakai, lalu ditutup, persis seperti file. Fungsi socket() menerima tiga parameter: keluarga alamat (AF_INET untuk IPv4), tipe komunikasi (SOCK_STREAM untuk TCP yang andal dan berurutan, SOCK_DGRAM untuk UDP yang ringan tetapi tanpa jaminan), dan protokol (biasanya 0, biarkan default). Setelah itu perannya terbelah: program server memanggil bind() (menempati alamat:port), listen() (berubah menjadi pendengar), dan accept() (menerima koneksi masuk); program client cukup memanggil connect() menuju alamat tujuan.
Siklus Hidup Socket yang Teruji #
Program berikut menjalankan empat panggilan dasar secara nyata, lalu menambahkan demonstrasi yang justru paling sering ditemui pemula: connect() ke port yang tidak ada servernya.
// socket-dasar.cpp — siklus hidup socket TCP: socket, bind, listen, close,
// plus demonstrasi connect yang gagal dengan pesan error yang pasti.
#include <arpa/inet.h>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <sys/socket.h>
#include <unistd.h>
int main() {
// 1) Buat socket TCP: keluarga alamat IPv4, tipe stream, protokol default.
int fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
std::printf("socket() gagal: %s\n", std::strerror(errno));
return 1;
}
std::printf("socket() : berhasil, TCP stream siap\n");
// 2) Bind ke 127.0.0.1 dengan port 0: OS memilih port bebas yang kosong,
// sehingga contoh ini deterministik dan tidak bentrok dengan program lain.
sockaddr_in alamat{};
alamat.sin_family = AF_INET;
alamat.sin_port = htons(0); // 0 = serahkan ke OS
alamat.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // 127.0.0.1
if (::bind(fd, reinterpret_cast<sockaddr*>(&alamat), sizeof(alamat)) < 0) {
std::printf("bind() gagal: %s\n", std::strerror(errno));
::close(fd);
return 1;
}
std::printf("bind() : berhasil ke 127.0.0.1:0 (port dipilih OS)\n");
// 3) Listen: ubah socket aktif menjadi socket pasif (menerima koneksi).
if (::listen(fd, 8) < 0) {
std::printf("listen() gagal: %s\n", std::strerror(errno));
::close(fd);
return 1;
}
std::printf("listen() : berhasil dengan backlog 8\n");
// 4) Close: lepaskan descriptor dan port yang terikat.
::close(fd);
std::printf("close() : descriptor dilepas\n\n");
// 5) Connect yang gagal: port 1 di loopback tidak ada servernya,
// sehingga hasilnya selalu connection refused.
int cd = ::socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in tujuan{};
tujuan.sin_family = AF_INET;
tujuan.sin_port = htons(1);
tujuan.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
int hasil = ::connect(cd, reinterpret_cast<sockaddr*>(&tujuan), sizeof(tujuan));
if (hasil < 0) {
std::printf("connect() : gagal ke 127.0.0.1:1 seperti diduga\n");
std::printf(" errno = %d (%s)\n", errno, std::strerror(errno));
} else {
std::printf("connect() : tak terduga berhasil\n");
}
::close(cd);
return 0;
}Output yang Dihasilkan:
socket() : berhasil, TCP stream siap
bind() : berhasil ke 127.0.0.1:0 (port dipilih OS)
listen() : berhasil dengan backlog 8
close() : descriptor dilepas
connect() : gagal ke 127.0.0.1:1 seperti diduga
errno = 111 (Connection refused)Bedah baris demi baris:
- Baris 12 (
::socket(AF_INET, SOCK_STREAM, 0)) — meminta socket TCP IPv4 dari kernel; hasilnya descriptor, dan tanda::menegaskan ini fungsi C global, bukan method class kita. - Baris 21-24 (
sockaddr_in alamat{}) — struct alamat diisi nol dulu ({}), lalu tiga field penting: keluarga alamat, port, dan alamat IP.htons()danhtonl()mengubah bilangan ke network byte order (big-endian) — urutan byte yang dijanjikan ABI jaringan, tema yang akan kembali di Chapter Cross-platform Development. - Baris 23 (
htons(0)) — port0berarti “pilihkan port bebas apa pun”; trik ini membuat contoh pasti berhasil tanpa bentrok dengan service lain, sekaligus menghindari butuh hak istimewa untuk port di bawah 1024. - Baris 25 (
::bind(...)) — kernel mencatat bahwa descriptor ini menempati127.0.0.1pada port pilihan OS;reinterpret_cast<sockaddr*>adalah pola wajib C API: struct spesifik dilempar ke tipe generik. - Baris 33 (
::listen(fd, 8)) — socket berubah dari aktif menjadi pasif;backlog 8adalah antrean maksimal koneksi yang menunggu di-accept()sebelum ditolak. - Baris 41 (
::close(fd)) — melepas descriptor dan port; RAII di Chapter Rule of Zero adalah cara lazim memastikan panggilan ini tidak terlupa. - Baris 51-54 (
::connectyang gagal) — port 1 di loopback tidak memiliki server, sehinggaconnectselalu gagal denganECONNREFUSED(errno 111, Connection refused). Pesan error inilah yang nyata: program jaringan yang baik selalu membacaerrnodanstrerror, bukan menduga-duga.
Kegagalan connect dengan pesan yang pasti adalah hadiah untuk pengujian: perilakunya sama di setiap mesin Linux tanpa server di port itu, berbeda dari mencoba port publik yang hasilnya tergantung jaringan.
Server dan Client yang Saling Berbicara #
Untuk pertukaran data sungguhan dibutuhkan dua program (atau dua thread): server yang memanggil accept() dan client yang memanggil connect(). Berikut pola minimal keduanya — kode ini butuh dua proses yang hidup bersamaan, sehingga kita tampilkan sebagai ilustrasi tanpa klaim output:
int server_fd = ::socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in alamat{};
alamat.sin_family = AF_INET;
alamat.sin_port = htons(5050); // port tetap, diketahui client
alamat.sin_addr.s_addr = INADDR_ANY; // terima dari semua antarmuka
::bind(server_fd, reinterpret_cast<sockaddr*>(&alamat), sizeof(alamat));
::listen(server_fd, 16);
int klien_fd = ::accept(server_fd, nullptr, nullptr); // menunggu (blocking)
char buffer[256] = {};
ssize_t terbaca = ::recv(klien_fd, buffer, sizeof(buffer) - 1, 0);
std::printf("diterima: %s\n", buffer);
::send(klien_fd, "Halo dari server C++\n", 22, 0);
::close(klien_fd);
::close(server_fd);Sisi client mengganti tiga panggilan pertama dengan satu connect() ke 127.0.0.1:5050, lalu send() dan recv() yang sama. Tiga hal yang paling sering menjadi jebakan: accept, recv, dan send blocking secara default (program berhenti menunggu), recv bisa mengembalikan sebagian data saja sehingga butuh loop hingga pesan utuh, dan TCP tidak mengenal “pesan” — ia hanya aliran byte, sehingga protokol aplikasi harus mendefinisikan batas pesannya sendiri.
asio: Model Modern untuk Program Jaringan #
Menulis server serius dengan socket mentah berarti mengelola banyak descriptor, timeout, dan threading secara manual — pekerjaan yang sudah dirapikan oleh asio, library header-only de facto standar untuk C++ modern (tersedia sebagai boost::asio di dalam Boost atau sebagai asio standalone). Pendekatannya asynchronous: Anda mendaftarkan operasi beserta completion handler, dan asio yang mengelola event serta thread di belakang layar:
asio::awaitable<void> sambutan(asio::ip::tcp::socket socket) {
char data[256];
for (;;) {
std::size_t n = co_await socket.async_read_some(
asio::buffer(data), asio::use_awaitable);
co_await asio::async_write(socket, asio::buffer(data, n),
asio::use_awaitable);
}
}Dukungan coroutine C++20 membuat kode async terbaca seperti kode sinkron, dan API yang sama berjalan di Linux, Windows, dan macOS tanpa satu #ifdef jaringan pun.
| Kebutuhan | Pilihan yang wajar |
|---|---|
| Belajar cara kerja jaringan; alat kecil tanpa dependensi | BSD sockets langsung |
| Server/agent produksi, banyak koneksi, timeout, async | asio |
| Bicara HTTP(S) ke API luar | libcurl atau library HTTP khusus |
| RPC antar service | gRPC dan sejenisnya |
| Game real-time, lossy tetapi cepat | UDP mentah atau library di atasnya |
Aturannya sederhana: socket mentah untuk memahami dan untuk tool kecil; asio begitu konkurensi dan portabilitas masuk hitungan; library lebih tinggi begitu protokolnya sendiri sudah kompleks. Dan begitu program mengirim serta menerima data dari dunia luar, pertanyaan berikutnya nyaris selalu sama: format apa untuk data itu? Untuk itulah Chapter berikutnya membahas JSON — format pertukaran data paling umum di jaringan modern.
Referensi: socket(2) — Linux man-pages , ip(7) — Linux man-pages .