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.

cpp
// 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:

text
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:

  1. 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.
  2. Baris 21-24 (sockaddr_in alamat{}) — struct alamat diisi nol dulu ({}), lalu tiga field penting: keluarga alamat, port, dan alamat IP. htons() dan htonl() mengubah bilangan ke network byte order (big-endian) — urutan byte yang dijanjikan ABI jaringan, tema yang akan kembali di Chapter Cross-platform Development.
  3. Baris 23 (htons(0)) — port 0 berarti “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.
  4. Baris 25 (::bind(...)) — kernel mencatat bahwa descriptor ini menempati 127.0.0.1 pada port pilihan OS; reinterpret_cast<sockaddr*> adalah pola wajib C API: struct spesifik dilempar ke tipe generik.
  5. Baris 33 (::listen(fd, 8)) — socket berubah dari aktif menjadi pasif; backlog 8 adalah antrean maksimal koneksi yang menunggu di-accept() sebelum ditolak.
  6. Baris 41 (::close(fd)) — melepas descriptor dan port; RAII di Chapter Rule of Zero adalah cara lazim memastikan panggilan ini tidak terlupa.
  7. Baris 51-54 (::connect yang gagal) — port 1 di loopback tidak memiliki server, sehingga connect selalu gagal dengan ECONNREFUSED (errno 111, Connection refused). Pesan error inilah yang nyata: program jaringan yang baik selalu membaca errno dan strerror, 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:

cpp Pola server TCP (ilustrasi)
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:

cpp Pola asio async (ilustrasi)
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.

KebutuhanPilihan yang wajar
Belajar cara kerja jaringan; alat kecil tanpa dependensiBSD sockets langsung
Server/agent produksi, banyak koneksi, timeout, asyncasio
Bicara HTTP(S) ke API luarlibcurl atau library HTTP khusus
RPC antar servicegRPC dan sejenisnya
Game real-time, lossy tetapi cepatUDP 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 .