PacketSession의 역할
상속 위치
classDiagram
Session <|-- PacketSession
PacketSession <|-- GameSession
PacketSession : +OnRecvPacket()
Session은 바이트 송수신 파이프라인을 담당합니다.
PacketSession은 그 위에 "바이트 스트림 -> 완전체 패킷" 조립 계층을 추가합니다.
핵심 목적
컨텐츠(GameSession)가 반쪽 패킷을 절대 보지 않게 하는 것.
경계
- 엔진 계층: 패킷 경계 복구, 유효성 검증, 처리 바이트 계산
- 컨텐츠 계층: 완성된 패킷 의미 처리(
OnRecvPacket)
OnRecv final + OnRecvPacket 분리
패턴
OnRecv는 final로 고정해 하위에서 바이트 파싱 규칙을 바꾸지 못하게 합니다.
- 하위 클래스는
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로 고정하면 어떤 종류의 버그를 예방할 수 있는가?
- 파싱 루프에서
break와 return -1를 나누는 기준은 무엇인가?
- 한 번의 Recv에서 여러 패킷을 처리하지 않으면 어떤 지연 문제가 생길 수 있는가?