← 일감 목록
T-001

하트비트로 끊김 감지

서버 완료 우선순위 높음 마감 2026-08-04 · 보관됨

배경

오프라인 진행을 폐지하면서 접속 판정이 곧 재화 생성 조건이 됐다. 지금은 TCP가 끊겨야 세션이 정리되는데, 그 사이(랜선을 뽑거나 PC가 절전에 들어간 구간)는 서버가 여전히 "접속 중"으로 보고 채취를 계속 굴린다. 그만큼이 공짜로 얻는 진행이다.

TCP는 끊김을 즉시 알려주지 않는다. RST가 오지 않는 종류의 단절(전원 차단·네트워크 소실)에서는 OS 기본 keepalive가 돌 때까지 수 시간이 걸릴 수도 있다.

할 일

  • [x] 하트비트 주기 결정 — 5초 (클라이언트 선행 구현에서 확정)
  • [x] 무응답 판정 시간 결정 — 15초. 채취 기준 주기(30초)보다 짧아 부당 적립 구간이 판정 1회를 채우지 못한다
  • [x] 재접속 유예를 둘지 결정 — 두지 않는다. 끊기면 진행 조각은 버린다(작업슬롯 3.1과 같은 규칙)
  • [x] C_HeartbeatRequest / S_HeartbeatResponse 패킷 추가불필요. C_PingRequest/S_PongResponse가 이미 있고 클라가 그걸 쓴다
  • [x] 무응답 세션 정리 로직 — SessionWatchdog(5초 타이머 → 로직 스레드)
  • [x] 정리 시 마지막 정산이 도는지 확인 — Disconnect()Disconnectedsession.GetUser()?.Destroy()OnDestroyDisconnect(now) 경로로 이어진다

완료 조건

랜선을 뽑은 뒤 정해진 무응답 시간 안에 세션이 정리되고, 그 이후 구간의 채취가 정산되지 않는다. 순단(짧은 끊김) 후 재접속했을 때의 동작이 결정한 대로 나온다.

코드는 완료. 실기 재현(Unity 플레이 중지·랜선 뽑기) 확인이 남았다.

구현 요약

살아 있음의 증거는 수신뿐이다. Send는 소켓 쓰기가 아니라 큐 적재(용량 1024)라 소켓이 죽어도 성공한다 — 송신 성공은 연결의 증거가 되지 못한다.

위치 내용
MikaNetwork.Server/IHeartbeatSession.cs 유휴 판정용 좁은 면. ISession에 얹지 않았다 — Unity에 수동 사본이 있고 유휴 판정은 서버만의 관심사다
MikaServerSession LastReceivedAt 추가(수신 시 갱신) · Disconnect()Interlocked로 멱등화
MikaServer.DisconnectIdle 세션 목록·시각·임계값을 인자로 받는 본체. 주기·임계값을 Lib이 정하지 않는다
WSGameServer/Common/SessionWatchdog.cs 5초 타이머 → 로직 스레드에서 스윕
Global.SessionIdleTimeout 15초
Entity.Destroy() 멱등 가드 — 여러 경로에서 들어와 종료 정산이 중복되던 것
UserManager.CreateUser pid 중복 시 기존 세션을 끊고 새 로그인을 받는다(기존: 무응답 return)
UserManager.LeaveUser 값 일치 제거로 바꿔, 좀비의 늦은 정리가 살아 있는 세션의 매핑을 지우지 않게 했다
EResultCode.AlreadyLoggedIn = 2 같은 세션의 중복 로그인 요청에 응답

테스트 11건 추가(108 → 119). 유휴 스윕 6건 + 정리 멱등·매핑 정합성 5건.

막고 있는 것 / 선행 일감

  • 주기·판정 시간·유예 세 값이 기획 미정이다2026-08-04 확정 (작업슬롯 3.3)

관련 커밋

  • da06737User를 채널·DB 큐 주입으로 분리 (이 작업이 딛고 선 seam)
  • 이슈 #10 대응 커밋 (본 작업)

참고

  • 작업슬롯 3.3 하트비트 — ✅ 확정
  • 게임기획코어 5장 — 하트비트로 끊김 감지 ✅ 확정
  • GitHub 이슈 #10 — 좀비 유저