159 lines
3.8 KiB
C#
159 lines
3.8 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Diagnostics;
|
|
using System.Text;
|
|
using ClientTester.Packet;
|
|
using LiteNetLib;
|
|
using LiteNetLib.Utils;
|
|
using Serilog;
|
|
|
|
namespace ClientTester.EchoDummyService;
|
|
|
|
public class EchoDummyClients
|
|
{
|
|
private NetManager manager;
|
|
private EventBasedNetListener listener;
|
|
private NetDataWriter? writer;
|
|
public NetPeer? peer;
|
|
public int clientId;
|
|
|
|
// seq → 송신 타임스탬프 (Stopwatch tick)
|
|
private ConcurrentDictionary<int, long> pendingPings = new();
|
|
private int seqNumber;
|
|
private const int MAX_PENDING_PINGS = 1000;
|
|
|
|
// 유닛 테스트용 (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 EchoDummyClients(int 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);
|
|
};
|
|
|
|
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 SendPing()
|
|
{
|
|
if (peer == null || writer == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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 _);
|
|
}
|
|
}
|
|
}
|
|
|
|
// string → raw bytes (길이 prefix 방지)
|
|
EchoPacket echoPacket = new EchoPacket();
|
|
echoPacket.Str = $"Echo seq:{seq}";
|
|
|
|
byte[] data = PacketSerializer.Serialize<EchoPacket>((ushort)PacketCode.ECHO, echoPacket);
|
|
writer.Put(data);
|
|
// 순서보장 안함 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();
|
|
}
|
|
}
|