친구찾기(DA·무호스트) 모드에서 겪은 네트워크 단절 문제들의 근본 원인·해결책·검증 근거를 한 곳에 모았다. 다음에 멀티플레이를 손댈 때 같은 길을 다시 헤매지 않기 위한 참고 문서.
RaceReadyGate가 죽어 데이터만 안 흐르는 경우가 많다.DontDestroyWithOwner = 1. 0이면 오너 이탈 시 세션 전체에서 파괴되고 다시 스폰되지 않는다. — 이번 오너 두절 문제의 단 하나의 근본 원인.IsConnectedClient가 아니라 "실제 데이터 재수신"으로 한다. 전송이 붙어도 앱 RPC는 안 올 수 있다(가짜 복구).문제의 절반은 Distributed Authority(무호스트) 구조를 Relay(호스트 기반)처럼 다뤄서 생긴다. 세 가지만 이해하면 나머지가 풀린다.
친구찾기는 WithDistributedAuthorityNetwork() — 전용 호스트가 없다. 세션 오너(DAHost)가 라우팅 주체이고, 오너가 나가면 오너십이 다른 클라로 이관된다. 레이스는 WithRelayNetwork()(호스트 기반, host migration 없음 → 호스트 나가면 세션 소멸).
RaceReadyGate는 씬에 배치된 NetworkObject로, 두 기기 사이의 위치·층이동·도착 RPC를 전부 이걸로 나른다. gate가 죽으면 두 기기는 서로의 상태를 한 글자도 못 주고받는다.
DA 오너십 이관 자체는 잘 된다(CurrentSessionOwner 1→2). 하지만 gate NetworkObject가 이관되도록 설정돼 있지 않으면(DontDestroyWithOwner=0) 파괴되고, 이관될 대상이 사라진다. "세션은 살아있는데 통신은 죽는" 상태의 정체.
증상 / 근본 원인 / 해결 / 근거(로그) 순. 상태 칩으로 검증 여부를 표시했다.
세션 오너 기기의 네트워크를 끊으면, 재입장은 되는데 위치 공유가 다시 살아나지 않고 "연결 끊김" 팝업이 반복된다.
재연결(세션 재입장·NGO 전송 복구)은 실제로 성공한다. 실패하는 건 위치 공유다. RaceReadyGate의 DontDestroyWithOwner=0 때문에, DA에서 오너가 이탈하면 gate가 세션 전체에서 파괴되고 → 이관 대상이 사라져 → RPC 채널이 영구 사망한다. 오너가 재입장해도 gate가 다시 스폰되지 않는다.
Assets/Scenes/7. FindFriend.unity의 RaceReadyGate NetworkObject DontDestroyWithOwner: 0 → 1 (1줄). 오너 이탈 시 gate가 파괴되지 않고 신 오너로 이관·생존 → RPC 채널 유지. 재입장한 옛 오너는 옵저버(SpawnWithObservers=1)로 gate를 받아 다시 붙는다.
1→2 성공Gate OnNetworkDespawn (파괴)Gate OnNetworkSpawn 없음재연결 실패: 위치 RPC 재개 실패 반복 → 팝업Gate 소유권 획득 (파괴 없이 이관)Gate OnNetworkSpawn 재발생진짜 복구 — 위치 RPC 재개 확인 수신경과 0.0s 4회 성공# BEFORE (미수정) — 정지 클라 관점 [15:40:48] Gate OnNetworkDespawn — LocalClientId=2 NObjId=10001 [15:40:48] [NetDbg][L2] 세션오너 변경 1 → 2 (내가오너=True) # 이관은 성공 ...재입장(clientId=3,4) 이후에도 Gate OnNetworkSpawn 없음 [15:41:37] 재연결 실패 1/3: 위치 RPC 재개 실패(6s 내 상대 위치 무수신) # AFTER (DontDestroyWithOwner=1) [16:08:00] Gate 소유권 상실 → Gate 소유권 획득 — 내가 새 오너 (gate 생존·RPC 라우팅 유지) [16:08:39] Gate OnNetworkSpawn — LocalClientId=3 # 재입장 시 재스폰 [16:08:40] 진짜 복구(전체 재입장) — 위치 RPC 재개 확인 hasRemote=True 수신경과=0.0s
IsConnectedClient=true로 "연결됨"인데 상대 위치가 계속 안 온다.
NGO 전송이 붙었다는 것이 애플리케이션 RPC 재개를 뜻하지 않는다. gate 미재스폰(문제 ①), Reliable 큐 적체 등으로 데이터만 안 흐를 수 있다. 실측: '복구' 후 177s 무수신.
재연결 성공 판정을 전송 플래그가 아니라 실제 상대 위치 재수신으로 한다 — WaitForRpcResumeAsync: 재연결 시각 이후 _lastRemoteRecvTime > since가 될 때까지 확인, timeout이면 '가짜 복구'로 재시도. 이게 있어야 팝업/토스트가 진짜 복구만 알린다.
두절 후 팝업(→재입장)까지 최대 60s 하드캡을 기다려 복구가 느리다.
초기엔 "총 무연결 NGO_GIVEUP_SEC(60s)" 하드캡에서만 팝업했다.
망은 살아있는데(HasActiveNetwork) NGO만 끊긴 상태가 RECONNECT_NETUP_GIVEUP(15s) 누적되면 팝업→재입장. 망 none 동안은 카운트 정지(붙을 망이 없으면 재입장해도 실패하므로). 총 무연결 60s 하드캡은 안전망으로 유지.
망사용가능 15s(임계 15s) → 끊김 팝업(최후 수단 전체 재입장) 발동 확인.
한쪽이 이동(걷기·층 이동) 중이면 상대의 위치 수신이 끊긴다. 죽는 방향이 이동하는 기기를 따라 뒤집힌다.
이동하는 기기의 업로드(업링크)가 먼저 끊긴다(셀룰러/계단/빠른 이동). 상대는 hasRemote=False로 수신 0건이 되어 화면 변화가 없다. 코드 문제가 아니라 무선 환경 문제.
수신경과 3s↑ "연결이 약합니다" 토스트 + 15s↑ 능동 재연결. 근본 회피는 이동하는 쪽을 비오너로 고정(오너 두절 비대칭 위험까지 함께 해결).
수동 재바인딩을 두 갈래로 시도했다 — 전략 A(Leave+Join), 전략 B(Shutdown+ReconnectAsync). flapping 망에서 각각 no-op·파괴적·'player is already a member'로 확인되어 자동 경로에서 폐기.
평상시엔 NGO/UnityTransport 내장 Relay 재연결에 맡긴다(UpdateConnectionHealth가 IsConnectedClient로 감시). 자체 복구가 끝내 실패했을 때만 최후 수단으로 세션 정리 후 코드 재입장(JoinByCode). 팝업~재시도 사이 자체 복구되면(IsConnectedClient=true) 파괴적 재입장을 취소.
크로스층 유도용 이동수단(엘베·계단) 노드가 0개로 로드된다.
LoadTransitNodesAsync가 Android persistentDataPath(로컬, file:// 스킴 없음)를 UnityWebRequest로 읽으려다 실패.
경로에 "://"가 없으면(로컬) File.ReadAllText로 직접 읽도록 분기.
FindFriendBootstrap의 조정 포인트. 자체 복구를 자주 뺏으면 값을 올리고, 복구가 느리면 내린다.
| 상수 | 값 | 의미 |
|---|---|---|
RECONNECT_NETUP_GIVEUP | 15s | 망 살아있음 + NGO만 끊김이 이만큼 누적되면 팝업→재입장 (옵션1 튜닝의 핵심) |
NGO_GIVEUP_SEC | 60s | 총 무연결 하드캡. 망이 끝내 안 돌아와도 반드시 팝업 |
RPC_RESUME_TIMEOUT | 6s | 재입장 후 실제 상대 위치 재수신 대기(가짜 복구 판정) |
MAX_RECONNECT | 3 | 최후 수단 재입장 최대 시도(망 복귀 후 실제 시도만 카운트) |
NGO_CONNECT_TIMEOUT | 8s | 재입장 후 IsConnectedClient 대기 |
NGO_SHUTDOWN_TIMEOUT | 3s | 재조인 전 NGO 완전 종료 대기('Failed to start' 레이스 방지) |
RECONNECT_OP_TIMEOUT | 12s | JoinByCode 무한 hang 방지 컷오프 |
CONN_WEAK_SEC | 3s | 수신 두절 이만큼이면 "연결 약함" 토스트 |
DisconnectTimeoutMS | 30s | UnityTransport 런타임 값(씬 직렬화). 두절→끊김 판정 지연 |
DontDestroyWithOwner=1IsConnectedClient=true는 전송만 뜻한다. 앱 RPC가 실제로 다시 오는지(마지막 수신 시각 갱신)까지 확인해 '가짜 복구'를 걸러라.HasActiveNetwork 게이트로 붙을 망이 없을 때의 무의미한 재시도 폭주('Cannot resolve host')를 막는다. 망 복귀가 재트리거.두 기기 동시 두절(C) — 세션을 지탱할 오너가 없어 MPS 세션/Relay가 소멸한다. 팝업으로 수동 복귀만 가능. (테스트 대상은 "오너 두절 O / 둘 다 두절은 안 되는 것"이 맞다.)
친구 이동수단 방송 유실 — 이동 기기가 두절 상태에서 이동수단을 고르면 선택 RPC가 유실되고 재연결 후 재방송하지 않아, 정지 기기가 유도 노드를 못 찾는다(안전 보류로 저하, 오안내는 아님). 재연결 후 재방송이 후속 과제.
MPS 세션/Relay의 실제 TTL(무주인 세션 소멸 시각) — 아직 실측 안 함. 크로스층 DA·오너십 재이관의 장기 안정성도 해피패스만 검증됨.