현재 서버의 GameRoom에서는 유저가 채팅을 입력할 때마다 즉시 Broadcast()를 호출하여 개별 패킷을 클라이언트들에게 전송하는 방식으로 동작하고 있었다.
public void Broadcast(ClientSession session, string chat)
{
S_Chat packet = new S_Chat();
packet.playerId = session.SessionId;
packet.chat = $"{chat} I am {packet.playerId}";
ArraySegment<byte> segment = packet.Write();
foreach (ClientSession s in _session)
s.Send(segment);
}
✅ 즉시 전송 방식
foreach를 통해 모든 클라이언트에게 패킷을 즉시 전송 ❌ N² 복잡도 문제
Broadcast()가 클라이언트 수만큼 반복 실행되므로 O(N²)의 성능 저하 발생 ❌ 네트워크 오버헤드 증가
❌ CPU & 메모리 낭비
_pendingList에 저장 GameRoom 개선 (패킷 저장 & Flush 기능 추가)using System;
using System.Collections.Generic;
using System.Text;
using ServerCore;
namespace Server
{
class GameRoom
{
List<ClientSession> _session = new List<ClientSession>();
JobQueue _jobQueue = new JobQueue();
List<ArraySegment<byte>> _pendingList = new List<ArraySegment<byte>>();
public void Push(Action job)
{
_jobQueue.Push(job);
}
// 일정 시간마다 모아둔 패킷을 전송하는 Flush 함수
public void Flush()
{
foreach (ClientSession s in _session)
{
s.Send(_pendingList);
}
Console.WriteLine($"Flushed {_pendingList.Count} packets");
_pendingList.Clear();
}
// 패킷을 즉시 전송하지 않고 대기열(_pendingList)에 저장
public void Broadcast(ClientSession session, string chat)
{
S_Chat packet = new S_Chat();
packet.playerId = session.SessionId;
packet.chat = $"{chat} I am {packet.playerId}";
ArraySegment<byte> segment = packet.Write();
_pendingList.Add(segment);
}
public void Enter(ClientSession session)
{
_session.Add(session);
session.Room = this;
}
public void Leave(ClientSession session)
{
_session.Remove(session);
}
}
}
✅ 변경 사항
1. _pendingList(List<ArraySegment>): 패킷을 저장하는 리스트 추가
2. Broadcast():
_pendingList에 추가하여 모아두었다가 나중에 전송(O) _pendingList에 저장된 패킷을 클라이언트들에게 전송 _pendingList.Clear(); 를 통해 리스트 초기화 ServerProgram.cs에서 Flush() 실행 주기적으로 호출using System.Net;
using System.Threading;
using ServerCore;
namespace Server
{
class ServerProgram
{
static Listener _listener = new Listener();
public static GameRoom Room = new GameRoom();
static void Main(string[] args)
{
string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint endPoint = new IPEndPoint(ipAddr, 1111);
_listener.Init(endPoint, () => { return SessionManager.Instance.Generate(); });
Console.WriteLine("Listening...");
while (true)
{
// 일정 주기마다 모아둔 패킷을 전송 (0.25초마다)
Room.Push(() => Room.Flush());
Thread.Sleep(250);
}
}
}
}
✅ 주요 변경 사항
Flush() 실행 0.25초(250ms)마다 _pendingList에 저장된 패킷들을 모아 전송 Thread.Sleep(250); 를 사용하여 주기적으로 실행 서버에서 패킷을 모아서 보내는 구조로 변경되었으므로, 클라이언트에서 수신 버퍼 크기 증가 필요
→ 그렇지 않으면 일부 패킷이 손실될 가능성이 있음
RecvBuffer 버퍼 크기 증가public class RecvBuffer
{
byte[] _buffer;
int _readPos;
int _writePos;
public RecvBuffer(int size)
{
_buffer = new byte[65535]; // 최대 크기로 설정
}
}
SendBuffer 버퍼 크기 증가public class SendBufferHelper
{
public static ThreadLocal<SendBuffer> CurrentBuffer = new ThreadLocal<SendBuffer>(() => { return null; });
// 기존보다 큰 버퍼로 확장
public static int ChunkSize { get; set; } = 65535 * 100;
}
✅ 수정 이유
RecvBuffer 및 SendBuffer 크기를 늘려 대량의 패킷 수신 가능 100개의 패킷을 동시에 받을 수 있도록 설정 public sealed override int OnRecv(ArraySegment<byte> buffer)
{
int processLen = 0;
int packetCount = 0;
while (true)
{
if (buffer.Count < HeaderSize)
break;
ushort dataSize = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
if (buffer.Count < dataSize)
break;
OnRecvPacket(new ArraySegment<byte>(buffer.Array, buffer.Offset, dataSize));
packetCount++;
processLen += dataSize;
buffer = new ArraySegment<byte>(buffer.Array, buffer.Offset + dataSize, buffer.Count - dataSize);
}
if (packetCount > 1)
Console.WriteLine($"패킷 모아보내기: {packetCount}");
return processLen;
}
✅ 패킷 모아 보내기 정상 동작 확인
packetCount 값을 통해 한 번에 처리된 패킷 수를 로그 출력