이번 기회에 패킷을 네트워크로 보낼 때 사용하는 htonl에 대해 이야기하려 합니다.

여러분도 알듯이 이는 엔디안 타입을 빅 엔디안에서 리틀 엔디안으로 바꾸는 함수입니다.

htonl 나 htons 함수를 사용하려면 ws2_32.lib 함수가 필요합니다.

헤더 부분에 

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

추가 해주시면 됩니다. 

 

winsock 헤더 파일에 htons(2 bytes)나 htonl(4 bytes) 이 포함돼있는 것으로 알고 있습니다. 

하지만 저는 8바이트 데이터도 보내야 되는 상황이였는데 지원하지 않는군요

그래서 직접 만들어 보았습니다.

 

uint64_t hton64(uint64_t num)
{
	uint8_t arr[8];
	uint8_t temp[8];
	uint64_t after=0;
	arr[0] = (uint8_t)(num & 0x00000000000000ff);
	arr[1] = (uint8_t)((num & 0x000000000000ff00) >> 8);
	arr[2] = (uint8_t)((num & 0x0000000000ff0000) >> 16);
	arr[3] = (uint8_t)((num & 0x00000000ff000000) >> 24);
	arr[4] = (uint8_t)((num & 0x000000ff00000000) >> 32);
	arr[5] = (uint8_t)((num & 0x0000ff0000000000) >> 40);
	arr[6] = (uint8_t)((num & 0x00ff000000000000) >> 48);
	arr[7] = (uint8_t)((num & 0xff00000000000000) >> 56);
	int i,k;
	for (i = 0; i < 8; i++)
	{
		temp[i] = arr[i];
	}
	for (i = 0, k=7; i < 8 && k>0; i++, k--)
	{
		arr[i] = temp[k];
	}
	after = ((uint64_t)arr[0]) | (((uint64_t)arr[1]) << 8) | (((uint64_t)arr[2]) << 16)
		| (((uint64_t)arr[3]) << 24) | (((uint64_t)arr[4]) << 32) | (((uint64_t)arr[5]) << 40)
		| (((uint64_t)arr[6]) << 48) | (((uint64_t)arr[7]) << 56);
	return after;


}

이런 식으로 8바이트 데이터도 엔디안 타입을 변경 할 수 있도록 하였습니다.

1바이트 8개 인수 배열에 값을 담고 배열 순서를 뒤집은 다음 다시 저장하고

그 값을 논리 연산자로 합 처서 8바이트로 다시 만드는 함수입니다.

 

이 함수를 제작한 뒤 이번엔 ntoh64 함수를 만들려는 중 의문이 들었습니다.

빅 엔디안에서 리틀 엔디안으로 가는 함수나 리틀 엔디안에서 빅 엔디안으로 가는 함수는 읽는 순서가 바뀌는 것이기 때문에 구분할 필요 없이 그냥 순서만 바꿔주면 되지 않을까라는 생각이 들었습니다. 

그래서 ntoh64 함수는 아래와 같이 만들어 봤습니다.

uint64_t ntonh64(uint64_t num)
{
	uint8_t arr[8];
	uint8_t temp[8];
	uint64_t after = 0;
	arr[0] = (uint8_t)(num & 0x00000000000000ff);
	arr[1] = (uint8_t)((num & 0x000000000000ff00) >> 8);
	arr[2] = (uint8_t)((num & 0x0000000000ff0000) >> 16);
	arr[3] = (uint8_t)((num & 0x00000000ff000000) >> 24);
	arr[4] = (uint8_t)((num & 0x000000ff00000000) >> 32);
	arr[5] = (uint8_t)((num & 0x0000ff0000000000) >> 40);
	arr[6] = (uint8_t)((num & 0x00ff000000000000) >> 48);
	arr[7] = (uint8_t)((num & 0xff00000000000000) >> 56);
	int i, k;
	for (i = 0; i < 8; i++)
	{
		temp[i] = arr[i];
	}
	for (i = 0, k = 7; i < 8 && k>0; i++, k--)
	{
		arr[i] = temp[k];
	}
	after = ((uint64_t)arr[0]) | (((uint64_t)arr[1]) << 8) | (((uint64_t)arr[2]) << 16)
		| (((uint64_t)arr[3]) << 24) | (((uint64_t)arr[4]) << 32) | (((uint64_t)arr[5]) << 40)
		| (((uint64_t)arr[6]) << 48) | (((uint64_t)arr[7]) << 56);
	return after;


}

맞습니다. 이름만 바꿨고 기능은 같습니다. 리틀 엔디안에서 빅 엔디안으로 가든 빅 엔디안에서 리틀 엔디안으로 가든 읽는 순서만 반대로 해주면 그만이기 때문에 내용은 바뀔 필요 없이 이름만 바꾸면 될 것 같습니다.  그래서 위와 같이 작성하였습니다.

 

그리고 구글링을 하던중

uint64_t hton64(uint64_t n)
{
	int backV = (int)(n & 0x00000000ffffffff);
	int frontV = (int)((n & 0xffffffff00000000) >> 32);
	uint64_t temp = (((uint64_t)htonl(frontV)) << 32) | ((uint64_t)htonl(backV));
	return temp;
}

 이런 느낌의 함수를 봤었는데 처음에는 그냥 쓰다가 문득 의문이 떠올랐습니다.

8바이트짜리 엔디안 변경 함수가 없으니 4바이트로 쪼갠다음 각각 htonl을 적용하는 함수였습니다.

하지만 이렇게 하면 문제가 생기지 않을까 생각합니다.

엔디안 변경 시 12345678의 값을 87654321로 변경해야 합니다.

하지만 위와 같이 사용하게 되면 43218765 이런 식으로 변경될 것이라 생각합니다.

엔디안 타입을 변경하길 원했지만 의미 없는 값이 나오게 되는 것입니다.

 

마지막으로 3바이트 htonl도 만들어 보았습니다.

#define hton24(n1, n2, n3)  ((uint32_t)(((n3 & 0xff) << 16) | ((n2 & 0xff) << 8) | (n1 & 0xff)))
#define ntoh24(n1, n2, n3)  ((uint32_t)(((n3 & 0xff) << 16) | ((n2 & 0xff) << 8) | (n1 & 0xff)))

저 매크로를 이용하여 함수를 만들어서 배열을 입력하지 않고 사용할 수도 있을 것입니다.

 

혹시 제가 쓴 글에 뭔가 추가해야 할 사항이 있다면 알려주세요

후기) htonll이란 메소드가 존재합니다. 이 메소드는 8바이트 변수값 변경을 지원합니다. 

 

 

  

+ Recent posts