3. 접속 핸드셰이크에서의 프로토콜 버전 협상
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 접속 핸드셰이크에서의 프로토콜 버전 협상
// ----------------------------------------------------------------------------
// 시나리오:
// - 클라이언트가 접속하면 가장 먼저 C_Handshake 를 보낸다.
// - 서버는 자신이 지원하는 프로토콜 버전과 비교해 접속을 수락/거부한다.
// - 운영 중에는 서버가 먼저 신버전으로 배포되고(부분 롤아웃, 카나리),
// 구버전 클라와 신버전 클라가 동시에 접속한다.
// - 빌드 번호(BuildNumber)는 핫픽스마다 올라가지만, 프로토콜 호환성은
// ProtocolVersion 으로만 판단해야 한다(같은 프로토콜이면 빌드가 달라도 호환).
//
// - 와이어 포맷: [ushort protocolVersion][uint buildNumber][ushort nameLen][name...]
// 모든 정수는 little-endian 으로 합의되어 있다.
//
// 요구사항:
// - 호환되는 클라만 입장시키고, 호환 불가 클라에는 "업데이트 필요" 사유를
// 명확히 알려준 뒤 정상적으로 끊어야 한다.
// - 핸드셰이크는 인증 전 단계이므로, 어떤 입력이 와도 서버가 죽으면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 발생하는지 설명하고,
// 수정안과 더 나은 버전 협상 설계를 제시하라.
// ============================================================================
using System;
using System.Text;
// 서버가 지원하는 프로토콜 버전 (이 빌드 기준)
public static class ServerInfo
{
public const ushort ProtocolVersion = 7;
public const uint ExpectedBuild = 10420;
}
public class Session
{
public bool authorized = false;
public ushort clientProtocol = 0;
public string clientName;
public void SendDisconnect(string reason) { /* 사유 전송 후 종료 */ }
public void Close() { /* ... */ }
}
public class HandshakeHandler
{
// recvBuf[0..recvLen) 에 들어온 핸드셰이크 패킷(헤더+가변이름)
public void OnHandshake(Session s, byte[] recvBuf, int recvLen)
{
// (A) 받은 버퍼에서 필드를 순서대로 읽는다
ushort protocolVersion = BitConverter.ToUInt16(recvBuf, 0);
uint buildNumber = BitConverter.ToUInt32(recvBuf, 2);
ushort nameLen = BitConverter.ToUInt16(recvBuf, 6);
s.clientProtocol = protocolVersion;
// (B) 버전 호환성 판단
if (protocolVersion != ServerInfo.ProtocolVersion)
{
// 빌드가 다르면 일단 거부
if (buildNumber != ServerInfo.ExpectedBuild)
{
s.SendDisconnect("Version mismatch");
s.Close();
return;
}
}
// (C) 클라이언트 이름 읽기 (가변 길이)
s.clientName = Encoding.UTF8.GetString(recvBuf, 8, nameLen);
// (D) 여기까지 왔으면 호환되는 것으로 간주
s.authorized = true;
OnHandshakeComplete(s);
}
private void OnHandshakeComplete(Session s) { /* 로비 입장 등 */ }
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 접속 핸드셰이크에서의 프로토콜 버전 협상
// ----------------------------------------------------------------------------
// 시나리오:
// - 클라이언트가 접속하면 가장 먼저 C_Handshake 를 보낸다.
// - 서버는 자신이 지원하는 프로토콜 버전과 비교해 접속을 수락/거부한다.
// - 운영 중에는 서버가 먼저 신버전으로 배포되고(부분 롤아웃, 카나리),
// 구버전 클라와 신버전 클라가 동시에 접속한다.
// - 빌드 번호(BuildNumber)는 핫픽스마다 올라가지만, 프로토콜 호환성은
// ProtocolVersion 으로만 판단해야 한다(같은 프로토콜이면 빌드가 달라도 호환).
//
// 요구사항:
// - 호환되는 클라만 입장시키고, 호환 불가 클라에는 "업데이트 필요" 사유를
// 명확히 알려준 뒤 정상적으로 끊어야 한다.
// - 핸드셰이크는 인증 전 단계이므로, 어떤 입력이 와도 서버가 죽으면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 발생하는지 설명하고,
// 수정안과 더 나은 버전 협상 설계를 제시하라.
// ============================================================================
#include <cstdint>
#include <cstring>
#include <string>
// 서버가 지원하는 프로토콜 버전 (이 빌드 기준)
static constexpr uint16_t SERVER_PROTOCOL_VERSION = 7;
#pragma pack(push, 1)
struct C_Handshake {
uint16_t protocolVersion;
uint32_t buildNumber;
uint16_t nameLen;
char name[1]; // 가변 길이: 뒤에 nameLen 바이트가 이어진다
};
#pragma pack(pop)
struct Session {
bool authorized = false;
uint16_t clientProtocol = 0;
std::string clientName;
void SendDisconnect(const char* reason) { /* 사유 전송 후 종료 */ }
void Close() { /* ... */ }
};
class HandshakeHandler {
public:
// recvBuf[0..recvLen) 에 들어온 핸드셰이크 패킷(헤더+가변이름)
void OnHandshake(Session& s, const uint8_t* recvBuf, size_t recvLen)
{
// (A) 받은 버퍼를 핸드셰이크 구조체로 해석
const C_Handshake* hs = reinterpret_cast<const C_Handshake*>(recvBuf);
s.clientProtocol = hs->protocolVersion;
// (B) 버전 호환성 판단
if (hs->protocolVersion != SERVER_PROTOCOL_VERSION) {
// 빌드가 다르면 일단 거부
if (hs->buildNumber != EXPECTED_BUILD) {
s.SendDisconnect("Version mismatch");
s.Close();
return;
}
}
// (C) 클라이언트 이름 읽기 (가변 길이)
s.clientName.assign(hs->name, hs->nameLen);
// (D) 여기까지 왔으면 호환되는 것으로 간주
s.authorized = true;
OnHandshakeComplete(s);
}
private:
static constexpr uint32_t EXPECTED_BUILD = 10420;
void OnHandshakeComplete(Session& s) { /* 로비 입장 등 */ }
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.