바이트(Byte) 정렬 함수

bolee·2022년 4월 5일
1

바이트 정렬(byte ordering): 메모리에 데이터를 저장할 때 바이트 순서를 나타내는 용어로 빅 엔디안과 리틀 엔디안 방식이 있다. 이는 CPU와 운영체제에 따라 다르다.

  • 빅 엔디안(big-endian): 최상위 바이트(MSB, Most Significant Byte)부터 차례로 저장하는 방식
  • 리틀 엔디안(little-endian): 최하위 바이트(LSB, Least Significant Byte)부터 차례로 저장하는 방식

아래 그림은 16진수 0x12345678을 메모리 0x1000번지에 저장할 때 두 바이트 정렬 방식의 차이를 보여준다. 빅 엔디안은 최상위 바이트로부터, 리틀 엔디안은 최하위 바이트부터 차례로 저장함을 할 수 있다.

(3-1 그림)

파일에 데이터를 저장하고 읽어오는 경우나 네트워크를 통해 데이터를 송신하고 수신하는 경우에는 바이트 정렬 방식에 유의해야 한다.

네트워크 통신에서 바이트 정렬 방식을 고려해야 하는 경우

프로토콜 구현과 응용 프로그램 데이터라는 두 가지 관점에서 바이트 정렬 방식을 고려해야하는 경우를 살펴볼 것이다.

IP 주소, 포트 번호와 같이 프로토콜 구현을 위해 필요한 정보

(3-2 (a) 그림)

위 그림은 IP 주소의 바이트 정렬 방식에 따른 문제점을 보인다. 호스트가 보낸 패킷의 IP 헤더에는 IP 주소가 포함되어 있다. 이 패킷을 라우터가 받으면 IP 주소를 참조하여 다음 위치에 있는 라우터에 보낸다. 이때 호스트와 라우터가 IP 주소의 바이트 정렬 방식을 약속하지 않으면 IP 주소 해석이 달라져 라우팅에 문제가 생길 수 있다.

(3-2 (b) 그림)

다음은 포트 번호의 바이트 정렬 방식에 따른 문제점을 보인다. 두 호스트가 포트 번호의 바이트 정렬 방식을 약속하지 않으면 포트 번호 해성이 달라져 데이터가 잘못된 목적지 프로세스에 전달될 수 있다.

이런 문제는 시스템이 사용하는 바이트 정렬 방식(호스트 바이트 정렬(host byre ordering))이 통일 되어 있지 않아서 발생한다. 이를 해결하기 위해 IP 주소와 포트 번호의 바이트 정렬 방식을 빅 엔디안으로 통일해 사용한다. 네트워크 용어로는 빅 엔디안을 네트워크 바이트 정렬(network byte ordering)이라고 한다.

응용 프로그램이 주고 받는 데이터

(3-2 (c) 그림)

그림은 데이터의 바이트 정렬 방식에 따른 문제점을 보인다. 두 호스트가 주고받는 데이터에 대한 바이트 정렬 방식을 약속하지 않으면 데이터 해석 문제가 발생할 수 있다. 서버와 클라이언트를 같이 제작하는 경우라면 바이트 정렬 방식을 둘 중 하나로 통일해서 구현하면 되는데, 대개는 네트워크 바이트 정렬(빅 에디안) 방식을 사용한다. 클라이언트만 제작한다면 기존 서버가 정한 바이트 정렬 방식을 따르면 된다.

바이트 정렬 윈속 함수들

응용 프로그램이 바이트 정렬 방식을 편하게 변환할 수 있도록 다음과 같은 윈속 함수가 제공된다.

u_short	htons(u_short hostshort);	// host-to-network-short
u_long	htonl(u_long hostlong);		// host-to-network-long
u_short	ntohs(u_short netshort);	// network-to-host-short
u_long	ntohl(u_long netlong);		// network-to-host-long
  • htons: 호스트 바이트 정렬로 저장된 16비트 값을 입력으로 받아 네트워크 바이트 정렬로 변환한 값을 리턴
  • htonl: 호스트 바이트 정렬로 저장된 32비트 값을 입력으로 받아 네트워크 바이트 정렬로 변환한 값을 리턴
  • ntohs: 네트워크 바이트 정렬로 저장된 16비트 값을 입력으로 받아 호스트 바이트 정렬로 변환한 값을 리턴
  • ntohl: 네트워크 바이트 정렬로 저장된 32비트 값을 입력으로 받아 호스트 바이트 정렬로 변환한 값을 리턴

(3-3, 3-4 그림)

일반적으로 htons(), htonls() 함수는 응용 프로그램이 소켓 함수에 데이터를 넘겨주기 전에 호출하며, ntohs(), ntohl() 함수는 소켓 함수가 결과로 리턴한 데이터를 으용 프로그램이 출력 등의 목적으로 사용하기 전에 호출된다.

윈속 2.x 에서는 바이트 정렬을 위해 다음과 같은 확장 함수도 지원한다. 첫 번째 인자로 소켓 디스크립터를 사용하고, 변환된 결과를 리턴 값이 아닌 세 번째 인자로 전달하는 차이가 있다.

int WSAHtons(SOCKET s, u_short hostshort, u_short *lpnetshort);
int WSAHtonl(SOCKET s, u_long hostshort, u_long *lpnetlong);
int WSANtohs(SOCKET s, u_short netshort, u_short *lphostshort);
int WSANtohl(SOCKET s, u_long netlong, u_short *lphostlong);

많이 사용되는 TCP/IP에서 사용할 SOCKADDR_IN/SOCKADDR_IN6 구조체의 경우 아래와 같은 바이트 정렬 방식을 따른다.

(3-5 그림)

바이트 정렬 함수 사용

바이트 정렬 함수를 사용하는 간단한 예제를 아래 소개할 것이다.

#pargma comment(lib, "ws2_32")
#include <winsock2.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
	WSADATA wsa;
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    	return 1;
    
    u_short x1 = 0x1234;
    u_long y1 = 0x12345678;
    u_short x2;
    u_long y2;
    
    // 호스트 바이트 → 네트워크 바이트
    printf("[호스트 바이트 -> 네트워크 바이트]\n");
    printf("0x%x -> 0x%x\n", x1, x2 = htons(x1));
    printf("0x%x -> 0x%x\n", y1, y2 = htonl(y1));
    
    // 네트워크 바이트 → 호스트 바이트
    printf("[네트워크 바이트 -> 호스트 바이트]\n");
    printf("0x%x -> 0x%x\n", x2, ntohs(x2));
    printf("0x%x -> 0x%x\n", y2, ntohl(y2));
    
    // 잘못된 사용 예
    printf("[잘못된 사용 예]\n");
    printf("0x%x -> 0x%x\n", x1, htonl(x1));
    
    WSACleanup()
    return 0;
}

실행 결과는 아래와 같다.

참고 자료
김성우 저, "TCP/IP 윈도우 소켓 프로그래밍", 한빛아카데미, 2018

0개의 댓글