[2026] 엔디안 완벽 가이드 | 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)은 다중 바이트 데이터를 메모리에 저장하는 바이트 순서를 의미합니다.
걸리버 여행기에서 유래
아래 코드는 code를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
Jonathan Swift의 "걸리버 여행기"에서:
- Little-Endians: 달걀의 작은 쪽(little end)을 깨는 사람들
- Big-Endians: 달걀의 큰 쪽(big end)을 깨는 사람들
컴퓨터 과학에서:
- Little Endian: 낮은(little) 바이트를 낮은 주소에 저장
- Big Endian: 높은(big) 바이트를 낮은 주소에 저장
시각적 비교
아래 코드는 code를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
숫자: 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)를 낮은 주소에 저장합니다. 아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
flowchart LR
subgraph Memory["메모리 (주소 증가 →)"]
A0["0x1000\n78"]
A1["0x1001\n56"]
A2["0x1002\n34"]
A3["0x1003\n12"]
end
Number[0x12345678] --> A0
style A0 fill:#e3f2fd
style A3 fill:#ffebee
장점:
- 타입 캐스팅이 간단 (하위 바이트만 읽으면 됨)
- 작은 값 연산이 빠름 단점:
- 사람이 읽기 어려움
- 디버깅 시 혼란 사용 CPU: Intel x86/x64, AMD, ARM (대부분), RISC-V
Big Endian (빅 엔디안)
높은 바이트(MSB)를 낮은 주소에 저장합니다. 아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
flowchart LR
subgraph Memory["메모리 (주소 증가 →)"]
A0["0x1000\n12"]
A1["0x1001\n34"]
A2["0x1002\n56"]
A3["0x1003\n78"]
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을 사용하나?
다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
flowchart TB
History[역사적 이유]
Reason1["1. 직관적\n사람이 읽는 순서와 동일"]
Reason2["2. 디버깅 용이\n패킷 분석이 쉬움"]
Reason3["3. 부호 비트 우선\n첫 바이트로 양수/음수 판단"]
Reason4["4. 표준화\n한 번 정하면 영구 유지"]
History --> Reason1
History --> Reason2
History --> Reason3
History --> Reason4
Reason1 --> Example1["12 34 56 78\n= 0x12345678"]
Reason2 --> Example2["Wireshark에서\n바로 읽기 가능"]
Reason3 --> Example3["첫 바이트 0x80 이상\n→ 음수"]
TCP/IP 헤더 예시
아래 코드는 code를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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 네트워크 예시
다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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 아키텍처
다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
flowchart TB
subgraph LE[Little Endian]
Intel[Intel x86/x64]
AMD[AMD]
ARM_LE["ARM\n대부분 모드"]
RISCV[RISC-V]
end
subgraph BE[Big Endian]
SPARC[SPARC]
PowerPC_BE["PowerPC\n구형"]
M68K[Motorola 68000]
Network["네트워크\n프로토콜"]
end
subgraph BI[Bi-Endian]
ARM_BI["ARM\nCortex-A"]
PowerPC_BI["PowerPC\n최신"]
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 (양방향 엔디안)
아래 코드는 c를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ARM에서 엔디안 전환 (특권 모드)
// SETEND 명령어로 런타임에 변경 가능
// Little Endian 모드
SETEND LE
// Big Endian 모드
SETEND BE
// 하지만 대부분의 ARM 시스템은 Little Endian으로 고정
5. 바이트 순서 변환
수동 변환
다음은 c를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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;
}
컴파일러 내장 함수
다음은 c를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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를 이용한 고속 변환
아래 코드는 c를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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
다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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++
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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
다음은 go를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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: 네트워크 데이터 깨짐
다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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: 바이너리 파일 읽기
다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 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: 크로스 플랫폼 데이터 교환
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 플랫폼 독립적인 직렬화
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 패킷 파싱
다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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: 바이너리 프로토콜 설계
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 커스텀 프로토콜 설계
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: 멀티바이트 정수 직렬화
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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 파일 (엔디안 표시)
다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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)
다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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)
다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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)
다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
성능 최적화
엔디안 변환 최적화
다음은 c를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 조건부 컴파일로 불필요한 변환 제거
#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는 바이트 스왑 수행
벡터화된 변환
다음은 c를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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로 엔디안 확인
다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
네트워크 패킷 분석
다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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. 플랫폼 독립적 코드 작성
다음은 c를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ 권장: 명시적 엔디안 지정
#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. 네트워크 프로그래밍 체크리스트
다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# ✅ 체크리스트
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. 바이너리 파일 포맷 설계
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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'}, ...]
엔디안과 성능
타입 캐스팅의 차이
다음은 c를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 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)과 엔디안
다음은 c를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 구조체 패킹과 엔디안
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++)
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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 (언어 독립적)
아래 코드는 protobuf를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// Protocol Buffers는 엔디안 문제를 자동 처리
syntax = "proto3";
message NetworkMessage {
uint32 message_id = 1;
uint64 timestamp = 2;
string payload = 3;
}
다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 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}")
정리
엔디안 선택 가이드
아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
flowchart TD
Start[데이터 저장/전송] --> Q1{용도는?}
Q1 -->|네트워크 프로토콜| BigEndian["✅ Big Endian\n네트워크 표준"]
Q1 -->|파일 포맷| Q2{호환성?}
Q1 -->|내부 처리| Native["시스템 네이티브\n변환 불필요"]
Q2 -->|크로스 플랫폼| Choose["Little Endian\n또는 Big Endian\n명시"]
Q2 -->|단일 플랫폼| Native2[시스템 네이티브]
BigEndian --> Always["항상 Big Endian\nRFC 표준"]
Choose --> Document["문서화 필수\n헤더에 표시"]
핵심 원칙
다음은 python를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 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 (중간 엔디안)
아래 코드는 code를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
PDP-11 (역사적 CPU)에서 사용
32비트 값 0x12345678을 저장할 때:
34 12 78 56
16비트 단위로는 Little Endian
32비트 전체로는 섞임
현대 시스템에서는 사용 안 함
부동소수점과 엔디안
다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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과 엔디안
다음은 python를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 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: 커스텀 네트워크 프로토콜
다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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: 이미지 파일 변환
다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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}")
디버깅 체크리스트
엔디안 문제 진단
다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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)
네트워크 디버깅
다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 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 같은 변환 함수를 반드시 사용해야 합니다.