Network Byte Order가 적용되는 실제 코드

C, Go, Python, Rust에서 Network Byte Order를 다루는 실제 코드 예제입니다.


개념 복습

Network Byte Order = Big-endian (RFC 1700)
Host Byte Order = 시스템마다 다름

네트워크 통신 시:
Host → Network: htons(), htonl()
Network → Host: ntohs(), ntohl()

C 언어

#include <stdio.h>
#include <stdint.h>
#include <arpa/inet.h>  // Unix/Linux
// Windows: #include <winsock2.h>

int main() {
    // 포트 번호 변환 (16비트)
    uint16_t host_port = 8080;
    uint16_t net_port = htons(host_port);

    printf("Host order: 0x%04X\n", host_port);  // 0x1F90
    printf("Network order: 0x%04X\n", net_port); // Big-endian

    // IP 주소 변환 (32비트)
    uint32_t host_ip = 0xC0A80001;  // 192.168.0.1
    uint32_t net_ip = htonl(host_ip);

    // 역변환
    uint16_t back_to_host = ntohs(net_port);
    printf("Back to host: %d\n", back_to_host);  // 8080

    return 0;
}

실제 소켓 프로그래밍 예제:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int create_server(int port) {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);        // 포트: 반드시 변환!
    addr.sin_addr.s_addr = INADDR_ANY;  // 0.0.0.0 - 이미 네트워크 순서

    bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
    listen(sockfd, 5);

    return sockfd;
}

// 커스텀 프로토콜 메시지 전송
struct message {
    uint32_t type;
    uint32_t length;
    char data[256];
};

void send_message(int sockfd, struct message *msg) {
    // 네트워크 바이트 순서로 변환
    uint32_t net_type = htonl(msg->type);
    uint32_t net_length = htonl(msg->length);

    send(sockfd, &net_type, sizeof(net_type), 0);
    send(sockfd, &net_length, sizeof(net_length), 0);
    send(sockfd, msg->data, msg->length, 0);
}

Go 언어

package main

import (
    "encoding/binary"
    "fmt"
    "net"
)

func main() {
    // Go의 encoding/binary 패키지 사용

    // Big-endian (Network byte order)
    buf := make([]byte, 4)
    binary.BigEndian.PutUint32(buf, 0x12345678)
    fmt.Printf("Big-endian: % X\n", buf)  // 12 34 56 78

    // Little-endian
    binary.LittleEndian.PutUint32(buf, 0x12345678)
    fmt.Printf("Little-endian: % X\n", buf)  // 78 56 34 12

    // 네트워크 프로토콜 구현 예
    writeNetworkPacket()
}

// TCP 패킷 헤더 작성 예제
func writeNetworkPacket() {
    packet := make([]byte, 8)

    // 메시지 타입 (2바이트) - Network byte order
    binary.BigEndian.PutUint16(packet[0:2], 0x0001)

    // 페이로드 길이 (4바이트) - Network byte order
    binary.BigEndian.PutUint32(packet[2:6], 1024)

    // 플래그 (2바이트) - Network byte order
    binary.BigEndian.PutUint16(packet[6:8], 0x8000)

    fmt.Printf("Packet: % X\n", packet)
}

// 실제 TCP 서버 예제
func tcpServer() {
    listener, _ := net.Listen("tcp", ":8080")
    conn, _ := listener.Accept()

    // 4바이트 길이 헤더 읽기
    header := make([]byte, 4)
    conn.Read(header)

    // Network byte order에서 변환
    length := binary.BigEndian.Uint32(header)

    // 본문 읽기
    body := make([]byte, length)
    conn.Read(body)
}

Python

import struct
import socket

# struct 모듈의 포맷 문자
# '>' : Big-endian (Network byte order)
# '<' : Little-endian
# '!' : Network byte order (= Big-endian)
# '@' : Native byte order

def endian_demo():
    value = 0x12345678

    # Big-endian 패킹
    big = struct.pack('>I', value)
    print(f"Big-endian: {big.hex()}")  # 12345678

    # Little-endian 패킹
    little = struct.pack('<I', value)
    print(f"Little-endian: {little.hex()}")  # 78563412

    # Network byte order (권장)
    network = struct.pack('!I', value)
    print(f"Network order: {network.hex()}")  # 12345678

def socket_example():
    """실제 소켓 프로그래밍 예제"""

    # 포트 변환 (C의 htons와 동일)
    port = 8080
    net_port = socket.htons(port)
    print(f"Port {port} -> Network: {net_port}")

    # IP 주소 변환 (C의 htonl과 동일)
    ip = 0xC0A80001  # 192.168.0.1
    net_ip = socket.htonl(ip)

    # inet_aton/inet_ntoa도 자동으로 처리
    packed_ip = socket.inet_aton("192.168.0.1")
    print(f"Packed IP: {packed_ip.hex()}")  # c0a80001

def protocol_example():
    """커스텀 바이너리 프로토콜 예제"""

    # 메시지 구조: [type:2][length:4][data:N]
    msg_type = 1
    data = b"Hello, Network!"
    length = len(data)

    # 헤더 패킹 (Network byte order)
    header = struct.pack('!HI', msg_type, length)
    packet = header + data

    print(f"Packet: {packet.hex()}")

    # 언패킹
    msg_type, length = struct.unpack('!HI', packet[:6])
    data = packet[6:6+length]
    print(f"Type: {msg_type}, Length: {length}, Data: {data}")

if __name__ == "__main__":
    endian_demo()
    socket_example()
    protocol_example()

Rust

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

fn main() {
    endian_demo();
}

fn endian_demo() {
    let value: u32 = 0x12345678;

    // Big-endian (Network byte order)
    let big_bytes = value.to_be_bytes();
    println!("Big-endian: {:02X?}", big_bytes);  // [12, 34, 56, 78]

    // Little-endian
    let little_bytes = value.to_le_bytes();
    println!("Little-endian: {:02X?}", little_bytes);  // [78, 56, 34, 12]

    // 역변환
    let from_big = u32::from_be_bytes(big_bytes);
    let from_little = u32::from_le_bytes(little_bytes);

    println!("From big: 0x{:08X}", from_big);
    println!("From little: 0x{:08X}", from_little);
}

fn send_network_message(stream: &mut TcpStream, msg_type: u16, data: &[u8]) {
    // 메시지 타입 (Network byte order)
    stream.write_all(&msg_type.to_be_bytes()).unwrap();

    // 데이터 길이 (Network byte order)
    let length = data.len() as u32;
    stream.write_all(&length.to_be_bytes()).unwrap();

    // 데이터 본문
    stream.write_all(data).unwrap();
}

fn receive_network_message(stream: &mut TcpStream) -> (u16, Vec<u8>) {
    // 메시지 타입 읽기
    let mut type_buf = [0u8; 2];
    stream.read_exact(&mut type_buf).unwrap();
    let msg_type = u16::from_be_bytes(type_buf);

    // 길이 읽기
    let mut len_buf = [0u8; 4];
    stream.read_exact(&mut len_buf).unwrap();
    let length = u32::from_be_bytes(len_buf) as usize;

    // 데이터 읽기
    let mut data = vec![0u8; length];
    stream.read_exact(&mut data).unwrap();

    (msg_type, data)
}

주의사항과 베스트 프랙티스

✅ DO:
- 네트워크 전송 시 항상 htonl/htons 또는 BigEndian 사용
- 프로토콜 문서에 바이트 순서 명시
- 파일 포맷에도 엔디언 명시 (예: BMP는 Little-endian)

❌ DON'T:
- 바이트 순서 변환 없이 구조체 직접 전송
- 플랫폼 기본 엔디언에 의존
- 문자열을 정수처럼 취급 (문자열은 엔디언 영향 없음)

참고 자료

  • RFC 1700 - Assigned Numbers (Network Byte Order 정의)
  • POSIX Socket Programming Guide