패킷 포맷

Jaemyeong Lee·2025년 3월 23일

게임 서버1

목록 보기
150/220

패킷 규약(Contract) 먼저 고정

기본 형식

┌──────────┬──────────┬─────────────────────┐
│ Size(2B) │ ID(2B)   │ Payload (가변)       │
└──────────┴──────────┴─────────────────────┘
  • Size: 패킷 전체 길이(헤더 포함)로 고정하는 것이 가장 직관적입니다.
  • ID: 패킷 종류 식별자(로그인/이동/공격 등).

구조체와 검증

struct PacketHeader
{
    uint16 size; // header + payload
    uint16 id;
};
static_assert(sizeof(PacketHeader) == 4);

규약 문서화 항목

  • size가 헤더 포함인지 여부
  • 최대 패킷 크기(kMaxPacketSize)
  • 허용 ID 범위/예약 ID
  • 바이트 오더(서버-클라 이기종 가능성 있으면 네트워크 바이트 오더 명시)

송신 측 직렬화 원칙

순서

  1. Payload 작성
  2. Header(size, id) 채움
  3. Header + Payload를 하나의 전송 단위로 enqueue

예시

PacketHeader h{};
h.size = static_cast<uint16>(sizeof(PacketHeader) + payloadSize);
h.id   = PKT_C_LOGIN;

실수 방지

  • size 계산 시 오버플로우/캐스팅 범위 확인
  • payload 없는 패킷도 size >= sizeof(PacketHeader) 조건 유지

수신 측 파싱 상태 머신

표준 루프

  1. DataSize >= sizeof(PacketHeader)인지 확인
  2. 헤더를 임시 변수로 복사해 읽기
  3. size 검증(최소/최대)
  4. DataSize >= header.size면 완전체 처리
  5. OnRead(header.size) 후 다음 패킷 반복

예시 코드

while (recvBuffer.DataSize() >= sizeof(PacketHeader))
{
    PacketHeader h;
    memcpy(&h, recvBuffer.ReadPtr(), sizeof(h));

    if (h.size < sizeof(PacketHeader) || h.size > kMaxPacketSize)
    {
        Disconnect(L"invalid packet size");
        return;
    }

    if (recvBuffer.DataSize() < h.size)
        break; // 반쪽 패킷: 다음 Recv 대기

    OnRecvPacket(recvBuffer.ReadPtr(), h.size);
    recvBuffer.OnRead(h.size);
}
recvBuffer.Clean();

핵심

  • "헤더 가능"과 "완전체 가능"은 다른 조건입니다.
  • 이 두 조건을 분리해야 경계 없는 TCP에서 안전하게 동작합니다.

검증과 보안(필수)

기본 검증 체크리스트

  • size < sizeof(PacketHeader) 차단
  • size > kMaxPacketSize 차단
  • 정의되지 않은 id 차단
  • payload 길이와 타입 규약 불일치 차단

왜 필요한가

  • 클라이언트 입력은 신뢰할 수 없습니다.
  • 헤더 조작만으로도 OOB 파싱, 메모리 압박, 서비스 지연을 유발할 수 있습니다.

대응 정책

상황권장 대응
단순 포맷 오류 1회경고 로그 후 세션 종료
반복 비정상 패킷즉시 차단 + 모니터링 알림
대량 유사 패턴IP/계정 단위 rate limit 고려

길이 기반 프레이밍 vs 구분자 기반

방식장점단점
길이 기반(size header)바이너리 안전, 파싱 고속, 명확한 완전체 판별헤더 검증 필수
구분자 기반(\n, \0)텍스트 프로토콜 구현 단순바이너리 payload에서 충돌 위험

게임 서버의 바이너리 패킷에는 길이 기반 프레이밍이 일반적으로 더 안전합니다.


운영 관측 포인트

로그 필수 항목

  • sessionId, packetId, packetSize, dataSizeAtParse
  • 파싱 실패 사유 코드(sizeTooSmall, sizeTooLarge, unknownId)

지표

  • 초당 파싱 성공/실패 횟수
  • 평균/최대 패킷 크기
  • 비정상 ID 비율

디버깅 팁

  • 의심 세션은 헤더 16~32바이트 hex dump를 남기면 원인 파악 속도가 빨라집니다.

강의 시 유의사항

강조 포인트

  • TCP는 메시지 경계가 없고, 패킷 포맷이 그 경계를 복구합니다.
  • 패킷 포맷의 핵심은 "읽는 방법"보다 "검증 규칙"입니다.
  • Part 9(ReceiveBuffer)와 Part 12(PacketSession)을 연결해 설명해야 이해가 완성됩니다.

자주 하는 오해

오해바로잡기
헤더만 읽히면 바로 처리 가능완전체(DataSize >= size) 확인이 먼저다
정상 클라만 오니 검증은 나중에입력 검증 누락은 즉시 취약점이 된다
구분자 방식이 더 쉽고 안전바이너리 프로토콜에서는 충돌/오검출 위험이 크다

체크 질문 (스스로 답해보기)

  • size가 헤더 크기보다 작을 때 즉시 종료해야 하는 이유는?
  • 파싱 루프에서 breakDisconnect를 구분하는 기준은?
  • unknown packet id를 발견했을 때 서버 정책을 어떻게 둘 것인가?

profile
李家네_공부방

0개의 댓글