KCP 기반 실시간 게임 통신 사례 연구
실시간 온라인 게임에서는 플레이어의 입력이 서버에 전달되고 그 결과가 돌아오기까지의 지연 시간이 중요하다. TCP는 신뢰성과 전송 순서를 보장하지만, 유실된 데이터를 기다리는 동안 뒤에 도착한 데이터의 전달도 늦어질 수 있다. UDP는 전송 순서와 도착을 보장하지 않으므로, 신뢰성이 필요한 메시지를 다루려면 이를 보완하는 장치가 필요하다.
KCP는 주로 UDP 위에서 재전송과 순서 제어를 제공하는 프로토콜이다. 이 글에서는 KCP가 지연 시간을 줄이는 원리를 살펴보고, 게임 통신 사례를 바탕으로 연결·인증 과정과 메시지 구조를 정리한다. 인증 절차와 애플리케이션 메시지 형식은 구현에 따라 달라질 수 있다.
KCP는 어떻게 지연 시간을 줄이는가?
KCP는 수신 확인과 재전송을 사용하는 ARQ 방식으로 신뢰성 있는 전송을 구현한다. 재전송 정책과 갱신 주기를 조정해 유실 후 대기 시간을 줄일 수 있으며, 설정에 따라 대역폭과 CPU 사용량이 늘어날 수 있다.
실제 성능은 네트워크 상태와 설정에 따라 달라진다.
- 선택적 재전송: 수신 확인 정보를 바탕으로 유실된 데이터를 골라 재전송한다. TCP도 SACK를 사용하면 선택적 재전송이 가능하므로, 이를 KCP만의 기능으로 볼 수는 없다.
- 재전송 대기 시간 조정: 빠른 모드에서는 유실이 반복될 때 재전송 대기 시간이 늘어나는 폭을 줄여 복구를 앞당긴다.
- 빠른 재전송: 송신 측은 뒤쪽 패킷의 ACK가 도착하는 것을 보고 앞선 패킷의 유실을 추정한다. 설정된 조건을 만족하면 타임아웃 전에 재전송한다.
- 짧은 갱신 주기: 갱신·전송 주기를 줄여 ACK와 재전송 데이터가 나갈 때까지의 대기를 줄일 수 있다. 실제 전송 시점은 애플리케이션의 호출 주기에도 영향을 받는다.
KCP도 순서대로 데이터를 전달하므로, 앞선 데이터가 유실되면 뒤쪽 데이터가 대기하는 현상은 남는다. 핵심은 이 대기를 없애는 것이 아니라 유실된 데이터를 더 빨리 복구하는 데 있다.
연결과 인증 과정
게임 서버와 통신하기 전에는 계정 인증과 접속할 서버의 선택이 필요하다. 다음은 웹 인증으로 받은 토큰을 게임 서버에서 확인하는 흐름을 단순화한 예시다.
이 예시에서 웹 서버는 계정을 인증하고, 게임 서버는 토큰을 검증해 게임 세션을 시작한다. 두 단계의 역할을 구분하면 로그인 이후의 통신 흐름을 이해하기 쉽다.
KCP 자체에는 암호화나 사용자 인증 기능이 없다. 보안이 필요한 경우 별도의 보안 계층을 구성해야 한다. Protobuf 역시 데이터를 직렬화하는 형식이므로, Protobuf로 키나 토큰을 표현하는 것만으로 안전한 전달이 보장되지는 않는다.
KCP 헤더와 애플리케이션 메시지
KCP 세그먼트는 24바이트 헤더와 데이터 영역으로 구성된다. 아래 표의 conv부터 len까지는 KCP 헤더이며, 데이터 영역 안의 PackLen, MsgType, CmdId 등은 이 사례에서 다루는 애플리케이션 메시지 형식이다. 이 내부 형식은 KCP의 공통 규격이 아니다.
표는 한 칸을 1바이트로 표시한다. 애플리케이션 메시지가 크면 여러 KCP 세그먼트로 나뉠 수 있으므로, 내부 표는 재조립된 메시지의 구조를 보여주는 예시로 읽으면 된다.
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | ||||||||||||||||||||||||||||||||
| conv | cmd | frg | wnd | ||||||||||||||||||||||||||||||||||||
| ts | sn | ||||||||||||||||||||||||||||||||||||||
| una | len | ||||||||||||||||||||||||||||||||||||||
Data: 애플리케이션 메시지 예시
|
|||||||||||||||||||||||||||||||||||||||
KCP 헤더에서 conv는 대화 식별자, cmd는 명령 종류, frg는 분할 정보, wnd는 수신 윈도 크기다. ts는 타임스탬프, sn은 세그먼트 순서 번호, una는 다음에 받을 순서 번호, len은 데이터 영역의 길이를 나타낸다.
애플리케이션 메시지의 필드는 다음과 같이 해석할 수 있다. 길이와 체크섬의 계산 범위는 각 구현의 규칙을 확인해야 한다.
PackLen: 메시지 길이를 나타내는 필드.MsgType: 메시지 유형과 플래그 정보.seqNo: 애플리케이션 메시지의 순서 번호.rpcId: 원격 프로시저 호출의 요청과 응답을 연결하는 식별자.CmdId: 페이로드를 해석할 메시지 종류를 나타내는 식별자.crc32: 오류 검출용 체크섬. 암호학적 인증을 대신하지는 않는다.Payload: 직렬화된 메시지 데이터. 암호화를 적용한 경우 복호화한 뒤 해석한다.
Protobuf로 페이로드 해석하기
이 사례의 페이로드는 Protocol Buffers(Protobuf)로 직렬화한 데이터를 담는다. Protobuf는 구조화된 데이터를 바이너리로 표현하는 형식으로, 필드 이름 대신 번호를 사용한다. 게임 메시지를 간결하게 표현할 수 있지만, 크기와 처리 성능은 데이터 구조와 구현에 따라 달라진다.
먼저 .proto 파일에 메시지 구조를 정의하고, protoc로 사용할 언어에 맞는 코드를 생성한다. 송신 측은 이 코드로 데이터를 직렬화하고, 수신 측은 같은 메시지 정의를 사용해 역직렬화한다.
예를 들어 다음과 같이 문자열 두 개를 담는 메시지를 정의할 수 있다.
애플리케이션에서 CmdId와 메시지 종류의 대응 관계를 다음처럼 정했다고 가정하자. 이 번호는 Protobuf가 자동으로 부여하는 값이 아니라 애플리케이션이 정한 규칙이다.
message가 Hello World!, name이 AZA.GG인 메시지를 직렬화하면 다음 바이트열이 된다. 아래 페이로드는 암호화 전의 데이터다.
각 문자열 앞에는 필드 번호와 데이터 유형을 담은 태그, 문자열의 바이트 길이가 붙는다. 0a와 12는 각각 1번과 2번 필드의 태그이며, 0c와 06은 문자열의 길이인 12와 6을 나타낸다.
수신 측은 CmdId가 11인 것을 확인하고 페이로드를 HelloWorld로 해석한다. 이처럼 KCP는 전송과 재전송을, 애플리케이션 헤더는 메시지 구분을, Protobuf는 데이터 표현을 담당한다.