Session의 역할과 경계
한 줄 정의
Session은 "클라이언트 1개 연결의 네트워크 상태와 I/O 파이프라인"을 대표하는 객체입니다.
Session이 담당하는 일
| 담당 | 설명 |
|---|
| 송수신 파이프라인 | RegisterRecv/ProcessRecv, Send/ProcessSend |
| 연결 상태 | Connected/Disconnecting 같은 상태 전이 관리 |
| 주소/식별 정보 | 원격 주소, 세션 ID, 통계 |
| 이벤트 콜백 브릿지 | OnRecv, OnDisconnected 등을 컨텐츠로 전달 |
Session이 하지 말아야 할 일
- 전투 판정, AI, 퀘스트 같은 게임 규칙 처리
- DB 직접 접근(로직/DB 파이프라인을 우회하는 구조)
- 전역 공유 상태 직접 수정(락 폭발 원인)
상태 전이(State Machine)
권장 상태
enum class SessionState
{
Disconnected,
Connecting,
Connected,
Disconnecting
};
전이 예시
| 이벤트 | 전이 |
|---|
| Accept/Connect 완료 | Disconnected -> Connected |
| Disconnect 시작 | Connected -> Disconnecting |
| 정리 완료 | Disconnecting -> Disconnected |
핵심 규칙
Disconnect는 idempotent(여러 번 호출돼도 한 번처럼 동작)하게 설계하세요.
- 상태 전이는 CAS/락으로 보호해서 중복 종료를 막아야 합니다.
IocpObject 상속과 Dispatch
왜 상속하는가
- Session도 IOCP 완료 이벤트를 직접 받는 대상입니다.
- 따라서
GetHandle과 Dispatch를 구현해 이벤트를 내부 처리 함수로 분기해야 합니다.
Dispatch 예시
void Session::Dispatch(IocpEvent* event, int32 numOfBytes)
{
switch (event->eventType)
{
case EventType::Recv: ProcessRecv(event, numOfBytes); break;
case EventType::Send: ProcessSend(event, numOfBytes); break;
case EventType::Connect: ProcessConnect(event, numOfBytes); break;
case EventType::Disconnect: ProcessDisconnect(event, numOfBytes); break;
default: break;
}
}
분기 시 주의
numOfBytes == 0(Recv)은 일반적으로 원격 정상 종료 신호로 처리합니다.
- 실패 완료(
GQCS FALSE && ov != nullptr)도 이벤트 타입 기준 정리 경로로 흘려야 합니다.
외부 인터페이스 설계
대표 API
| 함수 | 용도 | 호출 주체 |
|---|
Send(buffer) | 송신 요청 enqueue/등록 | 게임 로직/서비스 |
Connect() | 클라이언트 모드 접속 시작 | 클라이언트 서비스 |
Disconnect(reason) | 연결 종료 시작 | 모든 계층 |
IsConnected(), GetAddress() | 상태/정보 조회 | 로깅/관리 코드 |
API 계약(Contract)
Send는 보통 "즉시 전송 완료"가 아니라 "전송 요청 수락" 의미입니다.
- 연결 종료 진행 중(
Disconnecting)에는 신규 Send를 거부하는 정책이 안전합니다.
Disconnect 호출 후 콜백(OnDisconnected)이 늦게 올 수 있음을 계약에 명시해야 합니다.
가상 콜백과 스레드 경계
콜백 포인트
| 함수 | 호출 시점 | 일반 용도 |
|---|
OnConnected | 연결 확정 직후 | 인증 시작, 초기 패킷 전송 |
OnRecv | 유효 데이터 수신 시 | 패킷 파싱/Job 생성 |
OnSend | 송신 완료 시 | 송신 통계/후속 전송 |
OnDisconnected | 종료 정리 직후 | 세션 제거, 리소스 정리 |
스레드 경계 원칙
- 이 콜백들은 보통 네트워크 워커 스레드에서 호출됩니다.
- 무거운 게임 로직은 콜백에서 직접 실행하지 말고 JobQueue로 넘기세요.
재진입 주의
OnDisconnected 안에서 다시 Disconnect를 호출해도 안전해야 합니다.
- 콜백 내에서 Session 소멸을 강제하면 완료 이벤트와 레이스가 날 수 있습니다.
Send/Recv 파이프라인 요약
Recv 루프
RegisterRecv -> ProcessRecv -> OnRecv -> RegisterRecv
- 이 루프가 한 번 끊기면 해당 세션은 더 이상 수신하지 못합니다.
Send 루프
Send enqueue -> (필요 시) RegisterSend -> ProcessSend -> 남은 데이터 확인 -> 재등록
- 세션별 송신 큐 + 전송 중 플래그를 사용하면 동시
Send 경합을 줄일 수 있습니다.
부분 전송/부분 수신
Send/Recv는 한 번에 원하는 크기를 모두 처리한다는 보장이 없습니다.
- 누적 버퍼/패킷 프레이밍 로직을 Session 계층에 명시적으로 둬야 합니다.
강의 시 유의사항
강조 포인트
- Session은 "소켓 래퍼"가 아니라 "연결 생명주기 관리자"입니다.
- Listener(접속 파이프라인)와 Session(연결 파이프라인)을 역할별로 분리해 설명하세요.
- Part 6(레퍼런스 카운팅)과 이어서 "수명 보장 없으면 Session은 반드시 터진다"를 강조하세요.
자주 하는 오해
| 오해 | 바로잡기 |
|---|
Send() 호출하면 바로 전송 완료다 | 대부분 "전송 요청 등록"이다 |
OnRecv에서 게임 로직을 직접 돌려도 된다 | 워커 스레드 블로킹으로 전체 처리량이 떨어진다 |
| Disconnect는 한 번만 호출된다 | 실전에서는 중복 호출을 항상 가정해야 안전하다 |
체크 질문 (스스로 답해보기)
- Session 상태 전이를 원자적으로 보장하지 않으면 어떤 버그가 나는가?
OnRecv에서 바로 로직을 실행하지 않고 JobQueue로 넘기는 이유는 무엇인가?
- Recv 재등록이 누락됐는지 운영 중 어떤 지표/증상으로 감지할 수 있는가?