패킷을 주고받다보면 숫자뿐만 아니라 문자를 보내야 될 때도 있다. 그리고 문자를 보내는 대부분의 경우는 가변적인 데이터이다.
문자를 표현하는 방법에는 여러가지가 있는데
char sendData1[] = "가";
char sendData2[] = u8"가";
WCHAR sendData3[] = L"가";
TCHAR sendData4[] = _T("가");
한 번 살펴보자.
실제로 저장된 값을 살펴보면 1번 문자열에는 b0, a1이라는 값이 저장 되었고 2번 문자열은 ea, b0, 80이라는 값, 3, 4번 문자열은 ac00이라는 값이 저장 되어 있다. 이렇게 표현하는 방법에 따라 저장되는 값이 다르고 바이트 수도 다르다.
그렇기 때문에 클라이언트와 통신을 할 때에도 이런 규약들은 숙지하고 있어야 원활한 통신을 할 수 있을 것이다.
이번에는 유니코드와 인코딩에 대한 내용을 한 번 알아볼 것이다.
1. 아스키 코드
아스키코드는 다들 알고 있을 것이다.
1바이트를 사용해서 128개의 문자를 표현하는데 초창기에는 이정도만 해도 문제가 없었을 것이다.
하지만 컴퓨터가 세계로 보급이 되고 문자 표현에 대한 규약이 생기기 전 각 나라, 운영환경, 회사 등에 따라 다른 문자 체계를 사용하다 보니 통신에 문제가 생기게 되고 통일된 규약을 만들게 되는데 그것이 유니코드이다.
2. 유니코드
유니코드는 2바이트 문자로 65535개의 문자를 저장할 수 있다. 하위 128개의 문자는 아스키코드와 같으며 그 이후로는 각 주요 나라들의 문자가 저장되어 있다. 물론 모든 문자가 저장되어 있지는 않고 자주 사용되는 문자들 위주로 구성되어 있다.
이제 유니코드를 사용해서 각 나라들의 문자를 표현할 수 있게 되었다. 근데 만약 특정 나라에서만 서비스 되는 프로그램을 만든다고 생각해보자. 예를 들어 북미에서만 서비스 하는 프로그램의 경우 1바이트의 아스키코드 범위 만큼만 사용하면 되는데 굳이 2바이트를 전부 사용하는 것은 비효율적일 것이다. 그 외에도 대부분의 경우 영문이 가장 많이 사용되는데 그럴 때마다 항상 2바이트만 사용한다고 하면 메모리의 낭비가 심할 것이다. 반대로 2바이트의 범위를 넘어가는 문자의 경우는 사용하고 싶어도 사용할 수 없을 것이다.
그렇다면 어떻게 하면 좋을까? 0000 0000 ~ 0111 1111 범위의 최상위비트가 0인 데이터를 사용할 때는 1바이트만 사용하고 최상위 비트가 1이 되는 순간 2바이트를 사용하는 것으로 약속하고 문자를 표현하면 어떨까? 또는 가장 상위 비트에 몇 바이트를 사용할지 미리 표시한 후 사용하면 어떨까? 이렇게 규칙을 정해서 그에 맞춰 데이터의 형식을 변환하는 것을 인코딩이라고 한다.
3. UTF-8 인코딩
UTF-8 인코딩은 MSB부터 몇 바이트를 사용하는 문자인지 알려준다. 이후 바이트부터는 10을 사용하여 경계를 구분하였다. 첫 바이트에서 얼마만큼의 바이트를 사용할 것인지 알려주었는데 왜 굳이 10을 사용해서 구분을 지었을까?
만약 구분을 하지 않는다면 중간 바이트부터 데이터를 었을 때 예를들어 110라는 값이 나오면 해당 바이트가 2바이트 문자의 첫 바이트인지 아니면 2바이트 이상의 바이트를 사용하는 문자의 중간 바이트인지 알 수 없다. 하지만 10으로 구분을 한다면 데이터를 읽을 때 중간바이트인 것을 알 수 있고 단독으로 사용할 수 없다는 것도 알 수 있다.
이 UTF-8에서는 영문은 1바이트 문자이고 한글은 3바이트 문자이다. 영문을 많이 사용하는 프로그램이라면 이득을 볼 것이고 한글을 더 많이 사용하는 프로그램이라면 조금 손해를 볼 것이다.
4. UTF-16 인코딩
UTF-16에서는 기본적으로 2바이트로 문자들을 표현한다. 영문도 2바이트 한글도 2바이트인 것이다. 2바이트 이후로는 4바이트로 표현하는데 사실 4바이트 문자의 경우 거의 사용하지 않는 문자라고 봐도 무방하다.
결국 모든 문자들을 2바이트로 표현하는 것이기 때문에 영문 뿐만 아니라 한글, 일본어, 한자 등 다양하게 사용하는 환경이라면 UTF-16이 비교적 효율적일 것이다.
장단점이 명확하기 때문에 무엇이 더 좋다라고 확신할 수 없다. 서비스하는 환경에 맞춰 방식을 정하면 되겠다.
5. MBCS(Multi Byte Character Set) vs WBCS(Wide Byte Character Set)
MBCS는 각각의 문자를 여러 바이트로 표현한 것이다. 일반적으로 char를 사용할 때 이 방식이 사용된다. CP949라는 방식으로 로마자는 1바이트 한글은 2바이트를 사용한다. 위 그림을 한 번 참고해보면 "가"라는 문자를 표현하기 위해 b0, a1 이라는 2바이트의 값을 사용해서 표현된다. 만약 영문이었다면 1바이트만 사용했을 것이다.
WBCS는 유니코드를 기반으로 데이터를 표현한다. 리눅스냐 윈도우냐에 따라 다르지만 윈도우에서는 기본적으로 UTF-16을 사용한다. WCHAR를 사용하고 위 사진에서 sendData3이 이를 사용했다. "가"를 표현하기 위해 ac00라는 값을 사용했다.
이렇게 표현방식이 다르기 때문에 데이터 통신을 위해서는 한가지 방법으로 통일하는 것이 중요하다.
정리하자면 이렇다.
char sendData1[] = "가"; // CP949 (한글 2바이트, 로마 1바이트)
char sendData2[] = u8"가"; // UTF-8 (한글 3바이트, 로마 1바이트)
WCHAR sendData3[] = L"가"; // UTF-16 (한글 2바이트, 로마 2바이트)
TCHAR sendData4[] = _T("가"); // 알아서 골라줘
TCHAR는 뭔가 싶지만 별로 어려운 것은 없다. 실행환경에 맞춰서 알아서 WCHAR와 char중에 골라서 사용한다.
속성 고급 탭의 문자집합에서 멀티바이트와 유니코드 중 선택할 수 있다. 기본적으로는 유니코드로 설정되어 있다. 유니코드일 때 WCHAR, 멀티바이트일 때 char로 치환되어 인코딩 된다.
C#의 경우 UTF-16을 기본으로 인코딩이 되기 때문에 WCHAR을 사용하는 것이 C#과 통신할 때 궁합이 좋다. 물론 C#도 UTF-8을 지원하기 때문에 필요에 따라 선택하면 되겠다. 운영체제에 따라서 차이가 날 수도 있으니 잘 확인해 보는 것이 중요하다.
'서버 > 패킷 직렬화' 카테고리의 다른 글
5. 패킷 직렬화 #2 (0) | 2025.07.04 |
---|---|
4. 패킷 직렬화 #1 (0) | 2025.05.29 |
2. Packet Handler (0) | 2025.05.26 |
1. Buffer Helpers (0) | 2025.05.24 |