Socket adalah abstraksi komunikasi dua arah yang disediakan kernel. Berbeda dengan pipe yang hanya bisa antar proses berkerabat, socket dapat menghubungkan proses di mesin yang sama (Unix domain socket, AF_UNIX) atau di mesin berbeda melalui jaringan (TCP/IP, AF_INET). API socket cukup besar—mencakup socket, bind, listen, accept, connect, send, recv—sehingga pembahasan penuh membutuhkan satu section tersendiri (lihat section Jaringan dan Protokol). Pada chapter ini kita memperkenalkan empat langkah inti: membuat socket, bind, listen, dan connect, dengan dua demo konkret: Unix domain socket untuk IPC lokal dan TCP socket IPv4 sebagai persiapan komunikasi jaringan.
Empat Langkah Membuat Socket Server #
Membuat server socket selalu mengikuti empat langkah berurutan:
socket(domain, type, protocol): Membuat file descriptor socket baru.domainmenentukan jenis komunikasi (AF_UNIXuntuk lokal,AF_INETuntuk IPv4).typemenentukan semantik (SOCK_STREAMuntuk TCP-like,SOCK_DGRAMuntuk UDP-like).bind(fd, addr, len): Mengaitkan socket ke alamat—file path untuk Unix domain, atau IP:port untuk IPv4.listen(fd, backlog): Menandai socket sebagai pasif—siap menerima koneksi.backlogadalah batas antrian koneksi yang belum di-accept.accept(fd, ...): Memblokir hingga klien connect, lalu mengembalikan file descriptor baru untuk koneksi tersebut. Socket asli tetap mendengarkan koneksi lain.
/* File: socket_unix_dasar.c - Unix domain socket: buat, bind, listen */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
int main(void) {
/* Langkah 1: buat socket */
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
return 1;
}
printf("Socket dibuat, fd=%d\n", fd);
/* Langkah 2: bind ke path /tmp/socket_demo */
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, "/tmp/socket_demo", sizeof(addr.sun_path) - 1);
unlink("/tmp/socket_demo"); /* hapus kalau sudah ada */
if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) != 0) {
perror("bind");
close(fd);
return 1;
}
printf("Bind ke %s berhasil\n", addr.sun_path);
/* Langkah 3: listen */
if (listen(fd, 5) != 0) {
perror("listen");
close(fd);
return 1;
}
printf("Listen berhasil, menunggu koneksi...\n");
close(fd);
unlink("/tmp/socket_demo");
printf("Socket ditutup dan file dihapus.\n");
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -o socket_unix_dasar socket_unix_dasar.c
timeout 5 ./socket_unix_dasarOutput yang Dihasilkan:
Socket dibuat, fd=3
Bind ke /tmp/socket_demo berhasil
Listen berhasil, menunggu koneksi...
Socket ditutup dan file dihapus.Mari kita telaah baris-baris kode di atas:
- Baris 9 (
socket(AF_UNIX, SOCK_STREAM, 0)): Membuat socket Unix domain.AF_UNIXberarti komunikasi antar-proses di mesin yang sama lewat filesystem path.SOCK_STREAMberarti koneksi stream dua arah (mirip TCP, tetapi lokal). Argumen ketiga0memilih protokol default untuk kombinasi domain+type. - Baris 14-18 (
struct sockaddr_un addr): Struktur alamat untuk Unix domain socket. Fieldsun_familywajibAF_UNIX. Fieldsun_pathadalah path file di filesystem yang akan menjadi alamat socket. Path harus cukup pendek (batassizeof(addr.sun_path)biasanya 108 byte di Linux). - Baris 20 (
unlink("/tmp/socket_demo")): Menghapus file socket lama bila ada. Tanpaunlink,bindgagal denganEADDRINUSEbila path sudah dipakai socket sebelumnya yang tidak ditutup dengan benar. - Baris 21 (
bind(fd, ...)): Mengaitkan socket ke path. Kernel membuat file khusus bertipe socket di filesystem. Proses lain dapat connect ke path ini. - Baris 28 (
listen(fd, 5)): Mengubah socket menjadi pasif. Angka5adalah backlog—jumlah koneksi yang dapat menunggu di antrianacceptsebelum ditolak. Pada Linux modern, nilai ini adalah hint saja; kernel mungkin mengalokasikan lebih. - Baris 33-34 (
close(fd); unlink(...)): Menutup socket lalu menghapus file socket dari filesystem. Tanpaunlink, file socket tetap ada di filesystem tetapi tidak terpakai—sampah.
Klien-Server Unix Domain Socket Lengkap #
Demo berikut menjalankan server di parent dan klien di child, lewat fork. Server menerima koneksi, membaca pesan, membalas. Klien connect, mengirim pesan, membaca balasan.
/* File: socket_unix_complete.c - klien-server Unix domain socket */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <string.h>
#include <time.h>
#define SOCK_PATH "/tmp/socket_demo2"
static void jeda_ms(int ms) {
struct timespec ts = { ms / 1000, (ms % 1000) * 1000000L };
nanosleep(&ts, NULL);
}
static void jalankan_server(void) {
int server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (server_fd < 0) { perror("socket"); _exit(1); }
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);
unlink(SOCK_PATH);
if (bind(server_fd, (struct sockaddr *) &addr, sizeof(addr)) != 0) {
perror("bind"); _exit(1);
}
if (listen(server_fd, 5) != 0) { perror("listen"); _exit(1); }
printf("[SERVER] menunggu koneksi...\n");
fflush(stdout);
/* Langkah 4: accept - memblokir hingga klien connect */
int client_fd = accept(server_fd, NULL, NULL);
if (client_fd < 0) { perror("accept"); _exit(1); }
printf("[SERVER] koneksi diterima\n");
fflush(stdout);
char buf[128];
ssize_t n = read(client_fd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("[SERVER] menerima: %s\n", buf);
fflush(stdout);
}
const char *balasan = "Halo Klien, ini Server!";
write(client_fd, balasan, strlen(balasan));
close(client_fd);
close(server_fd);
unlink(SOCK_PATH);
}
static void jalankan_klien(void) {
/* Tunggu server siap */
jeda_ms(100);
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) { perror("socket"); _exit(1); }
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);
if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) != 0) {
perror("connect"); _exit(1);
}
const char *pesan = "Halo Server, ini Klien!";
write(fd, pesan, strlen(pesan));
char buf[128];
ssize_t n = read(fd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("[KLIEN] menerima: %s\n", buf);
fflush(stdout);
}
close(fd);
}
int main(void) {
fflush(stdout);
pid_t pid = fork();
if (pid == 0) { jalankan_klien(); _exit(0); }
jalankan_server();
waitpid(pid, NULL, 0);
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -o socket_unix_complete socket_unix_complete.c
timeout 5 ./socket_unix_completeOutput yang Dihasilkan:
[SERVER] menunggu koneksi...
[SERVER] koneksi diterima
[SERVER] menerima: Halo Server, ini Klien!
[KLIEN] menerima: Halo Klien, ini Server!Mari kita telaah baris-baris kode di atas:
- Baris 16-46 (
jalankan_server): Fungsi server—membuat socket, bind, listen, laluaccept.acceptmemblokir hingga klien connect, lalu mengembalikanclient_fdbaru. Setelah itu, server membaca pesan klien dan menulis balasan. - Baris 30 (
accept(server_fd, NULL, NULL)): Mengembalikan file descriptor baru untuk koneksi yang masuk. Socket asliserver_fdtetap mendengarkan koneksi lain—kita bisa memanggilacceptlagi untuk koneksi kedua. Argumen kedua dan ketigaNULLberarti kita tidak peduli alamat klien (bisa diisistruct sockaddr_ununtuk membaca path klien). - Baris 41 (
write(client_fd, ...)): Setelah membaca pesan, server menulis balasan keclient_fd(bukanserver_fd!). Inilah inti: koneksi aktif memiliki descriptor sendiri, terpisah dari listening socket. - Baris 60 (
jeda_ms(100)di klien): Klien menunggu 100 ms sebelum connect untuk memastikan server sudah memanggillisten. Tanpa jeda,connectbisa gagal denganECONNREFUSEDbila server belum siap. - Baris 73 (
connect(fd, ...)): Menghubungkan socket klien ke alamat server. Setelah sukses,fddapat dipakai untukread/writeseperti file biasa.
TCP Socket IPv4: Persiapan Jaringan #
Untuk komunikasi lintas mesin, gunakan AF_INET (IPv4) atau AF_INET6 (IPv6). Struktur alamat berubah dari sockaddr_un menjadi sockaddr_in, dengan field sin_port (port jaringan) dan sin_addr (IP address).
/* File: socket_tcp_dasar.c - TCP socket IPv4: buat, bind, listen */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(void) {
/* Buat TCP socket (IPv4) */
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
return 1;
}
printf("TCP socket dibuat, fd=%d\n", fd);
/* Aktifkan SO_REUSEADDR untuk demo cepat restart */
int opt = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
/* Bind ke 127.0.0.1:8080 */
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) != 0) {
perror("bind");
close(fd);
return 1;
}
printf("Bind ke 127.0.0.1:8080 berhasil\n");
if (listen(fd, 5) != 0) {
perror("listen");
close(fd);
return 1;
}
printf("Listen berhasil. Socket siap menerima koneksi TCP.\n");
close(fd);
printf("Socket ditutup.\n");
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -o socket_tcp_dasar socket_tcp_dasar.c
timeout 5 ./socket_tcp_dasarOutput yang Dihasilkan:
TCP socket dibuat, fd=3
Bind ke 127.0.0.1:8080 berhasil
Listen berhasil. Socket siap menerima koneksi TCP.
Socket ditutup.Mari kita telaah baris-baris kode di atas:
- Baris 10 (
socket(AF_INET, SOCK_STREAM, 0)):AF_INETberarti IPv4.SOCK_STREAMdi atas IPv4 otomatis memilih protokol TCP—tidak perlu argumen ketiga. - Baris 17 (
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, ...)): OpsiSO_REUSEADDRmengizinkan bind ke port yang baru saja dipakai proses keluar dalam keadaanTIME_WAIT. Tanpa opsi ini, restart server cepat bisa gagal denganEADDRINUSE. - Baris 20-23 (
struct sockaddr_in addr): Struktur alamat IPv4. Fieldsin_family=AF_INET. Fieldsin_portharus dalam network byte order—gunakanhtons(host-to-network-short) untuk mengkonversi. Fieldsin_addr.s_addrpakaihtonl(INADDR_LOOPBACK)=127.0.0.1(loopback, hanya koneksi dari mesin yang sama). - Baris 25 (
bind(fd, ...)): Mengaitkan socket ke IP:port. Berbeda dari Unix domain yang membuat file path, bind TCP hanya memakai nomor port di kernel—tidak ada file di filesystem. htonl/htons: Konversi byte order. Arsitektur x86 menyimpan angka sebagai little-endian, tetapi TCP/IP mensyaratkan network byte order big-endian.htonsmengkonversi 16-bit (untuk port),htonl32-bit (untuk IP).