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 melaluiepoll(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:
| Aspek | std::net + thread per koneksi | Tokio async |
|---|---|---|
| Koneksi bersamaan | Ratusan (dibatasi RAM & OS) | Puluhan ribu |
| Overhead per koneksi | ~8MB stack per thread | ~KB per task |
| Kompleksitas kode | Lebih sederhana | Lebih kompleks (lifetime, Pin) |
| CPU-intensive work | Alami — thread berjalan paralel | Perlu spawn_blocking |
| Ekosistem | Terbatas | Sangat kaya (reqwest, sqlx, axum) |
| Debugging | Lebih mudah | Lebih 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:#fff3e0Contoh 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::binduntuk server,TcpStream::connectuntuk client — keduanya mengembalikanResult; tangani error bind (port sudah dipakai) dan error connect (server tidak ada) secara eksplisit.- Spawn thread per koneksi untuk server sederhana —
thread::spawn(move || tangani_klien(stream))adalah pola paling mudah untuk concurrency. Tambahkan thread pool jika koneksi bersamaan bisa mencapai ratusan.BufReaderdanBufWriteruntuk efisiensi — baca baris per baris denganBufReader::lines()alih-alihread()langsung.BufWriterakumulasikan data sebelum dikirim — jangan lupaflush().- Selalu setel timeout —
set_read_timeoutdanset_write_timeoutmencegah thread memblok selamanya saat klien tidak merespons. TanganiErrorKind::TimedOutdanErrorKind::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 perluunwrapkarena tidak bisa gagal. Gunakan string parsing hanya untuk input dari pengguna.std::netuntuk 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