feat : 스트레스 테스트 기능 추가 / 패킷 처리량 제한 / 프로젝트 상황 리드미 추가
This commit is contained in:
203
ClientTester/EchoClientTester/StressTest/StressTestClient.cs
Normal file
203
ClientTester/EchoClientTester/StressTest/StressTestClient.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using ClientTester.DummyService;
|
||||
using ClientTester.Packet;
|
||||
using LiteNetLib;
|
||||
using LiteNetLib.Utils;
|
||||
using Serilog;
|
||||
|
||||
namespace ClientTester.StressTest;
|
||||
|
||||
/// <summary>
|
||||
/// 스트레스 테스트용 클라이언트.
|
||||
/// Echo RTT 측정 + 이동 패킷 전송을 동시에 수행.
|
||||
/// </summary>
|
||||
public class StressTestClient
|
||||
{
|
||||
private NetManager manager;
|
||||
private EventBasedNetListener listener;
|
||||
private NetDataWriter? writer;
|
||||
public NetPeer? peer;
|
||||
public long clientId;
|
||||
|
||||
// 이동
|
||||
private Vector3 position = new Vector3();
|
||||
private int rotY;
|
||||
private float moveSpeed = 3.5f;
|
||||
private float distance;
|
||||
private float preTime;
|
||||
private readonly Stopwatch moveClock = Stopwatch.StartNew();
|
||||
private float posX, posZ, dirX, dirZ;
|
||||
public MapBounds Map { get; set; } = new MapBounds(-50f, 50f, -50f, 50f);
|
||||
|
||||
// RTT 측정
|
||||
private readonly ConcurrentDictionary<int, long> pendingPings = new();
|
||||
private int seqNumber;
|
||||
private const int MAX_PENDING = 500;
|
||||
|
||||
/// <summary>개별 RTT 기록 (퍼센타일 계산용)</summary>
|
||||
public ConcurrentBag<double> RttSamples { get; } = new();
|
||||
|
||||
// 통계
|
||||
public int SentCount { get; set; }
|
||||
public int ReceivedCount { get; set; }
|
||||
public int RttCount { get; set; }
|
||||
public double TotalRttMs { get; set; }
|
||||
public double LastRttMs { get; set; }
|
||||
public double AvgRttMs => RttCount > 0 ? TotalRttMs / RttCount : 0;
|
||||
public bool IsConnected => peer != null;
|
||||
|
||||
public StressTestClient(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.Debug("[Stress {Id:000}] 연결됨", clientId);
|
||||
|
||||
DummyAccTokenPacket token = new DummyAccTokenPacket { Token = clientId };
|
||||
byte[] data = PacketSerializer.Serialize((ushort)PacketCode.DUMMY_ACC_TOKEN, token);
|
||||
writer!.Put(data);
|
||||
peer.Send(writer, DeliveryMethod.ReliableOrdered);
|
||||
writer.Reset();
|
||||
};
|
||||
|
||||
listener.NetworkReceiveEvent += (p, reader, ch, dm) =>
|
||||
{
|
||||
ReceivedCount++;
|
||||
|
||||
try
|
||||
{
|
||||
byte[] raw = reader.GetRemainingBytes();
|
||||
if (raw.Length >= 4)
|
||||
{
|
||||
ushort type = BitConverter.ToUInt16(raw, 0);
|
||||
ushort size = BitConverter.ToUInt16(raw, 2);
|
||||
|
||||
if (type == (ushort)PacketCode.ECHO && size > 0 && raw.Length >= 4 + size)
|
||||
{
|
||||
byte[] payload = new byte[size];
|
||||
Array.Copy(raw, 4, payload, 0, size);
|
||||
EchoPacket echo = PacketSerializer.DeserializePayload<EchoPacket>(payload);
|
||||
|
||||
if (echo.Str.StartsWith("Echo seq:") &&
|
||||
int.TryParse(echo.Str.AsSpan("Echo seq:".Length), out int seq) &&
|
||||
pendingPings.TryRemove(seq, out long sentTick))
|
||||
{
|
||||
double rttMs = (Stopwatch.GetTimestamp() - sentTick) * 1000.0 / Stopwatch.Frequency;
|
||||
LastRttMs = rttMs;
|
||||
TotalRttMs += rttMs;
|
||||
RttCount++;
|
||||
RttSamples.Add(rttMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 파싱 실패는 무시 (다른 패킷 타입)
|
||||
}
|
||||
};
|
||||
|
||||
listener.PeerDisconnectedEvent += (p, info) =>
|
||||
{
|
||||
Log.Debug("[Stress {Id:000}] 끊김: {Reason}", clientId, info.Reason);
|
||||
peer = null;
|
||||
};
|
||||
|
||||
manager.Start();
|
||||
manager.Connect(ip, port, key);
|
||||
}
|
||||
|
||||
public void UpdateAndSendTransform()
|
||||
{
|
||||
if (peer == null || writer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 이동 업데이트
|
||||
float now = (float)moveClock.Elapsed.TotalSeconds;
|
||||
float delta = preTime > 0f ? now - preTime : 0.1f;
|
||||
preTime = now;
|
||||
|
||||
if (distance <= 0f)
|
||||
{
|
||||
int wallRotY = Map.GetRotYAwayFromWall(posX, posZ);
|
||||
rotY = wallRotY >= 0
|
||||
? (wallRotY + Random.Shared.Next(-30, 31) + 360) % 360
|
||||
: (rotY + Random.Shared.Next(-30, 31) + 360) % 360;
|
||||
float rad = rotY * MathF.PI / 180f;
|
||||
dirX = MathF.Sin(rad);
|
||||
dirZ = MathF.Cos(rad);
|
||||
distance = moveSpeed * (3f + (float)Random.Shared.NextDouble() * 9f);
|
||||
}
|
||||
|
||||
float step = MathF.Min(moveSpeed * delta, distance);
|
||||
float nextX = posX + dirX * step;
|
||||
float nextZ = posZ + dirZ * step;
|
||||
bool hitWall = Map.Clamp(ref nextX, ref nextZ);
|
||||
posX = nextX;
|
||||
posZ = nextZ;
|
||||
distance = hitWall ? 0f : distance - step;
|
||||
position.X = (int)MathF.Round(posX);
|
||||
position.Z = (int)MathF.Round(posZ);
|
||||
|
||||
// 전송
|
||||
TransformPlayerPacket pkt = new TransformPlayerPacket
|
||||
{
|
||||
PlayerId = (int)clientId,
|
||||
RotY = rotY,
|
||||
Position = new Packet.Vector3 { X = position.X, Y = 0, Z = position.Z }
|
||||
};
|
||||
byte[] data = PacketSerializer.Serialize((ushort)PacketCode.TRANSFORM_PLAYER, pkt);
|
||||
writer.Put(data);
|
||||
peer.Send(writer, DeliveryMethod.Unreliable);
|
||||
SentCount++;
|
||||
writer.Reset();
|
||||
}
|
||||
|
||||
public void SendPing()
|
||||
{
|
||||
if (peer == null || writer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int seq = seqNumber++;
|
||||
pendingPings[seq] = Stopwatch.GetTimestamp();
|
||||
|
||||
if (pendingPings.Count > MAX_PENDING)
|
||||
{
|
||||
int cutoff = seq - MAX_PENDING;
|
||||
foreach (int k in pendingPings.Keys)
|
||||
{
|
||||
if (k < cutoff)
|
||||
{
|
||||
pendingPings.TryRemove(k, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EchoPacket echo = new EchoPacket { Str = $"Echo seq:{seq}" };
|
||||
byte[] data = PacketSerializer.Serialize((ushort)PacketCode.ECHO, echo);
|
||||
writer.Put(data);
|
||||
peer.Send(writer, DeliveryMethod.ReliableUnordered);
|
||||
SentCount++;
|
||||
writer.Reset();
|
||||
}
|
||||
|
||||
public void PollEvents()
|
||||
{
|
||||
manager.PollEvents();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
manager.Stop();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user