216 lines
5.7 KiB
C#
216 lines
5.7 KiB
C#
using System.Collections.Concurrent;
|
||
using System.Diagnostics;
|
||
using ClientTester.Packet;
|
||
using LiteNetLib;
|
||
using LiteNetLib.Utils;
|
||
using Serilog;
|
||
|
||
namespace ClientTester.DummyService;
|
||
|
||
public class DummyClients
|
||
{
|
||
private NetManager manager;
|
||
private EventBasedNetListener listener;
|
||
private NetDataWriter? writer;
|
||
public NetPeer? peer;
|
||
public long clientId;
|
||
|
||
// seq → 송신 타임스탬프 (Stopwatch tick)
|
||
private ConcurrentDictionary<int, long> pendingPings = new();
|
||
private int seqNumber;
|
||
private const int MAX_PENDING_PINGS = 1000;
|
||
|
||
// info
|
||
private Vector3 position = new Vector3();
|
||
private int rotY = 0;
|
||
private float moveSpeed = 3.5f;
|
||
private float distance = 0.0f;
|
||
private float preTime = 0.0f;
|
||
|
||
// 이동 계산용
|
||
private static readonly Random random = new();
|
||
private readonly Stopwatch moveClock = Stopwatch.StartNew();
|
||
private float posX = 0f;
|
||
private float posZ = 0f;
|
||
private float dirX = 0f;
|
||
private float dirZ = 0f;
|
||
|
||
// 유닛 테스트용 (0 = 제한 없음)
|
||
public int TestCount
|
||
{
|
||
get;
|
||
set;
|
||
} = 0;
|
||
|
||
// 통계
|
||
public int SentCount
|
||
{
|
||
set;
|
||
get;
|
||
}
|
||
|
||
public int ReceivedCount
|
||
{
|
||
set;
|
||
get;
|
||
}
|
||
|
||
public double LastRttMs
|
||
{
|
||
set;
|
||
get;
|
||
}
|
||
|
||
public double TotalRttMs
|
||
{
|
||
set;
|
||
get;
|
||
}
|
||
|
||
public int RttCount
|
||
{
|
||
set;
|
||
get;
|
||
}
|
||
|
||
public DummyClients(long clientId, string ip, int port, string key)
|
||
{
|
||
this.clientId = clientId;
|
||
listener = new EventBasedNetListener();
|
||
manager = new NetManager(listener);
|
||
writer = new NetDataWriter();
|
||
|
||
listener.PeerConnectedEvent += netPeer =>
|
||
{
|
||
peer = netPeer;
|
||
Log.Information("[Client {ClientId:00}] 연결됨", this.clientId);
|
||
|
||
// clientID가 토큰의 hashKey라고 가정함
|
||
PacketHeader packetHeader = new PacketHeader();
|
||
packetHeader.Code = (PacketCode)1;
|
||
packetHeader.BodyLength = 4 + sizeof(long);
|
||
writer.Put(clientId);
|
||
peer.Send(writer, DeliveryMethod.ReliableOrdered);
|
||
|
||
// 초기화
|
||
};
|
||
|
||
listener.NetworkReceiveEvent += (peer, reader, channel, deliveryMethod) =>
|
||
{
|
||
short code = reader.GetShort();
|
||
short bodyLength = reader.GetShort();
|
||
string? msg = reader.GetString();
|
||
long sentTick;
|
||
|
||
if (msg != null && msg.StartsWith("Echo seq:") &&
|
||
int.TryParse(msg.Substring("Echo seq:".Length), out int seq) &&
|
||
pendingPings.TryRemove(seq, out sentTick))
|
||
{
|
||
double rttMs = (Stopwatch.GetTimestamp() - sentTick) * 1000.0 / Stopwatch.Frequency;
|
||
LastRttMs = rttMs;
|
||
TotalRttMs += rttMs;
|
||
RttCount++;
|
||
}
|
||
|
||
ReceivedCount++;
|
||
|
||
if (TestCount > 0 && ReceivedCount >= TestCount)
|
||
{
|
||
peer.Disconnect();
|
||
}
|
||
|
||
reader.Recycle();
|
||
};
|
||
|
||
listener.PeerDisconnectedEvent += (peer, info) =>
|
||
{
|
||
Log.Warning("[Client {ClientId:00}] 연결 끊김: {Reason}", this.clientId, info.Reason);
|
||
this.peer = null;
|
||
};
|
||
|
||
manager.Start();
|
||
manager.Connect(ip, port, key);
|
||
}
|
||
|
||
public void UpdateDummy()
|
||
{
|
||
// 델타 타임 계산 (초 단위)
|
||
float now = (float)moveClock.Elapsed.TotalSeconds;
|
||
float delta = preTime > 0f ? now - preTime : 0.1f;
|
||
preTime = now;
|
||
|
||
// 남은 거리가 없으면 새 방향·목표 거리 설정
|
||
if (distance <= 0f)
|
||
{
|
||
// 현재 각도에서 -30~+30도 범위로 회전
|
||
rotY = (rotY + random.Next(-30, 31) + 360) % 360;
|
||
float rad = rotY * MathF.PI / 180f;
|
||
dirX = MathF.Sin(rad);
|
||
dirZ = MathF.Cos(rad);
|
||
|
||
// 3초~12초에 도달할 수 있는 거리 = moveSpeed × 랜덤 초
|
||
float seconds = 3f + (float)random.NextDouble() * 9f;
|
||
distance = moveSpeed * seconds;
|
||
}
|
||
|
||
// 이번 틱 이동량 (남은 거리 초과 방지)
|
||
float step = MathF.Min(moveSpeed * delta, distance);
|
||
posX += dirX * step;
|
||
posZ += dirZ * step;
|
||
distance -= step;
|
||
|
||
// 정수 Vector3 갱신
|
||
position.X = (int)MathF.Round(posX);
|
||
position.Z = (int)MathF.Round(posZ);
|
||
}
|
||
|
||
public void SendTransform()
|
||
{
|
||
if (peer == null || writer == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
UpdateDummy();
|
||
|
||
int seq = seqNumber++;
|
||
pendingPings[seq] = Stopwatch.GetTimestamp();
|
||
|
||
// 응답 없는 오래된 ping 정리 (패킷 유실 시 메모리 누수 방지)
|
||
if (pendingPings.Count > MAX_PENDING_PINGS)
|
||
{
|
||
int cutoff = seq - MAX_PENDING_PINGS;
|
||
foreach (int key in pendingPings.Keys)
|
||
{
|
||
if (key < cutoff)
|
||
{
|
||
pendingPings.TryRemove(key, out _);
|
||
}
|
||
}
|
||
}
|
||
|
||
PacketHeader packetHeader = new PacketHeader();
|
||
packetHeader.Code = 0;
|
||
packetHeader.BodyLength = (ushort)$"Echo seq:{seq}".Length;
|
||
writer.Put((short)packetHeader.Code);
|
||
writer.Put((short)packetHeader.BodyLength);
|
||
writer.Put($"Echo seq:{seq}");
|
||
// 순서보장 안함 HOL Blocking 제거
|
||
peer.Send(writer, DeliveryMethod.ReliableUnordered);
|
||
SentCount++;
|
||
writer.Reset();
|
||
}
|
||
|
||
public double AvgRttMs => RttCount > 0 ? TotalRttMs / RttCount : 0.0;
|
||
|
||
public void PollEvents()
|
||
{
|
||
manager.PollEvents();
|
||
}
|
||
|
||
public void Stop()
|
||
{
|
||
manager.Stop();
|
||
}
|
||
}
|