본문으로 바로가기
반응형

UUID(Universally Unique Identifier)는 유일한 식별자를 생성하기 위해서 표준화된 방식으로 128비트 크기를 갖고 5개 구룹으로 나뉘어 32자리 16진수 문자열로 표현이 된다.

 

RFC 4122에 의하면 UUID는 버전이 여러 가지가 있는데 이번에 포스팅해 볼 내용은 v4 랜덤 기반 UUID이다.

 

 

1. UUID V4 형식: xxxxxxxx-xxxx-4xxx-10xx-xxxxxxxxxxxx

e.g) 93e41c6e-2091-41b9-b36b-c7ce94edc677

 

포맷은 이런 식으로 구성이 되고 네트워크 전문에 주로 사용이 된다. 웹소켓 전문을 송수신할 때 메시지 아이디가 겹치지 않아 유용하게 사용했던 것으로 기억한다. 구조를 보면 8 - 4 - 4 - 4 - 12로 되어있고 아래 표를 참조하면 될 것 같다.

 

 

 

2. UUID 필드 구성(128 비트 구조)

필드명 비트 크기 바이트 크기 설명
time_low 32비트 4바이트 무작위 값
time_mid 16비트 2바이트 무작위 값
time_hi_and_version 16비트 2바이트 무작위 값 + 버전 정보(4비트)
clock_seq_hi_and_reserved 8비트 1바이트 무작위 값 + 10xx로 시작하는 변형 정보
clock_seq_low 8비트 1바이트 무작위 값
node 48비트 6바이트 MAC 주소 또는 무작위 생성된 값

분석에 앞서 93e41c6e-2091-41b9-b36b-c7ce94edc677를 보면 clock_seq_hi_and_reserved가 헷갈릴 수가 있는데 b3는 16진수로 2진수로 표현을 하게 되면 10110011이 된다. 즉, 10xx형식으로 시작을 하는 것이다.

 

d063c2ef-1c5d-4ce9-95f2-1f55ff0a8f80 다른 난수의 clock_seq_hi_and_reserved를 봐도 10010101로 동일한 형식으로 시작하는 것을 볼 수 있다. 10xx로 표현되기 위해서 clock_seq_hi_and_reserved는 앞자리가 8 or 9 or a or b 중 하나로 채워져야 한다.

 

이제 필드별로 구분을 하기 위해서 uuid를 배열을 선언하게 되면 null 포함 배열의 크기는 37이어야 한다.

 

1) time_low ( uuid[0] ~ uuid[7], xxxxxxxx )는 4바이트 16진수로 rand()를 사용하여 rand() % 16를 하면 랜덤 한 16진수의 무작위 인덱스가 나오고 const char *hex = "0123456789abcdef"로 되어있는 배열에 랜덤 인덱스를 넣으면 4바이트의 무작위 한 16진수의 값이 나오게 된다. 즉, hex[ rand() % 16 ]에서 해당 값이 나오게 되는 것이다.

 

2) time_mid (uuid[9] ~uuid[12], xxxx )는 time_low와 동일하게 계산이 되며 2바이트의 16진수 값을 갖게 된다.

 

3) time_hi_and_version (uuid[14]~uuid[17], 4xxx )는 uuid[14]의 경우 버전을 나타내기 위해서 '4'로 고정이고 나머지는 time_low와 같은 랜덤 값이다.

 

4) clock_seq_hi_and_reserved (uuid[19]~uuid[22] , yxxx)는 아까 규칙에 의하면 10xx로 8, 9, a, b 중 하나로 채워진다.

 

5) clock_seq_low (uuid[24]~uuid[25], xx)는 time_low와 동일한 랜덤 값이다.

 

6) node (uuid[26]~uuid[35], xxxxxxxxxx)는 time_low와 동일한 랜덤 값이다.

 

uuid[8], uuid[13], uuid[18], uuid[23]는 하이픈('-') 구분 문자가 삽입된다.

 

 

 

3. UUID 생성 코드

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <Windows.h>

char* generate_uuid(void);

int main(void)
{
	for (int i = 0; i < 60; i++) {
		printf("uuid: %s\r\n", generate_uuid());
		Sleep(5);
	}

	return 0;
}

char * generate_uuid(void)
{
	static char uuid[37];
	const char* hex = "0123456789abcdef";

	if (uuid == NULL) {
		printf("memory allocate fail\r\n");
		return NULL;
	}
	memset(uuid, 0x00, sizeof(uuid));
	clock_t seed = clock();							// ms 단위 시드 생성
	srand((unsigned int)seed);						// 시드 초기화

	for (size_t i = 0; i < 36; i++) {
		switch (i) {
			case 8:
			case 13:
			case 18:
			case 23:
				uuid[i] = '-';
				break;

			case 14:
				uuid[i] = '4';
				break;

			case 19:
				uuid[i] = hex[(rand() % 4) + 8];	// 8 or 9 or a or b
				break;

			default:
				uuid[i] = hex[rand() % 16];			// 16 진수
				break;
		}
	}
	uuid[36] = '\0';

	return uuid;
}

위에 uuid 필드 구성을 배열로 설명을 했기 때문에 코드 설명은 따로 필요 없을 것 같다. 딜레이를 적절히 주지 않으면 시간 변동이 없기 때문에 겹치게 될 가능성이 존재한다. 위에 코드처럼 5ms 기준으로 uuid를 60개를 생성 시 겹치는 uuid는 하나도 발견이 되지 않았다.

 

V4 기준으로 2^122 정도 되니 uuid가 겹칠 확률은 거의 0에 가깝다고 보면 될 것 같다.

반응형