Net

Net #

Networking adalah salah satu use case utama Rust — bahasa yang menjanjikan performa setara C dengan keamanan memori yang dijamin compiler. std::net menyediakan primitif networking synchronous yang lengkap: TCP untuk komunikasi yang andal dan terurut, UDP untuk komunikasi cepat tanpa jaminan pengiriman, serta tipe untuk merepresentasikan alamat jaringan. Memahami std::net adalah fondasi penting sebelum melangkah ke async networking dengan Tokio atau Hyper — banyak konsep yang sama, hanya model eksekusinya yang berbeda. Artikel ini membahas TCP server dan client, UDP socket, penanganan koneksi konkuren dengan thread, manajemen timeout, dan kapan synchronous networking sudah tidak cukup lagi.

Tipe Alamat Jaringan #

Sebelum membuat koneksi, Rust menyediakan tipe yang merepresentasikan alamat jaringan secara type-safe.

use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4};

fn main() {
    // IpAddr — bisa IPv4 atau IPv6
    let ipv4: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
    let ipv6: IpAddr = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));

    // Parse dari string
    let ip: IpAddr = "192.168.1.1".parse().expect("IP tidak valid");
    let ip6: IpAddr = "::1".parse().expect("IPv6 tidak valid");

    // Pengecekan tipe
    println!("{}", ipv4.is_ipv4());       // true
    println!("{}", ipv4.is_loopback());   // true (127.0.0.1)
    println!("{}", ipv4.is_private());    // false (127.x.x.x bukan private range)

    let private: IpAddr = "192.168.1.100".parse().unwrap();
    println!("{}", private.is_private()); // true

    // Ipv4Addr konstanta yang berguna
    println!("{}", Ipv4Addr::LOCALHOST);  // 127.0.0.1
    println!("{}", Ipv4Addr::UNSPECIFIED); // 0.0.0.0
    println!("{}", Ipv4Addr::BROADCAST);  // 255.255.255.255

    // SocketAddr — kombinasi IP dan port
    let addr: SocketAddr = "127.0.0.1:8080".parse().expect("Alamat tidak valid");
    println!("IP: {}", addr.ip());        // 127.0.0.1
    println!("Port: {}", addr.port());    // 8080

    // Membuat SocketAddr secara programatik
    let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
    let addr_v4 = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 3000);

    // Resolusi DNS — mengubah hostname menjadi SocketAddr
    use std::net::ToSocketAddrs;
    let addrs: Vec<SocketAddr> = "localhost:8080"
        .to_socket_addrs()
        .expect("Resolusi DNS gagal")
        .collect();
    println!("Resolved: {:?}", addrs);
}

TCP Server — Menerima Koneksi #

TcpListener mendengarkan koneksi masuk pada alamat dan port tertentu. Setiap koneksi yang diterima menghasilkan TcpStream yang bisa dibaca dan ditulis.

use std::net::{TcpListener, TcpStream};
use std::io::{Read, Write, BufRead, BufReader};

fn tangani_klien(mut stream: TcpStream) {
    let peer_addr = stream.peer_addr().unwrap();
    println!("Koneksi baru dari: {}", peer_addr);

    // Baca data dari klien
    let mut buffer = [0u8; 1024];
    match stream.read(&mut buffer) {
        Ok(0) => println!("Klien {} menutup koneksi", peer_addr),
        Ok(n) => {
            let pesan = String::from_utf8_lossy(&buffer[..n]);
            println!("Diterima dari {}: {}", peer_addr, pesan.trim());

            // Kirim respons
            let respons = format!("Echo: {}", pesan.trim());
            stream.write_all(respons.as_bytes()).unwrap();
        }
        Err(e) => eprintln!("Error baca dari {}: {}", peer_addr, e),
    }
}

fn main() {
    // Bind ke alamat — "0.0.0.0" berarti semua interface
    let listener = TcpListener::bind("127.0.0.1:7878")
        .expect("Gagal bind ke port 7878");

    println!("Server mendengarkan di {}", listener.local_addr().unwrap());

    // accept() memblok sampai ada koneksi masuk
    for stream in listener.incoming() {
        match stream {
            Ok(stream) => tangani_klien(stream),
            Err(e) => eprintln!("Error koneksi: {}", e),
        }
    }
}

Server yang Menangani Banyak Koneksi dengan Thread #

Server di atas hanya bisa menangani satu klien sekaligus — klien kedua harus menunggu klien pertama selesai. Untuk server yang nyata, setiap koneksi perlu ditangani di thread terpisah.

use std::net::{TcpListener, TcpStream};
use std::io::{Read, Write};
use std::thread;

fn tangani_klien(mut stream: TcpStream) {
    let peer = stream.peer_addr().unwrap();
    let mut buffer = [0u8; 4096];

    loop {
        match stream.read(&mut buffer) {
            Ok(0) => {
                println!("Klien {} terputus", peer);
                break;
            }
            Ok(n) => {
                let data = &buffer[..n];
                // Echo kembali ke klien
                if stream.write_all(data).is_err() {
                    break;
                }
            }
            Err(e) => {
                eprintln!("Error pada {}: {}", peer, e);
                break;
            }
        }
    }
}

fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
    println!("Echo server berjalan di port 7878");

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                // Spawn thread baru untuk setiap koneksi
                thread::spawn(move || tangani_klien(stream));
            }
            Err(e) => eprintln!("Error accept: {}", e),
        }
    }
}

Server HTTP Sederhana dari Nol #

Untuk memahami cara kerja HTTP di level rendah, kita bisa membangun HTTP server minimal:

use std::net::{TcpListener, TcpStream};
use std::io::{BufRead, BufReader, Write};
use std::thread;

fn tangani_http(mut stream: TcpStream) {
    let peer = stream.peer_addr().unwrap();
    let reader = BufReader::new(stream.try_clone().unwrap());

    // Baca request line dan headers
    let mut baris: Vec<String> = Vec::new();
    for line in reader.lines() {
        match line {
            Ok(l) if l.is_empty() => break,  // baris kosong = akhir headers
            Ok(l) => baris.push(l),
            Err(_) => break,
        }
    }

    if baris.is_empty() {
        return;
    }

    // Parse request line: "GET /path HTTP/1.1"
    let request_line = &baris[0];
    let bagian: Vec<&str> = request_line.split_whitespace().collect();
    if bagian.len() < 2 {
        return;
    }

    let method = bagian[0];
    let path = bagian[1];
    println!("{} {} dari {}", method, path, peer);

    // Buat respons berdasarkan path
    let (status, body) = match (method, path) {
        ("GET", "/") => (
            "200 OK",
            "<h1>Selamat datang!</h1><p>Server Rust berjalan.</p>"
        ),
        ("GET", "/health") => (
            "200 OK",
            r#"{"status": "ok"}"#
        ),
        _ => (
            "404 Not Found",
            "<h1>404 - Halaman Tidak Ditemukan</h1>"
        ),
    };

    let content_type = if path == "/health" {
        "application/json"
    } else {
        "text/html; charset=utf-8"
    };

    let respons = format!(
        "HTTP/1.1 {}\r\nContent-Type: {}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
        status,
        content_type,
        body.len(),
        body
    );

    stream.write_all(respons.as_bytes()).ok();
}

fn main() {
    let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
    println!("HTTP server berjalan di http://localhost:8080");

    for stream in listener.incoming().flatten() {
        thread::spawn(move || tangani_http(stream));
    }
}

TCP Client — Membuat Koneksi #

TcpStream::connect() membuat koneksi TCP ke server.

use std::net::TcpStream;
use std::io::{Read, Write};
use std::time::Duration;

fn main() {
    // Koneksi sederhana
    match TcpStream::connect("127.0.0.1:7878") {
        Ok(mut stream) => {
            println!("Terhubung ke server!");

            // Kirim data
            stream.write_all(b"halo server\n").unwrap();

            // Baca respons
            let mut buffer = [0u8; 1024];
            let n = stream.read(&mut buffer).unwrap();
            println!("Respons: {}", String::from_utf8_lossy(&buffer[..n]));
        }
        Err(e) => eprintln!("Gagal terhubung: {}", e),
    }

    // Koneksi dengan timeout
    let addr = "127.0.0.1:7878".parse().unwrap();
    match TcpStream::connect_timeout(&addr, Duration::from_secs(5)) {
        Ok(stream) => println!("Terhubung dalam batas waktu"),
        Err(e) => eprintln!("Timeout atau gagal: {}", e),
    }
}

HTTP Client Sederhana dari Nol #

use std::net::TcpStream;
use std::io::{BufRead, BufReader, Write, Read};

fn http_get(host: &str, path: &str) -> Result<String, Box<dyn std::error::Error>> {
    // Buat koneksi ke port 80
    let mut stream = TcpStream::connect(format!("{}:80", host))?;

    // Kirim HTTP request
    let request = format!(
        "GET {} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n",
        path, host
    );
    stream.write_all(request.as_bytes())?;

    // Baca seluruh respons
    let mut respons = String::new();
    stream.read_to_string(&mut respons)?;

    // Pisahkan headers dari body
    if let Some(pos) = respons.find("\r\n\r\n") {
        Ok(respons[pos + 4..].to_string())
    } else {
        Ok(respons)
    }
}

fn main() {
    // Untuk HTTPS, gunakan crate reqwest atau rustls
    // HTTP plain text semakin jarang di production
    match http_get("example.com", "/") {
        Ok(body) => println!("Body: {}...", &body[..body.len().min(200)]),
        Err(e) => eprintln!("Error: {}", e),
    }
}
Untuk HTTP client di kode produksi, gunakan crate reqwest — ia menangani HTTPS, redirect, cookie, connection pooling, dan timeout secara otomatis. HTTP client manual dari nol hanya berguna untuk memahami protokol atau skenario yang sangat spesifik.

Buffered I/O pada TcpStream #

Membaca dan menulis satu byte atau satu karakter pada satu waktu sangat tidak efisien karena setiap operasi menghasilkan system call. BufReader dan BufWriter menambahkan buffer di atas stream untuk mengurangi jumlah system call.

use std::net::{TcpListener, TcpStream};
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::thread;

fn tangani_line_protocol(stream: TcpStream) {
    let peer = stream.peer_addr().unwrap();

    // Clone stream untuk reader dan writer terpisah
    let stream_writer = stream.try_clone().expect("Gagal clone stream");

    let reader = BufReader::new(stream);
    let mut writer = BufWriter::new(stream_writer);

    // Baca baris demi baris — efisien karena BufReader mem-buffer
    for line in reader.lines() {
        match line {
            Ok(baris) => {
                println!("Dari {}: {}", peer, baris);

                // Proses perintah sederhana
                let respons = match baris.trim() {
                    "PING" => "PONG\n".to_string(),
                    "TIME" => format!("{}\n", chrono_sekarang()),
                    "QUIT" => {
                        writer.write_all(b"BYE\n").ok();
                        writer.flush().ok();
                        break;
                    }
                    perintah => format!("UNKNOWN: {}\n", perintah),
                };

                // BufWriter tidak langsung kirim — flush diperlukan
                writer.write_all(respons.as_bytes()).ok();
                writer.flush().ok();  // kirim sekarang
            }
            Err(_) => break,
        }
    }
}

fn chrono_sekarang() -> String {
    use std::time::{SystemTime, UNIX_EPOCH};
    let ts = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs();
    ts.to_string()
}

fn main() {
    let listener = TcpListener::bind("127.0.0.1:6379").unwrap();
    println!("Server line-protocol berjalan di port 6379");
    println!("Coba: nc localhost 6379 lalu ketik PING, TIME, atau QUIT");

    for stream in listener.incoming().flatten() {
        thread::spawn(move || tangani_line_protocol(stream));
    }
}

Timeout pada TcpStream #

Tanpa timeout, operasi baca dan tulis pada socket bisa memblok selamanya — berbahaya untuk server yang mengharapkan respons dalam waktu tertentu.

use std::net::TcpStream;
use std::io::{Read, Write};
use std::time::Duration;

fn main() {
    let mut stream = TcpStream::connect("127.0.0.1:7878").unwrap();

    // Setel timeout untuk operasi baca
    stream.set_read_timeout(Some(Duration::from_secs(5)))
        .expect("Gagal setel read timeout");

    // Setel timeout untuk operasi tulis
    stream.set_write_timeout(Some(Duration::from_secs(5)))
        .expect("Gagal setel write timeout");

    stream.write_all(b"request").unwrap();

    let mut buffer = [0u8; 1024];
    match stream.read(&mut buffer) {
        Ok(n) => println!("Diterima: {}", String::from_utf8_lossy(&buffer[..n])),
        Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
            eprintln!("Timeout — server tidak merespons dalam 5 detik");
        }
        Err(e) if e.kind() == std::io::ErrorKind::TimedOut => {
            eprintln!("Timeout — server tidak merespons dalam 5 detik");
        }
        Err(e) => eprintln!("Error: {}", e),
    }

    // Hapus timeout — kembali ke blocking tanpa batas
    stream.set_read_timeout(None).unwrap();

    // Cek timeout yang sudah disetel
    println!("{:?}", stream.read_timeout().unwrap());   // Some(5s)
    println!("{:?}", stream.write_timeout().unwrap());  // Some(5s)
}

UDP Socket #

UDP tidak memiliki koneksi — setiap paket dikirim secara independen tanpa handshake. Lebih cepat dari TCP tapi tidak menjamin pengiriman atau urutan.

use std::net::UdpSocket;
use std::time::Duration;

fn main() {
    // UDP Server
    let socket = UdpSocket::bind("127.0.0.1:8888")
        .expect("Gagal bind UDP socket");

    println!("UDP server mendengarkan di port 8888");

    // recv_from mengembalikan (jumlah_byte, alamat_pengirim)
    let mut buffer = [0u8; 1024];
    loop {
        match socket.recv_from(&mut buffer) {
            Ok((n, addr)) => {
                let pesan = String::from_utf8_lossy(&buffer[..n]);
                println!("Dari {}: {}", addr, pesan.trim());

                // Kirim respons ke pengirim
                let respons = format!("Echo: {}", pesan.trim());
                socket.send_to(respons.as_bytes(), addr).ok();
            }
            Err(e) => eprintln!("Error: {}", e),
        }
    }
}
use std::net::UdpSocket;
use std::time::Duration;

fn main() {
    // UDP Client
    let socket = UdpSocket::bind("0.0.0.0:0")  // port 0 = OS pilihkan port bebas
        .expect("Gagal bind");

    socket.set_read_timeout(Some(Duration::from_secs(3))).unwrap();

    // Kirim pesan
    let server = "127.0.0.1:8888";
    socket.send_to(b"halo UDP server", server).expect("Gagal kirim");

    // Terima respons
    let mut buffer = [0u8; 1024];
    match socket.recv_from(&mut buffer) {
        Ok((n, addr)) => {
            println!("Respons dari {}: {}", addr, String::from_utf8_lossy(&buffer[..n]));
        }
        Err(e) => eprintln!("Timeout atau error: {}", e),
    }

    // connect() pada UDP — bukan koneksi nyata, hanya filter alamat
    // Setelah ini, send/recv hanya dengan alamat yang ditentukan
    socket.connect(server).unwrap();
    socket.send(b"pesan via connected UDP").unwrap();
}
sequenceDiagram
    participant Client
    participant TCPServer as TCP Server
    participant UDPServer as UDP Server

    Note over Client,TCPServer: TCP — connection-oriented
    Client->>TCPServer: SYN
    TCPServer->>Client: SYN-ACK
    Client->>TCPServer: ACK (handshake selesai)
    Client->>TCPServer: Data
    TCPServer->>Client: ACK + Data
    Client->>TCPServer: FIN (tutup koneksi)

    Note over Client,UDPServer: UDP — connectionless
    Client->>UDPServer: Datagram (langsung)
    UDPServer->>Client: Datagram (langsung, tidak dijamin)
    Client->>UDPServer: Datagram (mungkin hilang, tidak ada notifikasi)

Thread Pool untuk TCP Server #

Server yang membuat thread baru untuk setiap koneksi tidak skalabel — ribuan koneksi bersamaan berarti ribuan thread, yang menghabiskan memori dan membebani scheduler OS. Thread pool membatasi jumlah thread sambil tetap melayani banyak koneksi.

use std::net::{TcpListener, TcpStream};
use std::io::{Read, Write};
use std::sync::{Arc, Mutex};
use std::sync::mpsc;
use std::thread;

type Job = Box<dyn FnOnce() + Send + 'static>;

struct ThreadPool {
    _workers: Vec<thread::JoinHandle<()>>,
    sender: mpsc::Sender<Option<Job>>,
}

impl ThreadPool {
    fn new(ukuran: usize) -> Self {
        let (tx, rx) = mpsc::channel::<Option<Job>>();
        let rx = Arc::new(Mutex::new(rx));

        let workers = (0..ukuran).map(|id| {
            let rx = Arc::clone(&rx);
            thread::spawn(move || loop {
                let pesan = rx.lock().unwrap().recv().unwrap();
                match pesan {
                    Some(job) => job(),
                    None => {
                        println!("Worker {} berhenti", id);
                        break;
                    }
                }
            })
        }).collect();

        ThreadPool { _workers: workers, sender: tx }
    }

    fn execute<F: FnOnce() + Send + 'static>(&self, f: F) {
        self.sender.send(Some(Box::new(f))).unwrap();
    }
}

impl Drop for ThreadPool {
    fn drop(&mut self) {
        // Kirim sinyal berhenti ke semua worker
        for _ in &self._workers {
            self.sender.send(None).unwrap();
        }
    }
}

fn tangani_koneksi(mut stream: TcpStream) {
    let mut buffer = [0u8; 1024];
    if let Ok(n) = stream.read(&mut buffer) {
        let respons = format!(
            "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, World!"
        );
        stream.write_all(respons.as_bytes()).ok();
    }
}

fn main() {
    let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
    let pool = ThreadPool::new(8);  // maksimal 8 thread concurrent

    println!("Server dengan thread pool berjalan di port 8080");

    for stream in listener.incoming().flatten() {
        pool.execute(move || tangani_koneksi(stream));
    }
}

Non-Blocking I/O #

Mode non-blocking memungkinkan socket dioperasikan tanpa memblok thread — read() dan write() segera kembali meski tidak ada data.

use std::net::{TcpListener, TcpStream};
use std::io::{self, Read, Write};

fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();

    // Setel listener ke non-blocking
    listener.set_nonblocking(true).unwrap();

    let mut koneksi: Vec<TcpStream> = Vec::new();

    loop {
        // Coba terima koneksi baru — tidak memblok
        match listener.accept() {
            Ok((stream, addr)) => {
                println!("Koneksi baru dari: {}", addr);
                stream.set_nonblocking(true).unwrap();
                koneksi.push(stream);
            }
            Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
                // Tidak ada koneksi baru — lanjut
            }
            Err(e) => eprintln!("Error accept: {}", e),
        }

        // Proses semua koneksi yang ada
        let mut masih_aktif = Vec::new();
        for mut stream in koneksi.drain(..) {
            let mut buffer = [0u8; 1024];
            match stream.read(&mut buffer) {
                Ok(0) => {
                    // Koneksi ditutup oleh klien
                    println!("Klien {} terputus", stream.peer_addr().unwrap());
                }
                Ok(n) => {
                    // Ada data — proses dan kirim balik
                    stream.write_all(&buffer[..n]).ok();
                    masih_aktif.push(stream);
                }
                Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
                    // Tidak ada data saat ini — koneksi masih hidup
                    masih_aktif.push(stream);
                }
                Err(e) => eprintln!("Error baca: {}", e),
            }
        }
        koneksi = masih_aktif;

        // Busy-wait yang tidak efisien — di production gunakan epoll/kqueue via Tokio
        std::thread::sleep(std::time::Duration::from_millis(1));
    }
}
Non-blocking I/O dengan busy-wait loop seperti di atas menghabiskan CPU secara tidak efisien. Di production, gunakan event-driven I/O melalui epoll (Linux), kqueue (macOS), atau IOCP (Windows). Tokio mengabstraksi semua ini dengan API yang lebih ergonomis — inilah alasan utama menggunakan async networking di Rust.

Kapan Beralih ke Async Networking #

std::net adalah synchronous dan blocking — setiap operasi memblok thread yang memanggilnya. Untuk server yang menangani banyak koneksi bersamaan, ini menjadi bottleneck.

Gunakan std::net jika:
  ✓ Jumlah koneksi bersamaan rendah (puluhan hingga ratusan)
  ✓ Setiap koneksi membutuhkan CPU-intensive work — thread per koneksi justru efisien
  ✓ Prototyping atau memahami networking dari level dasar
  ✓ Tool CLI yang membuat satu atau beberapa koneksi, bukan server
  ✓ Tidak ada dependency async di seluruh codebase

Beralih ke Tokio + async/await jika:
  ✗ Server perlu menangani ribuan koneksi bersamaan (C10K problem)
  ✗ Banyak operasi I/O-bound yang menunggu network atau disk
  ✗ Perlu integrasi dengan ecosystem async (reqwest, sqlx, axum)
  ✗ Latency per-request perlu diminimalkan
  ✗ Sudah menggunakan async di bagian lain codebase

Perbandingan model threading:

Aspekstd::net + thread per koneksiTokio async
Koneksi bersamaanRatusan (dibatasi RAM & OS)Puluhan ribu
Overhead per koneksi~8MB stack per thread~KB per task
Kompleksitas kodeLebih sederhanaLebih kompleks (lifetime, Pin)
CPU-intensive workAlami — thread berjalan paralelPerlu spawn_blocking
EkosistemTerbatasSangat kaya (reqwest, sqlx, axum)
DebuggingLebih mudahLebih sulit
flowchart TD
    A{Jenis aplikasi networking?} --> B{Jumlah koneksi bersamaan?}
    B -- Sedikit, puluhan --> C["std::net + thread per koneksi\nSederhana, cukup untuk use case ini"]
    B -- Banyak, ribuan --> D["Tokio + async/await\nEfisien untuk I/O-bound massif"]

    A --> E{CLI tool atau client?}
    E -- Ya --> F["std::net langsung\natau reqwest untuk HTTP"]
    E -- Tidak --> B

    C --> G{Perlu HTTP/HTTPS?}
    G -- Ya --> H["Gunakan framework:\nAxum, Actix-web, Rocket"]
    G -- Tidak --> I["std::net TCP/UDP sudah cukup"]

    D --> J{Protokol apa?}
    J -- HTTP/REST --> K["Axum atau Actix-web"]
    J -- TCP custom --> L["Tokio TcpListener"]
    J -- UDP --> M["Tokio UdpSocket"]

    style C fill:#e8f5e9
    style D fill:#e3f2fd
    style F fill:#e8f5e9
    style H fill:#fff3e0
    style K fill:#fff3e0

Contoh Ekuivalen: std::net vs Tokio #

// ===== std::net (synchronous) =====
use std::net::TcpListener;
use std::io::{Read, Write};
use std::thread;

fn server_sync() {
    let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
    for stream in listener.incoming().flatten() {
        thread::spawn(move || {
            let mut stream = stream;
            let mut buf = [0u8; 1024];
            let n = stream.read(&mut buf).unwrap();
            stream.write_all(&buf[..n]).unwrap();
        });
    }
}
// ===== Tokio (asynchronous) =====
// Cargo.toml: tokio = { version = "1", features = ["full"] }
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};

#[tokio::main]
async fn server_async() {
    let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap();
    loop {
        let (mut stream, _) = listener.accept().await.unwrap();
        tokio::spawn(async move {
            let mut buf = [0u8; 1024];
            let n = stream.read(&mut buf).await.unwrap();
            stream.write_all(&buf[..n]).await.unwrap();
        });
    }
}

Strukturnya hampir identik — perbedaan utama adalah await dan async. Memahami std::net terlebih dahulu membuat transisi ke Tokio lebih mudah karena konsepnya sama.


Ringkasan #

  • TcpListener::bind untuk server, TcpStream::connect untuk client — keduanya mengembalikan Result; tangani error bind (port sudah dipakai) dan error connect (server tidak ada) secara eksplisit.
  • Spawn thread per koneksi untuk server sederhanathread::spawn(move || tangani_klien(stream)) adalah pola paling mudah untuk concurrency. Tambahkan thread pool jika koneksi bersamaan bisa mencapai ratusan.
  • BufReader dan BufWriter untuk efisiensi — baca baris per baris dengan BufReader::lines() alih-alih read() langsung. BufWriter akumulasikan data sebelum dikirim — jangan lupa flush().
  • Selalu setel timeoutset_read_timeout dan set_write_timeout mencegah thread memblok selamanya saat klien tidak merespons. Tangani ErrorKind::TimedOut dan ErrorKind::WouldBlock.
  • UDP untuk kecepatan, TCP untuk keandalan — UDP tidak menjamin pengiriman atau urutan, tapi overhead-nya jauh lebih rendah. Cocok untuk game, streaming, DNS, dan metrics.
  • SocketAddr::from(([127,0,0,1], 8080)) lebih aman dari string parsing — tidak perlu unwrap karena tidak bisa gagal. Gunakan string parsing hanya untuk input dari pengguna.
  • std::net untuk koneksi terbatas, Tokio untuk ribuan koneksi — thread per koneksi tidak skalabel karena setiap thread memakan ~8MB stack. Tokio task hanya beberapa KB overhead.
  • Non-blocking I/O tanpa event loop tidak efisien — busy-wait loop menghabiskan CPU. Jika butuh non-blocking, gunakan Tokio yang sudah mengintegrasikan epoll/kqueue/IOCP.

← Sebelumnya: Process
About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact