Network Byte Order가 적용되는 실제 코드
C, Go, Python, Rust에서 Network Byte Order를 다루는 실제 코드 예제입니다.
개념 복습
Network Byte Order = Big-endian (RFC 1700)
Host Byte Order = 시스템마다 다름
네트워크 통신 시:
Host → Network: htons(), htonl()
Network → Host: ntohs(), ntohl()
C 언어
#include <stdio.h>
#include <stdint.h>
#include <arpa/inet.h> // Unix/Linux
// Windows: #include <winsock2.h>
int main() {
// 포트 번호 변환 (16비트)
uint16_t host_port = 8080;
uint16_t net_port = htons(host_port);
printf("Host order: 0x%04X\n", host_port); // 0x1F90
printf("Network order: 0x%04X\n", net_port); // Big-endian
// IP 주소 변환 (32비트)
uint32_t host_ip = 0xC0A80001; // 192.168.0.1
uint32_t net_ip = htonl(host_ip);
// 역변환
uint16_t back_to_host = ntohs(net_port);
printf("Back to host: %d\n", back_to_host); // 8080
return 0;
}
실제 소켓 프로그래밍 예제:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int create_server(int port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port); // 포트: 반드시 변환!
addr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0 - 이미 네트워크 순서
bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
listen(sockfd, 5);
return sockfd;
}
// 커스텀 프로토콜 메시지 전송
struct message {
uint32_t type;
uint32_t length;
char data[256];
};
void send_message(int sockfd, struct message *msg) {
// 네트워크 바이트 순서로 변환
uint32_t net_type = htonl(msg->type);
uint32_t net_length = htonl(msg->length);
send(sockfd, &net_type, sizeof(net_type), 0);
send(sockfd, &net_length, sizeof(net_length), 0);
send(sockfd, msg->data, msg->length, 0);
}
Go 언어
package main
import (
"encoding/binary"
"fmt"
"net"
)
func main() {
// Go의 encoding/binary 패키지 사용
// Big-endian (Network byte order)
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, 0x12345678)
fmt.Printf("Big-endian: % X\n", buf) // 12 34 56 78
// Little-endian
binary.LittleEndian.PutUint32(buf, 0x12345678)
fmt.Printf("Little-endian: % X\n", buf) // 78 56 34 12
// 네트워크 프로토콜 구현 예
writeNetworkPacket()
}
// TCP 패킷 헤더 작성 예제
func writeNetworkPacket() {
packet := make([]byte, 8)
// 메시지 타입 (2바이트) - Network byte order
binary.BigEndian.PutUint16(packet[0:2], 0x0001)
// 페이로드 길이 (4바이트) - Network byte order
binary.BigEndian.PutUint32(packet[2:6], 1024)
// 플래그 (2바이트) - Network byte order
binary.BigEndian.PutUint16(packet[6:8], 0x8000)
fmt.Printf("Packet: % X\n", packet)
}
// 실제 TCP 서버 예제
func tcpServer() {
listener, _ := net.Listen("tcp", ":8080")
conn, _ := listener.Accept()
// 4바이트 길이 헤더 읽기
header := make([]byte, 4)
conn.Read(header)
// Network byte order에서 변환
length := binary.BigEndian.Uint32(header)
// 본문 읽기
body := make([]byte, length)
conn.Read(body)
}
Python
import struct
import socket
# struct 모듈의 포맷 문자
# '>' : Big-endian (Network byte order)
# '<' : Little-endian
# '!' : Network byte order (= Big-endian)
# '@' : Native byte order
def endian_demo():
value = 0x12345678
# Big-endian 패킹
big = struct.pack('>I', value)
print(f"Big-endian: {big.hex()}") # 12345678
# Little-endian 패킹
little = struct.pack('<I', value)
print(f"Little-endian: {little.hex()}") # 78563412
# Network byte order (권장)
network = struct.pack('!I', value)
print(f"Network order: {network.hex()}") # 12345678
def socket_example():
"""실제 소켓 프로그래밍 예제"""
# 포트 변환 (C의 htons와 동일)
port = 8080
net_port = socket.htons(port)
print(f"Port {port} -> Network: {net_port}")
# IP 주소 변환 (C의 htonl과 동일)
ip = 0xC0A80001 # 192.168.0.1
net_ip = socket.htonl(ip)
# inet_aton/inet_ntoa도 자동으로 처리
packed_ip = socket.inet_aton("192.168.0.1")
print(f"Packed IP: {packed_ip.hex()}") # c0a80001
def protocol_example():
"""커스텀 바이너리 프로토콜 예제"""
# 메시지 구조: [type:2][length:4][data:N]
msg_type = 1
data = b"Hello, Network!"
length = len(data)
# 헤더 패킹 (Network byte order)
header = struct.pack('!HI', msg_type, length)
packet = header + data
print(f"Packet: {packet.hex()}")
# 언패킹
msg_type, length = struct.unpack('!HI', packet[:6])
data = packet[6:6+length]
print(f"Type: {msg_type}, Length: {length}, Data: {data}")
if __name__ == "__main__":
endian_demo()
socket_example()
protocol_example()
Rust
use std::io::{Read, Write};
use std::net::TcpStream;
fn main() {
endian_demo();
}
fn endian_demo() {
let value: u32 = 0x12345678;
// Big-endian (Network byte order)
let big_bytes = value.to_be_bytes();
println!("Big-endian: {:02X?}", big_bytes); // [12, 34, 56, 78]
// Little-endian
let little_bytes = value.to_le_bytes();
println!("Little-endian: {:02X?}", little_bytes); // [78, 56, 34, 12]
// 역변환
let from_big = u32::from_be_bytes(big_bytes);
let from_little = u32::from_le_bytes(little_bytes);
println!("From big: 0x{:08X}", from_big);
println!("From little: 0x{:08X}", from_little);
}
fn send_network_message(stream: &mut TcpStream, msg_type: u16, data: &[u8]) {
// 메시지 타입 (Network byte order)
stream.write_all(&msg_type.to_be_bytes()).unwrap();
// 데이터 길이 (Network byte order)
let length = data.len() as u32;
stream.write_all(&length.to_be_bytes()).unwrap();
// 데이터 본문
stream.write_all(data).unwrap();
}
fn receive_network_message(stream: &mut TcpStream) -> (u16, Vec<u8>) {
// 메시지 타입 읽기
let mut type_buf = [0u8; 2];
stream.read_exact(&mut type_buf).unwrap();
let msg_type = u16::from_be_bytes(type_buf);
// 길이 읽기
let mut len_buf = [0u8; 4];
stream.read_exact(&mut len_buf).unwrap();
let length = u32::from_be_bytes(len_buf) as usize;
// 데이터 읽기
let mut data = vec![0u8; length];
stream.read_exact(&mut data).unwrap();
(msg_type, data)
}
주의사항과 베스트 프랙티스
✅ DO:
- 네트워크 전송 시 항상 htonl/htons 또는 BigEndian 사용
- 프로토콜 문서에 바이트 순서 명시
- 파일 포맷에도 엔디언 명시 (예: BMP는 Little-endian)
❌ DON'T:
- 바이트 순서 변환 없이 구조체 직접 전송
- 플랫폼 기본 엔디언에 의존
- 문자열을 정수처럼 취급 (문자열은 엔디언 영향 없음)
참고 자료
- RFC 1700 - Assigned Numbers (Network Byte Order 정의)
- POSIX Socket Programming Guide