Service의 본질
한 줄 정의
Service는 "세션 생성 규칙 + 접속 모델(Server/Client) + 세션 생명주기"를 조율하는 오케스트레이터입니다.
왜 필요한가
- Listener/Session만으로는 "누가 세션을 만들고, 누가 보관하고, 언제 정리하는지"가 흩어집니다.
- Service가 이 정책을 중앙화하면 엔진 Core와 컨텐츠 경계를 깔끔하게 유지할 수 있습니다.
주요 책임
| 책임 | 설명 |
|---|
| 세션 생성 | SessionFactory를 통해 구체 타입 생성 |
| 세션 저장소 관리 | AddSession, ReleaseSession, 조회 |
| 시작/종료 수순 | Start, CloseService, 종료 전파 |
| 역할 모델 | Server/Client 모드에 맞는 동작 선택 |
ServiceType과 배치 모델
기본 타입
| 타입 | 특징 |
|---|
Server | Listener 보유, 외부 접속 수락 |
Client | Listener 없음, 원격 서버로 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의 세션 생성 흐름
표준 흐름
Listener::ProcessAccept에서 service->CreateSession() 호출
SessionFactory() 실행 -> SessionRef 획득
session->SetService(shared_from_this()) 연결
iocpCore->Register(session)로 IOCP 연결
AddSession(session)로 저장소 편입
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 수명주기
시작 순서(권장)
- Service 설정 검증(factory, 주소, maxSession)
- Listener 시작(Server 모드)
- 워커/타이머/부가 스레드 시작
종료 순서(권장)
- 신규 접속 차단(Listener 중지)
- 기존 Session에 Disconnect 전파
- 워커 종료 신호 전달 및 Join
- 세션 저장소 비우기
핵심 원칙
- 종료는 "한 번 호출되면 다시 호출돼도 안전"해야 합니다(idempotent close).
연결 끊김 정책(운영 관점)
| 상황 | 예시 | 권장 대응 |
|---|
| 핵심 의존 끊김 | 게임 서버 <-> 캐시/인증 핵심 노드 | fail-fast 또는 보호 모드 진입 |
| 비핵심 연동 끊김 | 운영툴/로그 수집기 | 지수 백오프 재연결 |
| 클라이언트 개별 끊김 | 단일 유저 네트워크 이탈 | 해당 세션만 정리 |
정책 분리 이유
- 모든 연결 끊김을 같은 방식으로 처리하면 과잉 장애 전파가 생깁니다.
로그 필수 항목
serviceType, peer, reasonCode, retryCount, uptime
강의 시 유의사항
강조 포인트
- Service는 단순 컨테이너가 아니라 정책 계층입니다.
SessionFactory로 엔진-컨텐츠 의존을 끊는 것이 아키텍처 핵심입니다.
SetService와 AddSession 순서가 깨지면 수명/참조 버그가 발생합니다.
자주 하는 오해
| 오해 | 바로잡기 |
|---|
| Service는 Start만 있으면 된다 | Stop/종료 수순이 더 중요하다 |
| 락은 하나면 관리가 쉽다 | 동접 증가 시 단일 락이 병목이 된다 |
| 핵심/비핵심 연결 끊김은 같은 정책이면 된다 | 의존도 기반으로 분리해야 안정적이다 |
체크 질문 (스스로 답해보기)
CreateSession 실패 시 어떤 단계까지 롤백해야 일관성이 유지되는가?
- Service 종료 중에
AddSession이 들어오지 않게 하려면 어떤 플래그/락 정책이 필요한가?
- 내 프로젝트에서 "핵심 의존 연결"과 "비핵심 연결"을 어떻게 분류할 것인가?