static void OnDedicatedServerSpawned( Guid match_id, List <string> account_ids, bool success, JObject match_data, long match_type) { Log.Assert(response_handler != null); // // 데디케이티드 서버 스폰 결과를 받는 콜백 핸들러입니다. // // 대기 중인 유후 서버가 있다면 데디케이티드 서버 매니저는 이 핸들러를 거의 즉시 // 호출하지만, 대기 중인 서버가 없다면 최대 FLAGS_dedicated_server_spawn_timeout // 초 만큼 기다린 후 이 핸들러를 호출할 수 있습니다 (success=false). // // 데디케이티드 서버 매니저는 이 콜백 함수가 끝나는 즉시 클라이언트에게 데디케이티드 // 서버 접속 메시지를 보냅니다. 즉, 데디케이티드 서버 매니저는 이 콜백 함수를 호출하는 // 시점에서 클라이언트가 데디케이티드 서버가 연결하기 전이라는 것을 보장합니다. // // 다음은 데디케이티드 서버 스폰 실패 시 확인해야 할 내용들입니다. // // - 접속 가능하고 매치를 받을 수 있는 데디케이티드 서버 호스트가 있는지 확인해주세요. // - Redis 의 ife-dedi-hosts 키에 등록된 호스트가 있는지 확인해주세요. // - AWS 를 사용할 경우 FLAGS_dedicated_server_spawn_timeout 이 충분히 길어야 // 합니다. 그렇지 않으면 새 서버를 추가하는 동안 타임 아웃이 날 수 있습니다) // - 데디케이티드 서버 실행 시 인지를 제대로 주었는지 확인해주세요. // - 유니티/언리얼 데디케이티드 서버는 정상적으로 실행했으나, 초기화(Ready)를 // 정상적으로 수행하지 못했거나, 프로세스가 크래시 했을 가능성도 있습니다. // Log.Info("OnDedicatedServerSpawned: match_id={0}, success={1}", match_id, (success ? "succeed" : "failed")); if (success) { lock (match_lock) { MyMatchInfo info = new MyMatchInfo(match_id, match_type, match_data); match_map[match_id] = info; if (!match_type_map.ContainsKey(match_type)) { match_type_map[match_type] = new SortedSet <Guid>(); } match_type_map[match_type].Add(match_id); } } foreach (var account_id in account_ids) { // 각 클라이언트가 데디케이티드 서버로 접속하기 전에 처리해야 할 것이 // 있을 경우, 이 곳에서 처리해야 합니다. Session session = AccountManager.FindLocalSession(account_id); if (session == null) { // 다른 곳 또는 사용자가 세션을 닫거나 로그아웃 한 상태입니다. return; } if (!success) { response_handler(SessionResponse.ResponseResult.FAILED, new SessionResponse(session, 500, "Internal server error.", new JObject())); return; } // 클라이언트에게 보낼 응답은 이 곳에 설정합니다. JObject response_data = new JObject(); response_data["match_then_dedi_key1"] = "match_then_dedi_value1"; response_data["match_then_dedi_key2"] = "match_then_dedi_value2"; response_data["match_then_dedi_key3"] = "match_then_dedi_value3"; response_handler(SessionResponse.ResponseResult.OK, new SessionResponse(session, 200, "OK", response_data)); } // for (const auto &account_id : account_ids) }
public static void SendUser( long match_type, string account_id, JObject user_data, DedicatedServerHelper.SendUserCallback send_callback) { Log.Assert(MatchmakingType.IsValidMatchType(match_type)); bool found = false; Guid target_match_id; JObject target_match_data = new JObject(); // 매치 타입과 일치하는 플레이어 수를 선택합니다. long total_players_for_match = MatchmakingType.GetNumberOfMaxPlayers(match_type); do { lock (match_lock) { // 현재 활성화된 매치를 검사하여 플레이어가 부족한 서버를 찾습니다. // 순서는 UUID 를 따르므로 별다른 우선 순위가 없습니다. // 조금 더 공정한 절차가 필요하다면 MyMatchInfo 에 모든 플레이어 없이 게임을 진행한 // 시간을 기록한 후, 이를 기준으로 우선순위를 부여할 수 있습니다. // // 1. 먼저 매치 타입으로 매치 ID 맵을 가져옵니다. if (!match_type_map.ContainsKey(match_type)) { break; } // 2. ID 에 해당하는 매치 목록을 순회하면서 필요한 플레이어보다 부족한 // 서버를 찾습니다. foreach (Guid match_id in match_type_map[match_type]) { Log.Assert(match_map.ContainsKey(match_id)); MyMatchInfo info = match_map[match_id]; if (info.players.Count < total_players_for_match) { // 서버를 찾았습니다. target_match_id = info.match_id; target_match_data = info.match_data; found = true; break; } } } }while (false); if (!found) { // 모든 서버가 매치에 필요한 플레이어를 확보했거나 서버가 없습니다. // 더 이상 진행할 수 없습니다. Log.Info("There's no available server to send user"); send_callback(false); } else { // 난입을 요청합니다. List <string> account_ids = new List <string> { account_id }; List <JObject> user_data_list = new List <JObject> { user_data }; DedicatedServerManager.SendCallback send_cb = new DedicatedServerManager.SendCallback( (Guid match_id2, List <string> users2, bool success2) => { OnUserSent(match_id2, users2, success2, target_match_data, match_type, send_callback); }); DedicatedServerManager.SendUsers( target_match_id, target_match_data, account_ids, user_data_list, send_cb); } }