JobQueue
우리가 이전에 만든 채팅 테스트 코드는 만약 100명의 유저가 있다고 해보면 100 * 100 번의 패킷이 서버로 전송이 되어 과부화를 일으키게된다. 그것을 관리하기 위해 클라이언트는 일감을 던져주기만 하고 서버는 그것을 Queue의 형태로 순차적으로 처리해주는 방식을 JobQueue라고 한다. 물론 실제 명칭은 아니다.
using System;
using System.Collections.Generic;
using System.Text;
namespace ServerCore
{
public interface IJobQueue
{
void Push(Action job);
}
public class JobQueue : IJobQueue
{
Queue<Action> _jobQueue = new Queue<Action>();
object _lock = new object();
bool _flush = false;
public void Push(Action job)
{
bool flush = false;
lock (_lock)
{
_jobQueue.Enqueue(job);
if (_flush == false)
flush = _flush = true;
}
if (flush)
Flush();
}
void Flush()
{
while (true)
{
Action action = Pop();
if (action == null)
{
_flush = false;
return;
}
action.Invoke();
}
}
Action Pop()
{
lock (_lock)
{
if (_jobQueue.Count == 0)
return null;
return _jobQueue.Dequeue();
}
}
}
}
그리고 모든 일감은(채팅) 모두 JobQueue에 등록을 하여 사용한다. 즉, Room에 있는 모든 함수들(입장, 전송, 퇴장) 전부다 JobQueue에서 관리를 한다. Push를 할때 Session에서 Send와 마찬가지로 Push 해줄때 자신이 처리할 차례라면 쌓여있는 Job을 전부 처리하는 과정을 거친다.
패킷 모아 보내기
그렇다면 JobQueue를 쓰면 n^2 번의 패킷 보내기는 해결이 된것일까?? 전혀 아니다. 이 것은 쓰레드가 일감만 던져주고 돌아가게끔하여 쓰레드풀에 영향을 덜 주기위한 기법인 것이지 채팅을 칠때마다 같은 방안에 있는 유저들에게 채팅내용을 보낸다는 것은 여전히 n^2번의 연산을 하게된다. 그렇기에 패킷을 한번에 모아서 처리를 하는 방법을 통해 연산을 N번으로 줄여야 한다.
GameRoom을 다음과 같이 수정한다.
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 Flush()
{
foreach (ClientSession s in _sessions)
{
s.Send(_pendingList);
}
Console.WriteLine($"Flushed {_pendingList.Count} Items");
_pendingList.Clear();
}
그리고 서버 메인스레드에서 0.25초 마다 Flush를 실행해주면 된다. 그리고 Session에서 Send가 List를 처리할 수 있게 처리한다.
JobTimer
우리가 위에서는 서버 메인스레드에서 0.25초 마다 Flush를 실행시켜주는데 만약 room 뿐만 아니라 여러가지 컨텐츠나 서버가 있을 때 0.25초를 쉬는 그런 행위를 메인스레드에서 할 수 없게 될 것이다.
그렇다면 예약 시스템이 있다면 정말 좋을 것 같다는 생각이 든다.
우선순위 큐를 이용하여 그 시스템을 구현해보자.
using System;
using System.Collections.Generic;
using System.Text;
using ServerCore;
namespace Server
{
struct JobTimerElem : IComparable<JobTimerElem>
{
public int execTick; // 실행 시간
public Action action;
public int CompareTo(JobTimerElem other)
{
return other.execTick - execTick;
}
}
class JobTimer
{
PriorityQueue<JobTimerElem> _pq = new PriorityQueue<JobTimerElem>();
object _lock = new object();
public static JobTimer Instance { get; } = new JobTimer();
public void Push(Action action, int tickAfter = 0)
{
JobTimerElem job;
job.execTick = System.Environment.TickCount + tickAfter;
job.action = action;
lock (_lock)
{
_pq.Push(job);
}
}
public void Flush()
{
while (true)
{
int now = System.Environment.TickCount;
JobTimerElem job;
lock (_lock)
{
if (_pq.Count == 0)
break;
job = _pq.Peek();
if (job.execTick > now)
break;
_pq.Pop();
}
job.action.Invoke();
}
}
}
}
'Unity > 온라인 RPG' 카테고리의 다른 글
[데이터베이스] SQL 입문 - 세팅 (0) | 2024.02.13 |
---|---|
[게임 서버] Unity와 서버 연동 (1) | 2024.02.06 |
[게임 서버] Job Queue - 채팅 테스트 (0) | 2024.02.01 |
[게임 서버] 패킷 직렬화 - Packet Generator (0) | 2024.01.30 |
[게임 서버] 패킷 직렬화 - Serialization (1) | 2024.01.24 |