엔디안 완벽 가이드 | 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 아키텍처별 엔디안
- 바이트 순서 변환 방법
- 실전 문제 해결
목차
- 엔디안 기본 개념
- Little Endian vs Big Endian
- 네트워크 바이트 순서
- CPU 아키텍처별 엔디안
- 바이트 순서 변환
- 프로그래밍 언어별 처리
- 실전 문제 해결
- 파일 포맷과 엔디안
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/x64 | Little | PC, 서버 대부분 |
| AMD | Little | x86 호환 |
| ARM Cortex-A | Bi-Endian | 대부분 Little 모드 |
| ARM Cortex-M | Little | 임베디드 |
| Apple M1/M2 | Little | ARM 기반 |
| SPARC | Big | Oracle 서버 |
| PowerPC | Bi-Endian | 구형 Mac, 게임기 |
| MIPS | Bi-Endian | 라우터, 임베디드 |
| RISC-V | Bi-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. 파일 포맷과 엔디안
주요 파일 포맷의 엔디안
| 파일 포맷 | 엔디안 | 비고 |
|---|---|---|
| BMP | Little | Windows 기본 |
| PNG | Big | 네트워크 표준 |
| JPEG | Big | JFIF 표준 |
| GIF | Little | 역사적 이유 |
| TIFF | Both | 헤더에 명시 |
| WAV | Little | Windows 기반 |
| MP3 | Big | 프레임 헤더 |
| MP4 | Big | QuickTime 기반 |
| ELF | Both | 헤더에 명시 |
| PE (EXE) | Little | Windows 실행 파일 |
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 Endian | Big Endian |
|---|---|---|
| 저장 순서 | 낮은 바이트 먼저 | 높은 바이트 먼저 |
| 예시 (0x1234) | 34 12 | 12 34 |
| 가독성 | 어려움 | 쉬움 |
| 타입 캐스팅 | 간단 | 복잡 |
| 네트워크 | 변환 필요 | 그대로 사용 |
| 주요 CPU | x86, ARM | SPARC, 네트워크 |
| 파일 포맷 | BMP, WAV, PE | PNG, 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
참고 자료
- RFC 1700 - Assigned Numbers (Network Byte Order)
- Endianness - Wikipedia
- Understanding Big and Little Endian Byte Order
- Boost.Endian Documentation
한 줄 요약: Little Endian은 대부분의 CPU가 사용하지만, 네트워크 프로토콜은 Big Endian을 표준으로 사용하므로 네트워크 프로그래밍 시 htonl/ntohl 같은 변환 함수를 반드시 사용해야 합니다.