엔디안 완벽 가이드 | Little Endian·Big Endian·네트워크 바이트 순서

엔디안 완벽 가이드 | Little Endian·Big Endian·네트워크 바이트 순서

이 글의 핵심

Little Endian과 Big Endian의 차이, 네트워크에서 Big Endian을 사용하는 이유, CPU 아키텍처별 엔디안, 바이트 순서 변환 방법까지. 실전 예제로 완벽 이해하는 엔디안 가이드.

들어가며: 엔디안이란?

네트워크 프로그래밍이나 바이너리 파일을 다루다 보면 엔디안(Endianness) 문제를 만나게 됩니다. 0x12345678이라는 숫자를 메모리에 저장할 때, 12 34 56 78 순서로 저장할까요, 아니면 78 56 34 12 순서로 저장할까요?

이 글에서 다룰 내용:

  • Little Endian vs Big Endian
  • 네트워크 바이트 순서 (Network Byte Order)
  • CPU 아키텍처별 엔디안
  • 바이트 순서 변환 방법
  • 실전 문제 해결

목차

  1. 엔디안 기본 개념
  2. Little Endian vs Big Endian
  3. 네트워크 바이트 순서
  4. CPU 아키텍처별 엔디안
  5. 바이트 순서 변환
  6. 프로그래밍 언어별 처리
  7. 실전 문제 해결
  8. 파일 포맷과 엔디안

1. 엔디안 기본 개념

엔디안이란?

엔디안(Endianness)은 다중 바이트 데이터를 메모리에 저장하는 바이트 순서를 의미합니다.

걸리버 여행기에서 유래

Jonathan Swift의 "걸리버 여행기"에서:
- Little-Endians: 달걀의 작은 쪽(little end)을 깨는 사람들
- Big-Endians: 달걀의 큰 쪽(big end)을 깨는 사람들

컴퓨터 과학에서:
- Little Endian: 낮은(little) 바이트를 낮은 주소에 저장
- Big Endian: 높은(big) 바이트를 낮은 주소에 저장

시각적 비교

숫자: 0x12345678 (4바이트)

메모리 주소:  0x1000  0x1001  0x1002  0x1003
            ┌───────┬───────┬───────┬───────┐
Little Endian│  78   │  56   │  34   │  12   │
            └───────┴───────┴───────┴───────┘
             낮은 바이트가 낮은 주소

메모리 주소:  0x1000  0x1001  0x1002  0x1003
            ┌───────┬───────┬───────┬───────┐
Big Endian   │  12   │  34   │  56   │  78   │
            └───────┴───────┴───────┴───────┘
             높은 바이트가 낮은 주소

2. Little Endian vs Big Endian

Little Endian (리틀 엔디안)

낮은 바이트(LSB)를 낮은 주소에 저장합니다.

flowchart LR
    subgraph Memory[메모리 (주소 증가 →)]
        A0[0x1000<br/>78]
        A1[0x1001<br/>56]
        A2[0x1002<br/>34]
        A3[0x1003<br/>12]
    end
    
    Number[0x12345678] --> A0
    
    style A0 fill:#e3f2fd
    style A3 fill:#ffebee

장점:

  • 타입 캐스팅이 간단 (하위 바이트만 읽으면 됨)
  • 작은 값 연산이 빠름

단점:

  • 사람이 읽기 어려움
  • 디버깅 시 혼란

사용 CPU: Intel x86/x64, AMD, ARM (대부분), RISC-V

Big Endian (빅 엔디안)

높은 바이트(MSB)를 낮은 주소에 저장합니다.

flowchart LR
    subgraph Memory[메모리 (주소 증가 →)]
        A0[0x1000<br/>12]
        A1[0x1001<br/>34]
        A2[0x1002<br/>56]
        A3[0x1003<br/>78]
    end
    
    Number[0x12345678] --> A0
    
    style A0 fill:#ffebee
    style A3 fill:#e3f2fd

장점:

  • 사람이 읽기 쉬움 (왼쪽에서 오른쪽)
  • 디버깅이 직관적
  • 부호 비트를 먼저 확인 가능

단점:

  • 타입 캐스팅 시 주소 계산 필요

사용 CPU: SPARC, PowerPC (구형), Motorola 68000, 네트워크 프로토콜

실제 메모리 예시

import struct
import sys

# 숫자 0x12345678
number = 0x12345678

# Little Endian으로 저장
le_bytes = struct.pack('<I', number)  # '<' = Little Endian
print(f"Little Endian: {le_bytes.hex()}")
# 출력: 78563412

# Big Endian으로 저장
be_bytes = struct.pack('>I', number)  # '>' = Big Endian
print(f"Big Endian: {be_bytes.hex()}")
# 출력: 12345678

# 시스템 엔디안 확인
print(f"System: {sys.byteorder}")  # 'little' 또는 'big'

3. 네트워크 바이트 순서

네트워크 바이트 순서란?

네트워크 바이트 순서(Network Byte Order)는 Big Endian을 의미합니다. TCP/IP 프로토콜은 모든 다중 바이트 정수를 Big Endian으로 전송합니다.

왜 Big Endian을 사용하나?

flowchart TB
    History[역사적 이유]
    
    Reason1[1. 직관적<br/>사람이 읽는 순서와 동일]
    Reason2[2. 디버깅 용이<br/>패킷 분석이 쉬움]
    Reason3[3. 부호 비트 우선<br/>첫 바이트로 양수/음수 판단]
    Reason4[4. 표준화<br/>한 번 정하면 영구 유지]
    
    History --> Reason1
    History --> Reason2
    History --> Reason3
    History --> Reason4
    
    Reason1 --> Example1[12 34 56 78<br/>= 0x12345678]
    Reason2 --> Example2[Wireshark에서<br/>바로 읽기 가능]
    Reason3 --> Example3[첫 바이트 0x80 이상<br/>→ 음수]

TCP/IP 헤더 예시

TCP 헤더 (Big Endian):

Source Port (16비트):     0x1F90 (8080)
메모리: 1F 90

Destination Port (16비트): 0x0050 (80)
메모리: 00 50

Sequence Number (32비트): 0x12345678
메모리: 12 34 56 78

네트워크 프로그래밍

#include <arpa/inet.h>
#include <stdio.h>

int main() {
    uint16_t port = 8080;
    uint32_t ip = 0x7F000001;  // 127.0.0.1
    
    printf("Host Byte Order:\n");
    printf("  Port: 0x%04X\n", port);
    printf("  IP:   0x%08X\n", ip);
    
    // Host → Network (Big Endian)
    uint16_t net_port = htons(port);  // Host TO Network Short
    uint32_t net_ip = htonl(ip);      // Host TO Network Long
    
    printf("\nNetwork Byte Order:\n");
    printf("  Port: 0x%04X\n", net_port);
    printf("  IP:   0x%08X\n", net_ip);
    
    // Network → Host
    uint16_t host_port = ntohs(net_port);  // Network TO Host Short
    uint32_t host_ip = ntohl(net_ip);      // Network TO Host Long
    
    printf("\nConverted back:\n");
    printf("  Port: 0x%04X\n", host_port);
    printf("  IP:   0x%08X\n", host_ip);
    
    return 0;
}

// Intel x86 (Little Endian) 시스템에서 출력:
// Host Byte Order:
//   Port: 0x1F90
//   IP:   0x7F000001
//
// Network Byte Order:
//   Port: 0x901F      (바이트 순서 변경됨)
//   IP:   0x0100007F  (바이트 순서 변경됨)
//
// Converted back:
//   Port: 0x1F90
//   IP:   0x7F000001

Python 네트워크 예시

import socket
import struct

# 소켓 생성
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# IP 주소 변환
ip_str = "192.168.1.100"
ip_int = struct.unpack('>I', socket.inet_aton(ip_str))[0]
print(f"IP as integer: 0x{ip_int:08X}")  # 0xC0A80164

# Big Endian으로 저장됨
ip_bytes = socket.inet_aton(ip_str)
print(f"IP as bytes: {ip_bytes.hex()}")  # c0a80164

# 포트 번호 (Big Endian으로 전송)
port = 8080
port_be = struct.pack('>H', port)  # Big Endian
print(f"Port (BE): {port_be.hex()}")  # 1f90

port_le = struct.pack('<H', port)  # Little Endian
print(f"Port (LE): {port_le.hex()}")  # 901f

4. CPU 아키텍처별 엔디안

주요 CPU 아키텍처

flowchart TB
    subgraph LE[Little Endian]
        Intel[Intel x86/x64]
        AMD[AMD]
        ARM_LE[ARM<br/>대부분 모드]
        RISCV[RISC-V]
    end
    
    subgraph BE[Big Endian]
        SPARC[SPARC]
        PowerPC_BE[PowerPC<br/>구형]
        M68K[Motorola 68000]
        Network[네트워크<br/>프로토콜]
    end
    
    subgraph BI[Bi-Endian]
        ARM_BI[ARM<br/>Cortex-A]
        PowerPC_BI[PowerPC<br/>최신]
        MIPS[MIPS]
    end
    
    style LE fill:#e3f2fd
    style BE fill:#fff3e0
    style BI fill:#f3e5f5

CPU별 엔디안 정리

CPU엔디안비고
Intel x86/x64LittlePC, 서버 대부분
AMDLittlex86 호환
ARM Cortex-ABi-Endian대부분 Little 모드
ARM Cortex-MLittle임베디드
Apple M1/M2LittleARM 기반
SPARCBigOracle 서버
PowerPCBi-Endian구형 Mac, 게임기
MIPSBi-Endian라우터, 임베디드
RISC-VBi-Endian대부분 Little

Bi-Endian (양방향 엔디안)

// ARM에서 엔디안 전환 (특권 모드)
// SETEND 명령어로 런타임에 변경 가능

// Little Endian 모드
SETEND LE

// Big Endian 모드
SETEND BE

// 하지만 대부분의 ARM 시스템은 Little Endian으로 고정

5. 바이트 순서 변환

수동 변환

#include <stdint.h>

// 16비트 바이트 스왑
uint16_t swap16(uint16_t value) {
    return (value >> 8) | (value << 8);
}

// 32비트 바이트 스왑
uint32_t swap32(uint32_t value) {
    return ((value >> 24) & 0x000000FF) |
           ((value >>  8) & 0x0000FF00) |
           ((value <<  8) & 0x00FF0000) |
           ((value << 24) & 0xFF000000);
}

// 64비트 바이트 스왑
uint64_t swap64(uint64_t value) {
    return ((value >> 56) & 0x00000000000000FFULL) |
           ((value >> 40) & 0x000000000000FF00ULL) |
           ((value >> 24) & 0x0000000000FF0000ULL) |
           ((value >>  8) & 0x00000000FF000000ULL) |
           ((value <<  8) & 0x000000FF00000000ULL) |
           ((value << 24) & 0x0000FF0000000000ULL) |
           ((value << 40) & 0x00FF000000000000ULL) |
           ((value << 56) & 0xFF00000000000000ULL);
}

// 테스트
int main() {
    uint32_t value = 0x12345678;
    uint32_t swapped = swap32(value);
    
    printf("Original: 0x%08X\n", value);    // 0x12345678
    printf("Swapped:  0x%08X\n", swapped);  // 0x78563412
    
    return 0;
}

컴파일러 내장 함수

#include <byteswap.h>  // Linux
#include <endian.h>

// GCC/Clang 내장 함수 (최적화됨)
uint16_t swapped16 = __builtin_bswap16(value);
uint32_t swapped32 = __builtin_bswap32(value);
uint64_t swapped64 = __builtin_bswap64(value);

// Linux 표준 함수
uint16_t swapped16 = bswap_16(value);
uint32_t swapped32 = bswap_32(value);
uint64_t swapped64 = bswap_64(value);

// Host ↔ Big Endian 변환
uint32_t be_value = htobe32(host_value);  // Host TO Big Endian
uint32_t host_value = be32toh(be_value);  // Big Endian TO Host

// Host ↔ Little Endian 변환
uint32_t le_value = htole32(host_value);
uint32_t host_value = le32toh(le_value);

SIMD를 이용한 고속 변환

#include <immintrin.h>  // x86 SIMD

// SSE2로 16바이트 한 번에 스왑
__m128i swap_bytes_simd(__m128i data) {
    // PSHUFB 명령어로 바이트 순서 변경
    __m128i shuffle = _mm_set_epi8(
        0, 1, 2, 3, 4, 5, 6, 7,
        8, 9, 10, 11, 12, 13, 14, 15
    );
    return _mm_shuffle_epi8(data, shuffle);
}

6. 프로그래밍 언어별 처리

Python

import struct
import sys

# 시스템 엔디안 확인
print(f"System endianness: {sys.byteorder}")  # 'little' 또는 'big'

# 숫자를 바이트로 변환
number = 0x12345678

# Little Endian
le_bytes = number.to_bytes(4, byteorder='little')
print(f"Little Endian: {le_bytes.hex()}")  # 78563412

# Big Endian
be_bytes = number.to_bytes(4, byteorder='big')
print(f"Big Endian: {be_bytes.hex()}")  # 12345678

# 바이트에서 숫자로 변환
le_number = int.from_bytes(le_bytes, byteorder='little')
be_number = int.from_bytes(be_bytes, byteorder='big')

print(f"LE decoded: 0x{le_number:08X}")  # 0x12345678
print(f"BE decoded: 0x{be_number:08X}")  # 0x12345678

# struct 모듈 사용
# '<' = Little Endian, '>' = Big Endian, '=' = Native, '!' = Network (Big)
le_packed = struct.pack('<I', number)
be_packed = struct.pack('>I', number)
net_packed = struct.pack('!I', number)  # Network = Big Endian

print(f"struct '<I': {le_packed.hex()}")
print(f"struct '>I': {be_packed.hex()}")
print(f"struct '!I': {net_packed.hex()}")

C/C++

#include <iostream>
#include <cstdint>
#include <bit>  // C++20

// 엔디안 확인 (C++20)
void check_endianness_cpp20() {
    if constexpr (std::endian::native == std::endian::little) {
        std::cout << "System: Little Endian" << std::endl;
    } else if constexpr (std::endian::native == std::endian::big) {
        std::cout << "System: Big Endian" << std::endl;
    }
}

// 엔디안 확인 (전통적 방법)
bool is_little_endian() {
    uint32_t value = 0x01;
    return *((uint8_t*)&value) == 0x01;
}

// Union을 이용한 확인
union EndianTest {
    uint32_t value;
    uint8_t bytes[4];
};

void check_endianness_union() {
    EndianTest test;
    test.value = 0x12345678;
    
    printf("Bytes: %02X %02X %02X %02X\n",
           test.bytes[0], test.bytes[1], test.bytes[2], test.bytes[3]);
    
    if (test.bytes[0] == 0x78) {
        printf("Little Endian\n");
    } else if (test.bytes[0] == 0x12) {
        printf("Big Endian\n");
    }
}

// 네트워크 바이트 순서 변환
#include <arpa/inet.h>

void network_example() {
    uint16_t port = 8080;
    uint32_t ip = 0xC0A80164;  // 192.168.1.100
    
    // Host → Network
    uint16_t net_port = htons(port);
    uint32_t net_ip = htonl(ip);
    
    // Network → Host
    uint16_t host_port = ntohs(net_port);
    uint32_t host_ip = ntohl(net_ip);
}

JavaScript

// JavaScript는 플랫폼 독립적이지만,
// ArrayBuffer와 DataView로 엔디안 제어 가능

const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);

const number = 0x12345678;

// Little Endian으로 저장
view.setUint32(0, number, true);  // true = Little Endian
console.log('Little Endian:', 
  Array.from(new Uint8Array(buffer))
    .map(b => b.toString(16).padStart(2, '0'))
    .join(' ')
);
// 출력: 78 56 34 12

// Big Endian으로 저장
view.setUint32(0, number, false);  // false = Big Endian
console.log('Big Endian:', 
  Array.from(new Uint8Array(buffer))
    .map(b => b.toString(16).padStart(2, '0'))
    .join(' ')
);
// 출력: 12 34 56 78

// 읽기
const le_value = view.getUint32(0, true);   // Little Endian
const be_value = view.getUint32(0, false);  // Big Endian

Go

package main

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

func main() {
    var number uint32 = 0x12345678
    
    // 시스템 엔디안 확인
    if isLittleEndian() {
        fmt.Println("System: Little Endian")
    } else {
        fmt.Println("System: Big Endian")
    }
    
    // Little Endian 변환
    leBytes := make([]byte, 4)
    binary.LittleEndian.PutUint32(leBytes, number)
    fmt.Printf("Little Endian: %x\n", leBytes)  // 78563412
    
    // Big Endian 변환
    beBytes := make([]byte, 4)
    binary.BigEndian.PutUint32(beBytes, number)
    fmt.Printf("Big Endian: %x\n", beBytes)  // 12345678
    
    // 읽기
    leValue := binary.LittleEndian.Uint32(leBytes)
    beValue := binary.BigEndian.Uint32(beBytes)
    
    fmt.Printf("LE decoded: 0x%08X\n", leValue)
    fmt.Printf("BE decoded: 0x%08X\n", beValue)
}

func isLittleEndian() bool {
    var i uint32 = 0x01020304
    return *(*byte)(unsafe.Pointer(&i)) == 0x04
}

7. 실전 문제 해결

문제 1: 네트워크 데이터 깨짐

import socket
import struct

# ❌ 잘못된 코드: 엔디안 변환 없음
def send_data_wrong(sock):
    length = 1000
    message_id = 0x1234
    
    # 호스트 바이트 순서로 전송 (잘못됨!)
    data = struct.pack('IH', length, message_id)
    sock.send(data)
    
    # Little Endian 시스템에서:
    # length: E8 03 00 00 (1000)
    # message_id: 34 12
    # Big Endian 서버가 받으면 잘못 해석됨!

# ✅ 올바른 코드: 네트워크 바이트 순서 사용
def send_data_correct(sock):
    length = 1000
    message_id = 0x1234
    
    # Big Endian (네트워크 바이트 순서)로 전송
    data = struct.pack('!IH', length, message_id)  # '!' = Network
    sock.send(data)
    
    # 모든 시스템에서 동일:
    # length: 00 00 03 E8
    # message_id: 12 34

# 수신 측
def receive_data(sock):
    data = sock.recv(6)
    
    # 네트워크 바이트 순서로 언팩
    length, message_id = struct.unpack('!IH', data)
    
    print(f"Length: {length}")        # 1000
    print(f"Message ID: 0x{message_id:04X}")  # 0x1234

문제 2: 바이너리 파일 읽기

# BMP 파일 헤더 읽기 (Little Endian)
def read_bmp_header(filename):
    with open(filename, 'rb') as f:
        # BMP 시그니처
        signature = f.read(2)
        if signature != b'BM':
            raise ValueError("Not a BMP file")
        
        # 파일 크기 (Little Endian)
        file_size_bytes = f.read(4)
        file_size = struct.unpack('<I', file_size_bytes)[0]
        
        f.seek(18)  # 이미지 너비 위치
        
        # 너비와 높이 (Little Endian)
        width = struct.unpack('<I', f.read(4))[0]
        height = struct.unpack('<I', f.read(4))[0]
        
        print(f"File size: {file_size} bytes")
        print(f"Dimensions: {width} x {height}")
        
        return {
            'width': width,
            'height': height,
            'file_size': file_size
        }

# PNG 파일 헤더 읽기 (Big Endian)
def read_png_header(filename):
    with open(filename, 'rb') as f:
        # PNG 시그니처
        signature = f.read(8)
        if signature != b'\x89PNG\r\n\x1a\n':
            raise ValueError("Not a PNG file")
        
        # IHDR 청크
        chunk_length = struct.unpack('>I', f.read(4))[0]
        chunk_type = f.read(4)
        
        if chunk_type != b'IHDR':
            raise ValueError("Invalid PNG")
        
        # 너비와 높이 (Big Endian)
        width = struct.unpack('>I', f.read(4))[0]
        height = struct.unpack('>I', f.read(4))[0]
        
        print(f"Dimensions: {width} x {height}")
        
        return {
            'width': width,
            'height': height
        }

문제 3: 크로스 플랫폼 데이터 교환

// 플랫폼 독립적인 직렬화
class Serializer {
public:
    // 항상 Little Endian으로 저장
    static void writeInt32(std::ostream& out, int32_t value) {
        uint8_t bytes[4];
        bytes[0] = (value >> 0) & 0xFF;
        bytes[1] = (value >> 8) & 0xFF;
        bytes[2] = (value >> 16) & 0xFF;
        bytes[3] = (value >> 24) & 0xFF;
        out.write(reinterpret_cast<char*>(bytes), 4);
    }
    
    static int32_t readInt32(std::istream& in) {
        uint8_t bytes[4];
        in.read(reinterpret_cast<char*>(bytes), 4);
        
        return (bytes[0] << 0) |
               (bytes[1] << 8) |
               (bytes[2] << 16) |
               (bytes[3] << 24);
    }
};

// 사용
std::ofstream file("data.bin", std::ios::binary);
Serializer::writeInt32(file, 0x12345678);
file.close();

// 어떤 플랫폼에서 읽어도 동일한 값
std::ifstream input("data.bin", std::ios::binary);
int32_t value = Serializer::readInt32(input);
printf("Value: 0x%08X\n", value);  // 0x12345678

실전 시나리오

시나리오 1: TCP 패킷 파싱

import socket
import struct

def parse_tcp_header(packet):
    """TCP 헤더 파싱 (Big Endian)"""
    # TCP 헤더는 네트워크 바이트 순서 (Big Endian)
    
    # 포트 번호 (16비트)
    src_port = struct.unpack('!H', packet[0:2])[0]
    dst_port = struct.unpack('!H', packet[2:4])[0]
    
    # 시퀀스 번호 (32비트)
    seq_num = struct.unpack('!I', packet[4:8])[0]
    
    # ACK 번호 (32비트)
    ack_num = struct.unpack('!I', packet[8:12])[0]
    
    # 플래그
    flags = packet[13]
    fin = (flags & 0x01) != 0
    syn = (flags & 0x02) != 0
    rst = (flags & 0x04) != 0
    psh = (flags & 0x08) != 0
    ack = (flags & 0x10) != 0
    
    return {
        'src_port': src_port,
        'dst_port': dst_port,
        'seq': seq_num,
        'ack': ack_num,
        'flags': {
            'FIN': fin,
            'SYN': syn,
            'RST': rst,
            'PSH': psh,
            'ACK': ack
        }
    }

# 예시 패킷 (Big Endian)
packet = bytes.fromhex(
    '1F90'  # Source Port: 8080
    '0050'  # Dest Port: 80
    '12345678'  # Sequence
    '87654321'  # ACK
    '5010'  # Data Offset + Flags
    '0000'  # Window
    '0000'  # Checksum
    '0000'  # Urgent
)

header = parse_tcp_header(packet)
print(f"Source Port: {header['src_port']}")      # 8080
print(f"Dest Port: {header['dst_port']}")        # 80
print(f"Sequence: 0x{header['seq']:08X}")        # 0x12345678
print(f"ACK: 0x{header['ack']:08X}")             # 0x87654321

시나리오 2: 바이너리 프로토콜 설계

# 커스텀 프로토콜 설계
class BinaryProtocol:
    """
    프로토콜 포맷 (Big Endian):
    - Magic Number (4 bytes): 0x42494E50 ('BINP')
    - Version (2 bytes)
    - Message Type (2 bytes)
    - Length (4 bytes)
    - Payload (variable)
    """
    
    MAGIC = 0x42494E50
    VERSION = 1
    
    @staticmethod
    def encode(message_type, payload):
        """메시지 인코딩"""
        length = len(payload)
        
        # 헤더 (Big Endian)
        header = struct.pack(
            '!IHH I',
            BinaryProtocol.MAGIC,
            BinaryProtocol.VERSION,
            message_type,
            length
        )
        
        return header + payload
    
    @staticmethod
    def decode(data):
        """메시지 디코딩"""
        if len(data) < 12:
            raise ValueError("Data too short")
        
        # 헤더 파싱 (Big Endian)
        magic, version, msg_type, length = struct.unpack('!IHHI', data[:12])
        
        if magic != BinaryProtocol.MAGIC:
            raise ValueError(f"Invalid magic: 0x{magic:08X}")
        
        if version != BinaryProtocol.VERSION:
            raise ValueError(f"Unsupported version: {version}")
        
        payload = data[12:12+length]
        
        return {
            'type': msg_type,
            'payload': payload
        }

# 사용
payload = b"Hello, World!"
encoded = BinaryProtocol.encode(0x0001, payload)

print(f"Encoded: {encoded.hex()}")
# 42494e50 0001 0001 0000000d 48656c6c6f2c20576f726c6421
# ^^^^^^^^ magic
#         ^^^^ version
#              ^^^^ type
#                   ^^^^^^^^ length (13)
#                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ payload

decoded = BinaryProtocol.decode(encoded)
print(f"Type: 0x{decoded['type']:04X}")
print(f"Payload: {decoded['payload'].decode('utf-8')}")

시나리오 3: 멀티바이트 정수 직렬화

class CrossPlatformSerializer:
    """플랫폼 독립적 직렬화"""
    
    @staticmethod
    def serialize(data):
        """Python 객체 → 바이트 (Little Endian)"""
        if isinstance(data, int):
            # 정수는 8바이트 Little Endian
            return b'I' + struct.pack('<q', data)
        
        elif isinstance(data, float):
            # 부동소수점은 8바이트 Little Endian
            return b'F' + struct.pack('<d', data)
        
        elif isinstance(data, str):
            # 문자열은 UTF-8 + 길이 (Little Endian)
            utf8 = data.encode('utf-8')
            return b'S' + struct.pack('<I', len(utf8)) + utf8
        
        elif isinstance(data, list):
            # 리스트는 재귀적 직렬화
            result = b'L' + struct.pack('<I', len(data))
            for item in data:
                result += CrossPlatformSerializer.serialize(item)
            return result
        
        else:
            raise TypeError(f"Unsupported type: {type(data)}")
    
    @staticmethod
    def deserialize(data):
        """바이트 → Python 객체"""
        type_byte = chr(data[0])
        
        if type_byte == 'I':
            return struct.unpack('<q', data[1:9])[0], 9
        
        elif type_byte == 'F':
            return struct.unpack('<d', data[1:9])[0], 9
        
        elif type_byte == 'S':
            length = struct.unpack('<I', data[1:5])[0]
            utf8 = data[5:5+length]
            return utf8.decode('utf-8'), 5 + length
        
        elif type_byte == 'L':
            count = struct.unpack('<I', data[1:5])[0]
            result = []
            offset = 5
            for _ in range(count):
                item, consumed = CrossPlatformSerializer.deserialize(data[offset:])
                result.append(item)
                offset += consumed
            return result, offset
        
        else:
            raise ValueError(f"Unknown type: {type_byte}")

# 테스트
original = [42, 3.14, "Hello 한글", [1, 2, 3]]
serialized = CrossPlatformSerializer.serialize(original)

print(f"Serialized: {len(serialized)} bytes")
print(f"Hex: {serialized[:50].hex()}...")

deserialized, _ = CrossPlatformSerializer.deserialize(serialized)
print(f"Deserialized: {deserialized}")
assert original == deserialized
print("✅ Serialization test passed")

8. 파일 포맷과 엔디안

주요 파일 포맷의 엔디안

파일 포맷엔디안비고
BMPLittleWindows 기본
PNGBig네트워크 표준
JPEGBigJFIF 표준
GIFLittle역사적 이유
TIFFBoth헤더에 명시
WAVLittleWindows 기반
MP3Big프레임 헤더
MP4BigQuickTime 기반
ELFBoth헤더에 명시
PE (EXE)LittleWindows 실행 파일

TIFF 파일 (엔디안 표시)

def read_tiff_header(filename):
    """TIFF 파일 엔디안 감지"""
    with open(filename, 'rb') as f:
        # 처음 2바이트로 엔디안 확인
        byte_order = f.read(2)
        
        if byte_order == b'II':  # 0x4949
            print("✅ TIFF: Little Endian (Intel)")
            endian = '<'
        elif byte_order == b'MM':  # 0x4D4D
            print("✅ TIFF: Big Endian (Motorola)")
            endian = '>'
        else:
            raise ValueError("Not a TIFF file")
        
        # Magic number (42)
        magic = struct.unpack(f'{endian}H', f.read(2))[0]
        if magic != 42:
            raise ValueError("Invalid TIFF magic number")
        
        # IFD 오프셋
        ifd_offset = struct.unpack(f'{endian}I', f.read(4))[0]
        
        print(f"IFD Offset: {ifd_offset}")
        
        return endian

# 사용
endian = read_tiff_header('image.tiff')

ELF 실행 파일 (Linux)

def read_elf_header(filename):
    """ELF 파일 헤더 파싱"""
    with open(filename, 'rb') as f:
        # ELF Magic
        magic = f.read(4)
        if magic != b'\x7fELF':
            raise ValueError("Not an ELF file")
        
        # Class (32-bit or 64-bit)
        ei_class = f.read(1)[0]
        if ei_class == 1:
            print("32-bit ELF")
        elif ei_class == 2:
            print("64-bit ELF")
        
        # Endianness
        ei_data = f.read(1)[0]
        if ei_data == 1:
            print("✅ Little Endian")
            endian = '<'
        elif ei_data == 2:
            print("✅ Big Endian")
            endian = '>'
        else:
            raise ValueError("Invalid endianness")
        
        return endian

# 사용
endian = read_elf_header('/bin/ls')

네트워크 프로토콜 상세

IP 헤더 (Big Endian)

def parse_ip_header(packet):
    """IPv4 헤더 파싱"""
    # 버전과 헤더 길이
    version_ihl = packet[0]
    version = version_ihl >> 4
    ihl = version_ihl & 0x0F
    
    # 전체 길이 (Big Endian)
    total_length = struct.unpack('!H', packet[2:4])[0]
    
    # 프로토콜
    protocol = packet[9]
    protocol_names = {1: 'ICMP', 6: 'TCP', 17: 'UDP'}
    
    # 출발지 IP (Big Endian)
    src_ip = '.'.join(str(b) for b in packet[12:16])
    
    # 목적지 IP (Big Endian)
    dst_ip = '.'.join(str(b) for b in packet[16:20])
    
    return {
        'version': version,
        'header_length': ihl * 4,
        'total_length': total_length,
        'protocol': protocol_names.get(protocol, f'Unknown({protocol})'),
        'src_ip': src_ip,
        'dst_ip': dst_ip
    }

# 예시 패킷
packet = bytes.fromhex(
    '45 00'      # Version=4, IHL=5, ToS=0
    '00 3C'      # Total Length = 60 (Big Endian)
    '1C 46'      # Identification
    '40 00'      # Flags + Fragment Offset
    '40 06'      # TTL=64, Protocol=6 (TCP)
    '00 00'      # Checksum
    'C0 A8 01 64'  # Source IP: 192.168.1.100
    'C0 A8 01 01'  # Dest IP: 192.168.1.1
)

header = parse_ip_header(packet)
print(f"Version: IPv{header['version']}")
print(f"Length: {header['total_length']} bytes")
print(f"Protocol: {header['protocol']}")
print(f"Source: {header['src_ip']}")
print(f"Destination: {header['dst_ip']}")

DNS 쿼리 (Big Endian)

def create_dns_query(domain):
    """DNS 쿼리 패킷 생성 (Big Endian)"""
    import random
    
    # Transaction ID (Big Endian)
    transaction_id = random.randint(0, 0xFFFF)
    
    # Flags (Big Endian)
    flags = 0x0100  # Standard query, recursion desired
    
    # Questions, Answers, Authority, Additional (모두 Big Endian)
    questions = 1
    answers = 0
    authority = 0
    additional = 0
    
    # 헤더 (12바이트, Big Endian)
    header = struct.pack(
        '!HHHHHH',
        transaction_id,
        flags,
        questions,
        answers,
        authority,
        additional
    )
    
    # 도메인 이름 인코딩
    question = b''
    for part in domain.split('.'):
        question += bytes([len(part)]) + part.encode('ascii')
    question += b'\x00'  # 종료
    
    # Type (A record) and Class (IN) - Big Endian
    question += struct.pack('!HH', 1, 1)
    
    return header + question

# 사용
query = create_dns_query('example.com')
print(f"DNS Query: {query.hex()}")

# Wireshark로 확인하면 모든 필드가 Big Endian

성능 최적화

엔디안 변환 최적화

// 조건부 컴파일로 불필요한 변환 제거
#include <endian.h>

#if __BYTE_ORDER == __LITTLE_ENDIAN
    #define HOST_TO_BE32(x) __builtin_bswap32(x)
    #define BE32_TO_HOST(x) __builtin_bswap32(x)
    #define HOST_TO_LE32(x) (x)
    #define LE32_TO_HOST(x) (x)
#elif __BYTE_ORDER == __BIG_ENDIAN
    #define HOST_TO_BE32(x) (x)
    #define BE32_TO_HOST(x) (x)
    #define HOST_TO_LE32(x) __builtin_bswap32(x)
    #define LE32_TO_HOST(x) __builtin_bswap32(x)
#endif

// Little Endian 시스템에서:
// HOST_TO_LE32는 아무 작업도 안 함 (최적화)
// HOST_TO_BE32는 바이트 스왑 수행

벡터화된 변환

#include <immintrin.h>

// AVX2로 32바이트 한 번에 스왑
__m256i swap_bytes_avx2(__m256i data) {
    // 바이트 순서 셔플 마스크
    __m256i shuffle_mask = _mm256_set_epi8(
        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
    );
    
    return _mm256_shuffle_epi8(data, shuffle_mask);
}

// 대용량 데이터 변환
void convert_array_endian(uint32_t* data, size_t count) {
    size_t i = 0;
    
    // AVX2로 8개씩 처리
    for (; i + 8 <= count; i += 8) {
        __m256i vec = _mm256_loadu_si256((__m256i*)&data[i]);
        
        // 각 32비트 정수의 바이트 스왑
        // (실제로는 더 복잡한 셔플 필요)
        
        _mm256_storeu_si256((__m256i*)&data[i], vec);
    }
    
    // 나머지 스칼라 처리
    for (; i < count; i++) {
        data[i] = __builtin_bswap32(data[i]);
    }
}

디버깅 도구

Hex Dump로 엔디안 확인

def hex_dump(data, bytes_per_line=16):
    """Hex dump with endianness visualization"""
    for i in range(0, len(data), bytes_per_line):
        chunk = data[i:i+bytes_per_line]
        
        # 주소
        addr = f"{i:08X}"
        
        # Hex 표시
        hex_str = ' '.join(f"{b:02X}" for b in chunk)
        
        # ASCII 표시
        ascii_str = ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk)
        
        print(f"{addr}: {hex_str:48s} | {ascii_str}")

# 사용
data = struct.pack('<I', 0x12345678)  # Little Endian
print("Little Endian:")
hex_dump(data)
# 00000000: 78 56 34 12                                      | xV4.

data = struct.pack('>I', 0x12345678)  # Big Endian
print("\nBig Endian:")
hex_dump(data)
# 00000000: 12 34 56 78                                      | .4Vx

네트워크 패킷 분석

from scapy.all import *

# 패킷 캡처 및 분석
def analyze_packet(packet):
    """패킷의 엔디안 확인"""
    if packet.haslayer(TCP):
        tcp = packet[TCP]
        
        # Scapy는 자동으로 네트워크 바이트 순서 처리
        print(f"Source Port: {tcp.sport}")      # 자동 변환됨
        print(f"Dest Port: {tcp.dport}")
        print(f"Seq: 0x{tcp.seq:08X}")
        
        # Raw 바이트 확인
        raw = bytes(tcp)[:4]
        print(f"Raw bytes: {raw.hex()}")
        
        # 수동 파싱 (Big Endian)
        src_port_manual = struct.unpack('!H', raw[0:2])[0]
        dst_port_manual = struct.unpack('!H', raw[2:4])[0]
        
        print(f"Manual parse - Src: {src_port_manual}, Dst: {dst_port_manual}")

# 패킷 캡처
sniff(filter="tcp", prn=analyze_packet, count=1)

실전 팁

1. 플랫폼 독립적 코드 작성

// ✅ 권장: 명시적 엔디안 지정
#include <stdint.h>

// 항상 Little Endian으로 저장
void write_int32_le(FILE* file, int32_t value) {
    uint8_t bytes[4];
    bytes[0] = (value >> 0) & 0xFF;
    bytes[1] = (value >> 8) & 0xFF;
    bytes[2] = (value >> 16) & 0xFF;
    bytes[3] = (value >> 24) & 0xFF;
    fwrite(bytes, 1, 4, file);
}

int32_t read_int32_le(FILE* file) {
    uint8_t bytes[4];
    fread(bytes, 1, 4, file);
    
    return (bytes[0] << 0) |
           (bytes[1] << 8) |
           (bytes[2] << 16) |
           (bytes[3] << 24);
}

// ❌ 피해야 할 코드: 플랫폼 의존적
void write_int32_bad(FILE* file, int32_t value) {
    fwrite(&value, sizeof(value), 1, file);
    // Little Endian 시스템에서 저장한 파일을
    // Big Endian 시스템에서 읽으면 깨짐!
}

2. 네트워크 프로그래밍 체크리스트

# ✅ 체크리스트
import socket
import struct

# 1. 항상 네트워크 바이트 순서 사용
data = struct.pack('!I', value)  # '!' = Network (Big Endian)

# 2. 수신 시 호스트 바이트 순서로 변환
value = struct.unpack('!I', data)[0]

# 3. IP 주소는 inet_aton/inet_ntoa 사용
ip_bytes = socket.inet_aton('192.168.1.1')  # Big Endian
ip_str = socket.inet_ntoa(ip_bytes)

# 4. 포트 번호 변환
# Python socket 라이브러리는 자동 변환하지만,
# 수동 패킷 생성 시 주의
port = 8080
port_be = struct.pack('!H', port)

# ❌ 잘못된 코드
port_bytes = port.to_bytes(2, byteorder='little')  # 네트워크에서 깨짐!

3. 바이너리 파일 포맷 설계

class BinaryFileFormat:
    """플랫폼 독립적 바이너리 포맷"""
    
    MAGIC = b'MYFT'  # Magic number
    VERSION = 1
    
    @staticmethod
    def write(filename, data):
        """파일 쓰기 (Little Endian 사용)"""
        with open(filename, 'wb') as f:
            # 헤더
            f.write(BinaryFileFormat.MAGIC)
            f.write(struct.pack('<H', BinaryFileFormat.VERSION))
            
            # 데이터 개수
            f.write(struct.pack('<I', len(data)))
            
            # 데이터 (모두 Little Endian)
            for item in data:
                f.write(struct.pack('<I', item['id']))
                f.write(struct.pack('<d', item['value']))
                
                name_utf8 = item['name'].encode('utf-8')
                f.write(struct.pack('<H', len(name_utf8)))
                f.write(name_utf8)
    
    @staticmethod
    def read(filename):
        """파일 읽기"""
        with open(filename, 'rb') as f:
            # Magic number 확인
            magic = f.read(4)
            if magic != BinaryFileFormat.MAGIC:
                raise ValueError("Invalid file format")
            
            # 버전
            version = struct.unpack('<H', f.read(2))[0]
            if version != BinaryFileFormat.VERSION:
                raise ValueError(f"Unsupported version: {version}")
            
            # 데이터 개수
            count = struct.unpack('<I', f.read(4))[0]
            
            # 데이터 읽기
            data = []
            for _ in range(count):
                item_id = struct.unpack('<I', f.read(4))[0]
                value = struct.unpack('<d', f.read(8))[0]
                
                name_len = struct.unpack('<H', f.read(2))[0]
                name = f.read(name_len).decode('utf-8')
                
                data.append({
                    'id': item_id,
                    'value': value,
                    'name': name
                })
            
            return data

# 사용
data = [
    {'id': 1, 'value': 3.14, 'name': '항목1'},
    {'id': 2, 'value': 2.71, 'name': '항목2'}
]

BinaryFileFormat.write('data.bin', data)
loaded = BinaryFileFormat.read('data.bin')

print(loaded)
# [{'id': 1, 'value': 3.14, 'name': '항목1'}, ...]

엔디안과 성능

타입 캐스팅의 차이

// Little Endian 시스템에서

uint32_t value32 = 0x12345678;

// Little Endian: 하위 바이트가 먼저
// 메모리: 78 56 34 12

// 16비트로 캐스팅
uint16_t value16 = *(uint16_t*)&value32;
printf("0x%04X\n", value16);  // 0x5678 (하위 2바이트)

// 8비트로 캐스팅
uint8_t value8 = *(uint8_t*)&value32;
printf("0x%02X\n", value8);  // 0x78 (하위 1바이트)

// Big Endian 시스템에서는:
// 메모리: 12 34 56 78
// value16 = 0x1234 (상위 2바이트)
// value8 = 0x12 (상위 1바이트)

정렬(Alignment)과 엔디안

// 구조체 패킹과 엔디안
struct NetworkPacket {
    uint8_t type;       // 1 byte
    uint8_t flags;      // 1 byte
    uint16_t length;    // 2 bytes (Big Endian)
    uint32_t timestamp; // 4 bytes (Big Endian)
} __attribute__((packed));

// 직렬화
void serialize_packet(struct NetworkPacket* pkt, uint8_t* buffer) {
    buffer[0] = pkt->type;
    buffer[1] = pkt->flags;
    
    // Big Endian으로 변환
    uint16_t net_length = htons(pkt->length);
    memcpy(&buffer[2], &net_length, 2);
    
    uint32_t net_timestamp = htonl(pkt->timestamp);
    memcpy(&buffer[4], &net_timestamp, 4);
}

// 역직렬화
void deserialize_packet(const uint8_t* buffer, struct NetworkPacket* pkt) {
    pkt->type = buffer[0];
    pkt->flags = buffer[1];
    
    // Big Endian에서 변환
    uint16_t net_length;
    memcpy(&net_length, &buffer[2], 2);
    pkt->length = ntohs(net_length);
    
    uint32_t net_timestamp;
    memcpy(&net_timestamp, &buffer[4], 4);
    pkt->timestamp = ntohl(net_timestamp);
}

엔디안 변환 라이브러리

Boost.Endian (C++)

#include <boost/endian/conversion.hpp>
#include <iostream>

int main() {
    uint32_t value = 0x12345678;
    
    // 조건부 변환 (Little Endian 시스템에서만 변환)
    uint32_t be_value = boost::endian::native_to_big(value);
    uint32_t le_value = boost::endian::native_to_little(value);
    
    std::cout << std::hex;
    std::cout << "Original: 0x" << value << std::endl;
    std::cout << "Big Endian: 0x" << be_value << std::endl;
    std::cout << "Little Endian: 0x" << le_value << std::endl;
    
    // In-place 변환
    boost::endian::big_to_native_inplace(be_value);
    std::cout << "Converted back: 0x" << be_value << std::endl;
    
    return 0;
}

Protocol Buffers (언어 독립적)

// Protocol Buffers는 엔디안 문제를 자동 처리
syntax = "proto3";

message NetworkMessage {
  uint32 message_id = 1;
  uint64 timestamp = 2;
  string payload = 3;
}
# Protocol Buffers 사용 (엔디안 걱정 없음)
import network_pb2

# 메시지 생성
msg = network_pb2.NetworkMessage()
msg.message_id = 0x12345678
msg.timestamp = 1234567890
msg.payload = "Hello"

# 직렬화 (플랫폼 독립적)
serialized = msg.SerializeToString()

# 역직렬화 (어떤 플랫폼에서도 동일)
msg2 = network_pb2.NetworkMessage()
msg2.ParseFromString(serialized)

print(f"Message ID: 0x{msg2.message_id:08X}")

정리

엔디안 선택 가이드

flowchart TD
    Start[데이터 저장/전송] --> Q1{용도는?}
    
    Q1 -->|네트워크 프로토콜| BigEndian[✅ Big Endian<br/>네트워크 표준]
    Q1 -->|파일 포맷| Q2{호환성?}
    Q1 -->|내부 처리| Native[시스템 네이티브<br/>변환 불필요]
    
    Q2 -->|크로스 플랫폼| Choose[Little Endian<br/>또는 Big Endian<br/>명시]
    Q2 -->|단일 플랫폼| Native2[시스템 네이티브]
    
    BigEndian --> Always[항상 Big Endian<br/>RFC 표준]
    Choose --> Document[문서화 필수<br/>헤더에 표시]

핵심 원칙

# 1. 네트워크는 항상 Big Endian
data = struct.pack('!I', value)  # Network Byte Order

# 2. 파일 포맷은 명시적으로 지정
data = struct.pack('<I', value)  # Little Endian
data = struct.pack('>I', value)  # Big Endian

# 3. 크로스 플랫폼은 하나로 통일
# 보통 Little Endian 선택 (대부분의 CPU)

# 4. 변환 함수 사용
# C: htonl, ntohl, htons, ntohs
# Python: struct.pack('!...'), struct.unpack('!...')

# 5. 문서화
# 파일 포맷 명세에 엔디안 명시

엔디안 비교표

측면Little EndianBig Endian
저장 순서낮은 바이트 먼저높은 바이트 먼저
예시 (0x1234)34 1212 34
가독성어려움쉬움
타입 캐스팅간단복잡
네트워크변환 필요그대로 사용
주요 CPUx86, ARMSPARC, 네트워크
파일 포맷BMP, WAV, PEPNG, JPEG, MP4

고급 주제

Mixed Endian (중간 엔디안)

PDP-11 (역사적 CPU)에서 사용

32비트 값 0x12345678을 저장할 때:
34 12 78 56

16비트 단위로는 Little Endian
32비트 전체로는 섞임

현대 시스템에서는 사용 안 함

부동소수점과 엔디안

import struct

# IEEE 754 부동소수점도 엔디안 영향 받음
value = 3.14159

# Little Endian
le_float = struct.pack('<f', value)
print(f"Float (LE): {le_float.hex()}")
# d0 0f 49 40

# Big Endian
be_float = struct.pack('>f', value)
print(f"Float (BE): {be_float.hex()}")
# 40 49 0f d0

# 더블 (8바이트)
le_double = struct.pack('<d', value)
be_double = struct.pack('>d', value)

print(f"Double (LE): {le_double.hex()}")
print(f"Double (BE): {be_double.hex()}")

UTF-16과 엔디안

# UTF-16은 엔디안 영향 받음
text = "Hello"

# UTF-16 LE (Little Endian)
utf16_le = text.encode('utf-16-le')
print(f"UTF-16 LE: {utf16_le.hex()}")
# 48 00 65 00 6c 00 6c 00 6f 00

# UTF-16 BE (Big Endian)
utf16_be = text.encode('utf-16-be')
print(f"UTF-16 BE: {utf16_be.hex()}")
# 00 48 00 65 00 6c 00 6c 00 6f

# BOM으로 엔디안 표시
utf16_with_bom = text.encode('utf-16')
print(f"UTF-16 with BOM: {utf16_with_bom.hex()}")
# ff fe 48 00 65 00 6c 00 6c 00 6f 00
# ^^^^^ BOM (Little Endian)

실전 예제

예제 1: 커스텀 네트워크 프로토콜

class CustomProtocol:
    """커스텀 프로토콜 (Big Endian)"""
    
    @staticmethod
    def create_packet(msg_type, payload):
        """패킷 생성"""
        # 헤더 (Big Endian)
        # - Magic: 0xCAFEBABE (4 bytes)
        # - Version: 1 (2 bytes)
        # - Type: msg_type (2 bytes)
        # - Length: len(payload) (4 bytes)
        
        header = struct.pack(
            '!IHH I',
            0xCAFEBABE,
            1,
            msg_type,
            len(payload)
        )
        
        return header + payload
    
    @staticmethod
    def parse_packet(data):
        """패킷 파싱"""
        if len(data) < 12:
            raise ValueError("Packet too short")
        
        # 헤더 파싱 (Big Endian)
        magic, version, msg_type, length = struct.unpack('!IHHI', data[:12])
        
        if magic != 0xCAFEBABE:
            raise ValueError(f"Invalid magic: 0x{magic:08X}")
        
        payload = data[12:12+length]
        
        return {
            'version': version,
            'type': msg_type,
            'length': length,
            'payload': payload
        }

# 클라이언트
def send_message(sock, msg_type, payload):
    packet = CustomProtocol.create_packet(msg_type, payload)
    sock.send(packet)
    print(f"✅ Sent {len(packet)} bytes")

# 서버
def receive_message(sock):
    # 헤더 먼저 받기
    header = sock.recv(12)
    if len(header) < 12:
        return None
    
    # 길이 추출
    length = struct.unpack('!I', header[8:12])[0]
    
    # 페이로드 받기
    payload = sock.recv(length)
    
    # 전체 패킷 파싱
    packet = CustomProtocol.parse_packet(header + payload)
    return packet

# 테스트
import socket

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 9000))
server.listen(1)

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('localhost', 9000))

conn, addr = server.accept()

# 메시지 전송
send_message(client, 0x0001, b"Hello, Server!")

# 메시지 수신
msg = receive_message(conn)
print(f"Received: Type=0x{msg['type']:04X}, Payload={msg['payload']}")

예제 2: 이미지 파일 변환

def convert_bmp_to_big_endian(input_file, output_file):
    """BMP (Little Endian) → Big Endian 변환"""
    with open(input_file, 'rb') as f:
        data = bytearray(f.read())
    
    # BMP 헤더의 정수 필드들을 Big Endian으로 변환
    # (실제로는 의미 없는 예시, 교육용)
    
    # 파일 크기 (offset 2, 4 bytes)
    file_size = struct.unpack('<I', data[2:6])[0]
    struct.pack_into('>I', data, 2, file_size)
    
    # 이미지 오프셋 (offset 10, 4 bytes)
    offset = struct.unpack('<I', data[10:14])[0]
    struct.pack_into('>I', data, 10, offset)
    
    # 너비 (offset 18, 4 bytes)
    width = struct.unpack('<I', data[18:22])[0]
    struct.pack_into('>I', data, 18, width)
    
    # 높이 (offset 22, 4 bytes)
    height = struct.unpack('<I', data[22:26])[0]
    struct.pack_into('>I', data, 22, height)
    
    with open(output_file, 'wb') as f:
        f.write(data)
    
    print(f"✅ Converted: {input_file}{output_file}")

디버깅 체크리스트

엔디안 문제 진단

def diagnose_endian_issue(data, expected_value):
    """엔디안 문제 진단"""
    print(f"Expected: 0x{expected_value:08X}")
    print(f"Raw bytes: {data.hex()}")
    
    # Little Endian으로 해석
    le_value = struct.unpack('<I', data)[0]
    print(f"As Little Endian: 0x{le_value:08X}")
    
    # Big Endian으로 해석
    be_value = struct.unpack('>I', data)[0]
    print(f"As Big Endian: 0x{be_value:08X}")
    
    # 진단
    if le_value == expected_value:
        print("✅ 데이터는 Little Endian입니다")
    elif be_value == expected_value:
        print("✅ 데이터는 Big Endian입니다")
    else:
        print("❌ 엔디안 문제가 아닙니다 (데이터 손상?)")

# 사용
data = bytes.fromhex('78 56 34 12')
diagnose_endian_issue(data, 0x12345678)

네트워크 디버깅

# Wireshark로 패킷 캡처
# 모든 필드가 Big Endian으로 표시됨

# tcpdump로 hex dump
sudo tcpdump -i any -X port 80 -c 1

# 출력 예시:
# 0x0000:  4500 003c 1c46 4000 4006 0000 c0a8 0164
#          ^^^^ IP 헤더 시작 (Big Endian)
#          45 = Version 4, IHL 5
#          00 = ToS
#          003c = Total Length (60 bytes, Big Endian)

# Python으로 검증
total_length = int('003c', 16)
print(f"Total Length: {total_length} bytes")  # 60

참고 자료

한 줄 요약: Little Endian은 대부분의 CPU가 사용하지만, 네트워크 프로토콜은 Big Endian을 표준으로 사용하므로 네트워크 프로그래밍 시 htonl/ntohl 같은 변환 함수를 반드시 사용해야 합니다.

---
... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3