Compare commits
2 Commits
18fd8a0737
...
bfa3394ad1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfa3394ad1 | ||
|
|
c8ce36a624 |
@@ -11,29 +11,46 @@ public class RestApi : Singleton<RestApi>
|
||||
private const string VERIFY_URL = "https://a301.api.tolelom.xyz/api/auth/verify";
|
||||
private readonly HttpClient httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
|
||||
|
||||
// 토큰 검증 - 성공 시 username 반환, 실패(401/타임아웃 등) 시 null 반환
|
||||
private const int MAX_RETRY = 3;
|
||||
private static readonly TimeSpan RETRY_DELAY = TimeSpan.FromSeconds(1);
|
||||
|
||||
// 토큰 검증 - 성공 시 username 반환
|
||||
// 401 → 재시도 없이 즉시 null 반환 (토큰 자체가 무효)
|
||||
// 타임아웃/네트워크 오류 → 최대 MAX_RETRY회 재시도 후 null 반환
|
||||
public async Task<string?> VerifyTokenAsync(string token)
|
||||
{
|
||||
try
|
||||
for (int attempt = 1; attempt <= MAX_RETRY; attempt++)
|
||||
{
|
||||
HttpResponseMessage response = await httpClient.PostAsJsonAsync(VERIFY_URL, new { token });
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
try
|
||||
{
|
||||
Log.Warning("[RestApi] 인증 실패 (401)");
|
||||
return null;
|
||||
HttpResponseMessage response = await httpClient.PostAsJsonAsync(VERIFY_URL, new { token });
|
||||
|
||||
// 401: 토큰 자체가 무효 → 재시도해도 같은 결과, 즉시 반환
|
||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
Log.Warning("[RestApi] 토큰 인증 실패 (401)");
|
||||
return null;
|
||||
}
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
AuthVerifyResponse? result = await response.Content.ReadFromJsonAsync<AuthVerifyResponse>();
|
||||
return result?.Username;
|
||||
}
|
||||
catch (Exception ex) when (attempt < MAX_RETRY)
|
||||
{
|
||||
// 일시적 장애 (타임아웃, 네트워크 오류 등) → 재시도
|
||||
Log.Warning("[RestApi] 통신 실패 (시도 {Attempt}/{Max}): {Message}", attempt, MAX_RETRY, ex.Message);
|
||||
await Task.Delay(RETRY_DELAY);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 마지막 시도까지 실패
|
||||
Log.Error("[RestApi] 최종 통신 실패 ({Max}회 시도): {Message}", MAX_RETRY, ex.Message);
|
||||
}
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
AuthVerifyResponse? result = await response.Content.ReadFromJsonAsync<AuthVerifyResponse>();
|
||||
return result?.Username;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("[RestApi] 웹서버 통신 실패: {Message}", ex.Message);
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private sealed class AuthVerifyResponse
|
||||
|
||||
@@ -85,7 +85,11 @@ public class GameServer : ServerBase
|
||||
{
|
||||
AccTokenPacket accTokenPacket = Serializer.Deserialize<AccTokenPacket>(new ReadOnlyMemory<byte>(payload));
|
||||
string token = accTokenPacket.Token;
|
||||
long hashKey = UuidGeneratorManager.Instance.GetOrCreate(token);
|
||||
tokenHash.TryGetValue(token, out long hashKey);
|
||||
if (hashKey <= 1000)
|
||||
{
|
||||
hashKey = UuidGeneratorManager.Instance.Create();
|
||||
}
|
||||
|
||||
if (sessions.TryGetValue(hashKey, out NetPeer? existing))
|
||||
{
|
||||
@@ -102,6 +106,7 @@ public class GameServer : ServerBase
|
||||
if (username == null)
|
||||
{
|
||||
Log.Warning("[Server] 토큰 검증 실패 - 연결 거부 PeerId={Id}", peer.Id);
|
||||
UuidGeneratorManager.Instance.Release(hashKey);
|
||||
peer.Disconnect();
|
||||
return;
|
||||
}
|
||||
@@ -110,17 +115,11 @@ public class GameServer : ServerBase
|
||||
}
|
||||
|
||||
peer.Tag = new Session(hashKey, peer);
|
||||
((Session)peer.Tag).Token = token;
|
||||
sessions[hashKey] = peer;
|
||||
tokenHash[token] = hashKey;
|
||||
pendingPeers.Remove(peer.Id);
|
||||
|
||||
if (hashKey <= 1000)
|
||||
{
|
||||
// 더미 클라이언트면 에러
|
||||
Log.Error("[Server] Dummy 클라이언트가 입니다. 연결을 종료합니다. HashKey={Key} PeerId={Id}", hashKey, peer.Id);
|
||||
peer.Disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Information("[Server] 인증 완료 HashKey={Key} PeerId={Id}", hashKey, peer.Id);
|
||||
OnSessionConnected(peer, hashKey);
|
||||
}
|
||||
@@ -152,6 +151,7 @@ public class GameServer : ServerBase
|
||||
{
|
||||
Log.Information("[GameServer] 세션 해제 HashKey={Key} Reason={Reason}", hashKey, info.Reason);
|
||||
}
|
||||
UuidGeneratorManager.Instance.Release(hashKey);
|
||||
}
|
||||
|
||||
protected override void HandlePacket(NetPeer peer, long hashKey, ushort type, byte[] payload)
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace MMOserver.Utils;
|
||||
|
||||
public class UuidGeneratorManager : Singleton<UuidGeneratorManager>
|
||||
{
|
||||
private long counter = 0;
|
||||
private readonly ConcurrentDictionary<string, long> tokenMap = new();
|
||||
// 0 ~ 1000 은 더미 클라이언트 예약 범위
|
||||
private const long DUMMY_RANGE_MAX = 1000;
|
||||
|
||||
// string token → 고유 long ID 변환 (멀티스레드 안전)
|
||||
// 동일 token은 항상 같은 ID 반환
|
||||
public long GetOrCreate(string token)
|
||||
private readonly object idLock = new();
|
||||
private readonly HashSet<long> usedIds = new();
|
||||
|
||||
// 고유 랜덤 long ID 발급 (1001번 이상, 충돌 시 재생성)
|
||||
public long Create()
|
||||
{
|
||||
return tokenMap.GetOrAdd(token, _ => Interlocked.Increment(ref counter));
|
||||
lock (idLock)
|
||||
{
|
||||
long id;
|
||||
do
|
||||
{
|
||||
id = Random.Shared.NextInt64(DUMMY_RANGE_MAX + 1, long.MaxValue);
|
||||
} while (usedIds.Contains(id));
|
||||
|
||||
usedIds.Add(id);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGet(string token, out long id)
|
||||
// 로그아웃 / 세션 만료 시 ID 반납
|
||||
public bool Release(long id)
|
||||
{
|
||||
return tokenMap.TryGetValue(token, out id);
|
||||
lock (idLock)
|
||||
{
|
||||
return usedIds.Remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ public abstract class ServerBase : INetEventListener
|
||||
// peer → hashKey 역방향은 peer.Tag as Session 으로 대체
|
||||
protected readonly Dictionary<long, NetPeer> sessions = new();
|
||||
|
||||
// Token / HashKey 관리
|
||||
protected readonly Dictionary<string, long> tokenHash = new();
|
||||
|
||||
// 재사용 NetDataWriter (단일 스레드 폴링이므로 안전)
|
||||
private readonly NetDataWriter cachedWriter = new();
|
||||
|
||||
@@ -123,6 +126,12 @@ public abstract class ServerBase : INetEventListener
|
||||
// (재연결로 이미 교체된 경우엔 건드리지 않음)
|
||||
if (sessions.TryGetValue(session.HashKey, out NetPeer? current) && current.Id == peer.Id)
|
||||
{
|
||||
// 더미 클라 아니면 token관리
|
||||
if (!string.IsNullOrEmpty(session.Token))
|
||||
{
|
||||
tokenHash.Remove(session.Token);
|
||||
}
|
||||
|
||||
sessions.Remove(session.HashKey);
|
||||
Log.Information("[Server] 세션 해제 HashKey={Key} Reason={Reason}", session.HashKey, disconnectInfo.Reason);
|
||||
OnSessionDisconnected(peer, session.HashKey, disconnectInfo);
|
||||
|
||||
@@ -4,12 +4,28 @@ namespace ServerLib.Service;
|
||||
|
||||
public class Session
|
||||
{
|
||||
public long HashKey { get; init; }
|
||||
public NetPeer Peer { get; set; }
|
||||
public string? Token
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public long HashKey
|
||||
{
|
||||
get;
|
||||
init;
|
||||
}
|
||||
|
||||
public NetPeer Peer
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Session(long hashKey, NetPeer peer)
|
||||
{
|
||||
HashKey = hashKey;
|
||||
Peer = peer;
|
||||
Peer = peer;
|
||||
Token = null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user