Quick Reference
프로토콜 버전은 하나의 숫자로 끝나지 않습니다. 먼저 연결 가능한 wire protocol인지 협상하고, 각 message schema가 해석 가능한지 지키며, 같은 매치에서 허용할 game build와 규칙 revision을 따로 확인합니다. byte를 읽을 수 있어도 그 client가 현재 게임 규칙을 실행할 수 있다는 뜻은 아닙니다.
| 구분 | 확인하는 질문 | 맞지 않을 때 |
|---|---|---|
| wire protocol | frame과 message type을 해석할 수 있는가 | 연결 거절 또는 업데이트 안내 |
| schema | field를 안전하게 무시·기본값 처리할 수 있는가 | 해당 message 차단 또는 변환 |
| game build | map, skill data, asset 규칙이 같은가 | 같은 match 배정 금지 |
| rule revision | server 판정 규칙과 client 표시가 맞는가 | 다시 동기화하거나 업데이트 요구 |
연결 첫 request에는 client build, 지원 가능한 protocol 범위, platform을 보냅니다. server는 선택한 protocol과 최소 지원 build를 응답합니다. 이후 매 message에 version을 반복할지는 transport 경계와 rollout 방식에 따라 정하되, decoder가 어떤 schema를 써야 하는지는 connection state에 고정합니다.
호환되는 변경과 깨지는 변경
추가적인 optional field, 새 message type, 읽지 않는 field는 대체로 점진 배포에 유리합니다. 반대로 기존 field의 의미를 바꾸거나, type을 바꾸거나, 삭제한 field 번호를 다른 의미로 재사용하면 구버전 decoder가 틀린 값을 정상 값처럼 처리할 수 있습니다. Protocol Buffers를 쓰면 field 번호와 unknown field 처리 규칙을 지켜야 하며, JSON이라고 해서 의미 호환 문제가 없어지지는 않습니다.
type Hello = {
protocolMin: number;
protocolMax: number;
build: string;
};
function negotiate(hello: Hello) {
const selected = Math.min(hello.protocolMax, SERVER_PROTOCOL_MAX);
if (selected < Math.max(hello.protocolMin, SERVER_PROTOCOL_MIN)) {
return { accepted: false, reason: "update-required" };
}
if (!isSupportedBuild(hello.build)) {
return { accepted: false, reason: "game-build-required" };
}
return { accepted: true, protocol: selected };
}새 field를 넣을 때는 없는 값을 어떤 default로 해석할지 먼저 결정합니다. 0이 실제 유효 값이면 단순한 기본값으로 "전송되지 않음"을 구분할 수 없습니다. presence field, nullable wrapper, 명시적인 enum을 사용합니다. enum에는 Unknown 또는 안전한 fallback을 두고, client가 모르는 새 enum을 받았을 때 임의 action을 실행하지 않게 합니다.
배포 순서
호환 가능한 release는 보통 server가 새 형식을 읽음 -> client가 새 형식을 보냄 -> 오래된 형식 사용 중지 순서입니다. server가 먼저 새 request를 요구하면 아직 배포 중인 구버전 client가 막힙니다. 반대로 새 field가 없어도 server가 동작하도록 만든 뒤 client를 배포하면 rollout 창을 견딜 수 있습니다.
match를 시작할 때 build와 rule revision을 고정합니다. 매치 도중 server만 새 combat rule로 바꾸면 같은 입력을 client와 server가 다르게 예측합니다. 긴 매치가 있는 게임은 새 build를 새 match에만 배정하고, 기존 match는 drain할지 별도 compatibility path를 둘지 정합니다.
자주 틀리는 부분
version: 2를 모든 문제의 해법으로 두고 이전 decoder를 삭제하면, reconnect한 구 client와 저장된 replay를 읽을 수 없게 됩니다. version 숫자는 선택 기준일 뿐이며, 각 version이 지원하는 message, field 의미, 종료 시점을 release note와 test fixture로 남겨야 합니다.
또한 protocol version이 같다는 이유만으로 서로 다른 data table을 같은 match에 넣지 않습니다. wire compatibility와 gameplay compatibility는 별도 검증 대상입니다.
참고 링크
2 sources