PacketSession의 역할

상속 위치

classDiagram
    Session <|-- PacketSession
    PacketSession <|-- GameSession
    PacketSession : +OnRecvPacket()
  • Session은 바이트 송수신 파이프라인을 담당합니다.
  • PacketSession은 그 위에 "바이트 스트림 -> 완전체 패킷" 조립 계층을 추가합니다.

핵심 목적

컨텐츠(GameSession)가 반쪽 패킷을 절대 보지 않게 하는 것.

경계

  • 엔진 계층: 패킷 경계 복구, 유효성 검증, 처리 바이트 계산
  • 컨텐츠 계층: 완성된 패킷 의미 처리(OnRecvPacket)

OnRecv final + OnRecvPacket 분리

패턴

  • OnRecvfinal로 고정해 하위에서 바이트 파싱 규칙을 바꾸지 못하게 합니다.
  • 하위 클래스는 OnRecvPacket만 오버라이드합니다.

이유

  • OnRecv를 열어두면 팀마다 파싱 규칙이 흩어져 버그가 늘어납니다.
  • 파싱 규칙을 엔진 단에 통일하면 검증/보안/로그 정책을 일관되게 유지할 수 있습니다.

인터페이스 예시

class PacketSession : public Session
{
public:
    int32 OnRecv(BYTE* buffer, int32 len) final;
    virtual void OnRecvPacket(BYTE* packet, int32 len) = 0;
};

OnRecv 처리 바이트 계약

반환값 의미

  • OnRecv는 "이번 호출에서 소비한 총 바이트 수"를 반환합니다.
  • 상위 Session::ProcessRecv는 이 값을 ReceiveBuffer::OnRead에 전달합니다.

반드시 지킬 규칙

  • 0 <= processedLen <= len
  • 오류가 있으면 음수 반환 또는 Disconnect 정책으로 명확히 분기

왜 중요한가

  • 처리 바이트 계산이 틀리면 ReadPos가 깨지고 이후 모든 패킷이 오염됩니다.

패킷 조립 루프 표준 구현

표준 의사코드

int32 PacketSession::OnRecv(BYTE* buffer, int32 len)
{
    int32 processed = 0;

    while (true)
    {
        const int32 remain = len - processed;
        if (remain < sizeof(PacketHeader))
            break; // 헤더 부족

        PacketHeader header;
        memcpy(&header, buffer + processed, sizeof(header));

        if (header.size < sizeof(PacketHeader) || header.size > kMaxPacketSize)
            return -1; // 비정상 헤더

        if (remain < header.size)
            break; // 반쪽 패킷

        OnRecvPacket(buffer + processed, header.size);
        processed += header.size;
    }

    return processed;
}

루프가 필요한 이유

  • 한 번의 Recv 완료에 여러 패킷이 붙어 올 수 있습니다.
  • 완전체가 연속으로 존재하면 한 호출에서 최대한 소모해야 지연을 줄일 수 있습니다.

ReceiveBuffer 연결

  • processed는 ReceiveBuffer의 OnRead(processed)로 전달됩니다.
  • 남은 반쪽 패킷은 다음 Recv까지 버퍼에 유지됩니다.

오류 처리 정책

권장 분기

상황대응
헤더 부족/패킷 부족break 후 다음 수신 대기
size 비정상즉시 Disconnect
알 수 없는 id정책에 따라 drop 또는 Disconnect

구현 팁

  • OnRecvPacket이 예외를 던질 가능성이 있으면 세션 보호 경로를 준비하세요.
  • 실패 사유 코드를 로그에 남겨 운영 분석 가능성을 확보하세요.

과도한 파싱 방지

  • 한 틱에서 처리할 최대 패킷 수를 두면 악성 입력으로 워커가 독점되는 것을 줄일 수 있습니다.

PacketSession과 PacketHandler의 연결

책임 연결

  • PacketSession: 경계 복구 + 헤더 검증 + 완전체 전달
  • PacketHandler(Part 13): id 기반 실제 비즈니스 핸들러 분기

장점

  • 파싱 안전성과 비즈니스 분기를 분리하면 코드 변경 영향 범위가 줄어듭니다.

실무 권장

  • OnRecvPacket 내부에서는 직접 switch를 두기보다 Handler 테이블을 호출하는 구조가 유지보수에 유리합니다.

강의 시 유의사항

강조 포인트

  • PacketSession은 "바이트를 패킷으로 바꾸는 어댑터"입니다.
  • 컨텐츠는 OnRecvPacket만 신뢰하면 되고, 반쪽 패킷 걱정은 엔진이 담당합니다.
  • OnRecv final은 확장 제한이 아니라 품질 보호 장치입니다.

자주 하는 오해

오해바로잡기
OnRecv를 열어두면 더 유연하다팀 규모가 커질수록 파싱 규칙 붕괴 위험이 커진다
패킷 부족도 오류다부족은 정상 스트림 상태이므로 다음 수신을 기다려야 한다
처리 바이트 계산은 대충 해도 된다커서가 한 번 틀어지면 이후 모든 패킷이 깨진다

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

  • OnRecv를 final로 고정하면 어떤 종류의 버그를 예방할 수 있는가?
  • 파싱 루프에서 breakreturn -1를 나누는 기준은 무엇인가?
  • 한 번의 Recv에서 여러 패킷을 처리하지 않으면 어떤 지연 문제가 생길 수 있는가?

profile
李家네_공부방

0개의 댓글