rust-network - 解説

実装の詳細

ソケットの基本操作

use std::net::{TcpStream, TcpListener, UdpSocket};

// TCP サーバー
let listener = TcpListener::bind("0.0.0.0:8080")?;
for stream in listener.incoming() {
    let stream = stream?;
    // handle stream
}

// TCP クライアント
let mut stream = TcpStream::connect("127.0.0.1:8080")?;
stream.write_all(b"Hello")?;

// UDP
let socket = UdpSocket::bind("0.0.0.0:9000")?;
socket.send_to(b"Hello", "127.0.0.1:9001")?;
let (n, from) = socket.recv_from(&mut buffer)?;

DNSパケット構造

DNS Query:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|         Transaction ID (16bit)              |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR| Opcode |AA|TC|RD|RA|   Z    |   RCODE   |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|              QDCOUNT (16bit)                |
|              ANCOUNT (16bit)                |
|              NSCOUNT (16bit)                |
|              ARCOUNT (16bit)                |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|              Question Section               |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

ホスト名のエンコード

// "example.com" を DNS形式にエンコード
// [7]example[3]com[0]

fn encode_hostname(hostname: &str) -> Vec<u8> {
    let mut encoded = Vec::new();
    for part in hostname.split('.') {
        encoded.push(part.len() as u8);
        encoded.extend_from_slice(part.as_bytes());
    }
    encoded.push(0);  // 終端
    encoded
}

よくある間違い

1. パケット分割の未考慮

// 間違い: 一度の read で全データ取得を仮定
let n = stream.read(&mut buffer)?;
// TCPはストリーム → データが分割される可能性

// 正しい: メッセージ境界を自分で管理
// 例: 長さプレフィックス
let mut len_buf = [0u8; 4];
stream.read_exact(&mut len_buf)?;
let len = u32::from_be_bytes(len_buf) as usize;
let mut data = vec![0u8; len];
stream.read_exact(&mut data)?;

2. UDPのパケット順序

// UDPは順序保証なし
// パケット1 → パケット2 → パケット3 と送信
// 受信順: パケット2 → パケット1 → パケット3 かも

// 必要なら自分でシーケンス番号を管理
struct Packet {
    sequence: u32,
    data: Vec<u8>,
}

3. タイムアウト設定忘れ

// 間違い: 永遠にブロック
let (n, from) = socket.recv_from(&mut buffer)?;

// 正しい: タイムアウト設定
socket.set_read_timeout(Some(Duration::from_secs(5)))?;
match socket.recv_from(&mut buffer) {
    Ok((n, from)) => { /* データ受信 */ }
    Err(e) if e.kind() == ErrorKind::WouldBlock => { /* タイムアウト */ }
    Err(e) => { /* その他エラー */ }
}

パフォーマンス最適化

バッファサイズ

// MTU を考慮
// Ethernet MTU: 1500 bytes
// IP header: 20 bytes
// UDP header: 8 bytes
// → UDP payload: 1472 bytes

const UDP_PAYLOAD_MAX: usize = 1472;

// TCP では大きめのバッファ
const TCP_BUFFER_SIZE: usize = 64 * 1024;  // 64KB

ソケットオプション

use std::net::TcpStream;

let stream = TcpStream::connect(addr)?;

// Nagle アルゴリズム無効化(低遅延)
stream.set_nodelay(true)?;

// 読み書きタイムアウト
stream.set_read_timeout(Some(Duration::from_secs(30)))?;
stream.set_write_timeout(Some(Duration::from_secs(30)))?;

// 非ブロッキングモード
stream.set_nonblocking(true)?;

発展トピック

非同期I/O

// mio を使用した低レベル非同期I/O
use mio::{Events, Interest, Poll, Token};
use mio::net::TcpListener;

let mut poll = Poll::new()?;
let mut events = Events::with_capacity(1024);

let mut listener = TcpListener::bind(addr)?;
poll.registry().register(&mut listener, Token(0), Interest::READABLE)?;

loop {
    poll.poll(&mut events, None)?;
    for event in &events {
        match event.token() {
            Token(0) => {
                // 新しい接続を受け入れ
            }
            Token(n) => {
                // クライアントからのデータ
            }
        }
    }
}

UDPホールパンチング

// NAT越えの基本手順
// 1. 両方のクライアントがSTUNサーバーに接続
// 2. 外部IP:ポートを取得
// 3. 相手の外部アドレスを交換
// 4. 互いにUDPパケットを送信

fn punch_hole(local: &UdpSocket, peer_external: SocketAddr) {
    // パケットを送信(NAT穴を開ける)
    local.send_to(b"PUNCH", peer_external).ok();

    // 相手からのパケットを待つ
    let mut buf = [0u8; 1024];
    local.recv_from(&mut buf).ok();
}