Service의 본질

한 줄 정의

Service는 "세션 생성 규칙 + 접속 모델(Server/Client) + 세션 생명주기"를 조율하는 오케스트레이터입니다.

왜 필요한가

  • Listener/Session만으로는 "누가 세션을 만들고, 누가 보관하고, 언제 정리하는지"가 흩어집니다.
  • Service가 이 정책을 중앙화하면 엔진 Core와 컨텐츠 경계를 깔끔하게 유지할 수 있습니다.

주요 책임

책임설명
세션 생성SessionFactory를 통해 구체 타입 생성
세션 저장소 관리AddSession, ReleaseSession, 조회
시작/종료 수순Start, CloseService, 종료 전파
역할 모델Server/Client 모드에 맞는 동작 선택

ServiceType과 배치 모델

기본 타입

타입특징
ServerListener 보유, 외부 접속 수락
ClientListener 없음, 원격 서버로 Connect

복합 역할

  • 하나의 프로세스가 동시에 여러 Service를 가질 수 있습니다.
  • 예: 게임 서버는 플레이어에겐 Server, 캐시 서버엔 Client로 동작.

설계 포인트

  • 역할이 다르면 장애 정책도 달라집니다(강제 종료 vs 재연결 시도).
  • Service 단위로 정책을 분리해야 운영 중 대응이 단순해집니다.

SessionFactory 패턴

문제

  • 엔진은 GameSession, ChatSession 같은 컨텐츠 타입을 알면 안 됩니다.
  • 타입 의존이 생기면 엔진 재사용성이 무너집니다.

해결

using SessionFactory = std::function<SessionRef(void)>;

SessionFactory factory = []() -> SessionRef {
    return std::make_shared<GameSession>();
};
  • Service는 "함수 호출"만 하고, 어떤 Session이 생성될지는 컨텐츠가 결정합니다.

확장성

  • 풀링 기반 생성, 테스트용 MockSession, 역할별 Session 타입 분기 모두 가능합니다.

ServerService의 세션 생성 흐름

표준 흐름

  1. Listener::ProcessAccept에서 service->CreateSession() 호출
  2. SessionFactory() 실행 -> SessionRef 획득
  3. session->SetService(shared_from_this()) 연결
  4. iocpCore->Register(session)로 IOCP 연결
  5. AddSession(session)로 저장소 편입
  6. session->ProcessConnect()/RegisterRecv() 시작

SetService가 중요한 이유

  • Session 내부에서 Service 정책(브로드캐스트, 세션 제거, 설정 조회)을 참조합니다.
  • 누락 시 GetService() null 경로로 런타임 크래시가 발생할 수 있습니다.

실패 시 롤백 규칙

  • 생성/등록 중간 실패 시 세션을 저장소에 넣지 않습니다.
  • 이미 넣은 뒤 실패하면 반드시 ReleaseSession으로 일관되게 되돌립니다.

세션 저장소와 락 전략

기본 패턴

USE_LOCK;
std::unordered_set<SessionRef> _sessions;

void AddSession(const SessionRef& s)
{
    WRITE_LOCK;
    _sessions.insert(s);
}

샤딩(sharding) 락

  • 동접이 커지면 단일 락은 병목이 됩니다.
  • 세션 ID hash 기반으로 락/버킷을 나누면 경합을 줄일 수 있습니다.

데드락 예방 규칙

  • 락 보유 상태에서 외부 콜백(OnDisconnected, 서비스 외부 함수) 호출 금지
  • 락 순서 고정(예: Service 락 -> Session 락)
  • 같은 std::mutex 재진입 잠금은 데드락 위험이 있으니 호출 스택을 명확히 관리

Start/Stop 수명주기

시작 순서(권장)

  1. Service 설정 검증(factory, 주소, maxSession)
  2. Listener 시작(Server 모드)
  3. 워커/타이머/부가 스레드 시작

종료 순서(권장)

  1. 신규 접속 차단(Listener 중지)
  2. 기존 Session에 Disconnect 전파
  3. 워커 종료 신호 전달 및 Join
  4. 세션 저장소 비우기

핵심 원칙

  • 종료는 "한 번 호출되면 다시 호출돼도 안전"해야 합니다(idempotent close).

연결 끊김 정책(운영 관점)

상황예시권장 대응
핵심 의존 끊김게임 서버 <-> 캐시/인증 핵심 노드fail-fast 또는 보호 모드 진입
비핵심 연동 끊김운영툴/로그 수집기지수 백오프 재연결
클라이언트 개별 끊김단일 유저 네트워크 이탈해당 세션만 정리

정책 분리 이유

  • 모든 연결 끊김을 같은 방식으로 처리하면 과잉 장애 전파가 생깁니다.

로그 필수 항목

  • serviceType, peer, reasonCode, retryCount, uptime

강의 시 유의사항

강조 포인트

  • Service는 단순 컨테이너가 아니라 정책 계층입니다.
  • SessionFactory로 엔진-컨텐츠 의존을 끊는 것이 아키텍처 핵심입니다.
  • SetServiceAddSession 순서가 깨지면 수명/참조 버그가 발생합니다.

자주 하는 오해

오해바로잡기
Service는 Start만 있으면 된다Stop/종료 수순이 더 중요하다
락은 하나면 관리가 쉽다동접 증가 시 단일 락이 병목이 된다
핵심/비핵심 연결 끊김은 같은 정책이면 된다의존도 기반으로 분리해야 안정적이다

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

  • CreateSession 실패 시 어떤 단계까지 롤백해야 일관성이 유지되는가?
  • Service 종료 중에 AddSession이 들어오지 않게 하려면 어떤 플래그/락 정책이 필요한가?
  • 내 프로젝트에서 "핵심 의존 연결"과 "비핵심 연결"을 어떻게 분류할 것인가?

profile
李家네_공부방

0개의 댓글