클라이언트가 서버로 접속하면 서버는 해당 세션을 특정 게임 룸에 입장 시킴
클라이언트가 서버로 메세지를 보내면 서버는 해당 게임 룸에 접속한 클라이언트 모두에게 해당 메세지를 보냄
클라이언트에게 메세지를 받자마자 서버에서 바로 게임 룸의 세션 목록 데이터에 락을 걸고
세션들에게 메세지를 뿌리는 경우 문제가 있을 수 있음
=> 하나의 게임 룸에 매우 많은 클라이언트가 있고 해당 클라이언트들이 모두 메세지를 보내고 있다면
하나의 메세지를 클라이언트들 모두에게 한번씩 보내는 것 자체가 오래걸리고,
락에 의해 다른 클라이언트의 메세지를 보내지 못하고 스레드가 대기하며 정체하게 됨
=> 스레드 병목 현상으로 스레드풀이 계속하여 새로운 스레드를 생성하고 프로그램의 메모리가 계속하여 늘어남
이를 막기 위해 커맨드 패턴을 이용한 job(task) queue를 이용
=> 클라이언트들에게 메세지를 받으면 바로 처리 하지 않고 필요한 정보와 해야할 일(함수)을 큐에 넣음
=> 락은 어차피 하나의 스레드 밖에 잡지 못하기 때문에 주기적으로 하나의 스레드가 락을 잡고 쌓인 일을 모두 처리
또한 메세지들을 효과적으로 보내기 위해 보내야할 패킷들을 쌓아두고 한번에 보냄
public void Push(Action job)
{
bool flush = false;
lock (_lock)
{
_jobQueue.Enqueue(job);
if (_flush == false)
{
_flush = true;
flush = true;
}
}
if (flush)
{
Flush();
}
}
void Flush()
{
while (true)
{
Action action = Pop();
if (action == null)
{
return;
}
action.Invoke();
}
}
Action Pop()
{
lock (_lock)
{
if (_jobQueue.Count == 0)
{
_flush = false;
return null;
}
return _jobQueue.Dequeue();
}
}
해야할 일들이 저장되는 큐와 해당 데이터를 담당하는 락으로 구성
외부에서는 오로지 push만 사용할 수 있고, 내부에서 pop하여 쌓인 일을 실행
=> 쌓인 일을 처리하기 위해 push가 필요
=> 원하는 네트워크 처리 주기를 정하여 해당 주기마다 push 실행
public void Broadcast(ClientSession session, string chat)
{
S_Chat packet = new S_Chat();
packet.playerID = session.SessionID;
packet.chat = $"{packet.playerID}: {chat}";
ArraySegment<byte> segment = packet.Write();
_pendingList.Add(segment);
}
public void Enter(ClientSession session)
{
_sessions.Add(session);
session.Room = this;
}
public void Leave(ClientSession session)
{
_sessions.Remove(session);
}
public void Push(Action job)
{
_jobQueue.Push(job);
}
public void Flush()
{
for (int i = 0; i < _sessions.Count; i++)
{
_sessions[i].Send(_pendingList);
}
_pendingList.Clear();
}
클라이언트들을 세션들을 관리하는 리스트와 보내야 하는 패킷들을 관리하는 리스트,
그리고 Job Queue로 구성
push 이외의 함수들은 직접적으로 호출하면 안되고 push를 사용하여 간접적으로 실행해야 함
public override void OnConnected(EndPoint endPoint)
{
Program.Room.Push(() => Program.Room.Enter(this));
}
public override void OnDisconnected(EndPoint endPoint)
{
SessionManager.Instance.Remove(this);
if (Room != null)
{
GameRoom room = Program.Room;
room.Push(() => room.Leave(this));
Room = null;
}
}
public static void C_ChatHandler(PacketSession session, IPacket packet)
{
C_Chat chatPacket = packet as C_Chat;
ClientSession clientSession = session as ClientSession;
if (clientSession.Room == null)
{
return;
}
GameRoom room = clientSession.Room;
clientSession.Room.Push(
() => room.Broadcast(clientSession, chatPacket.chat));
}
세션에 접속하고 끊을 때, 그리고 채팅을 하는 경우 위 코드와 같이 push를 이용하여 처리
static void FlushRoom()
{
Room.Push(() => Room.Flush());
JobTimer.Instance.Push(FlushRoom, 250);
}
네트워크가 주기적으로 처리될 수 있도록 일정 주기마다 flush 함수를 push하여 처리