현재 클라에서는 하나의 유저만 접속하고 있는 상황이라, 이를 다수의 유저들이 접속하는 상황으로 변경해줄 것이다.
서버 쪽과 동일하게 여기도 Manager를 만들어 대신 제작하여 넘겨주는 방식을 사용하자
서버에서 작업했던 것과 유사하게 싱글톤 형식으로 만들어 주고, Generate()
함수로 만든 Session을 list로 관리한다.
Connector 영역에서도 int count
매개 변수를 추가하여 count의 숫자 만큼 다수의 Session이 연결될 수 있는 환경을 만들어 준다.
이제 다시 Program으로 돌아와서 원하는 유저의 수 만큼 숫자를 넣어주면, 해당 숫자만큼 클라이언트가 생성된다.
그동안 코드 정리가 안되어있던 이 부분을 정리 해주면 된다. PacketSession을 상속 받게 하고, PacketManager.instance.OnRecvPacket()
까지 변경해주면 코드가 깔끔하게 정리 된다.
다시 SessionManager로 돌아와서 SendForEach()
함수를 통해서 다른 Session들에게 패킷을 전송해준다.
여기서 헷갈릴 수 있는 포인트
지난 시간BroadCast()
라는 함수의 역할도 자신에게 속해 있는 session들에게 packet을 보내주고 있었다.
잘 생각해보면SendForEach()
는 클라 -> 서버로 패킷을 전송하고 있고, 서버에서는 패킷을 받아BroadCast()
를 통해서 해당 Room에 있는 Session들에게 패킷을 뿌려주는 역할을 하고 있는 것이다.
패킷 별로 처리하기 위해 만들어둔 PacketHanlder를 수정 해준다.
잠깐 서버 쪽으로 돌아가서, 기존에 Server의 Main 영역이 시작할 때 Register 했던 부분을 자동으로 처음에 생성하도록 변경해줄 것이다.
처음에 바로 new 를 통해 인스턴스 해주고, 생성자를 통해서 생성되면 Register 하는 구조로 자동화 해준다.
이 과정을 바꿔주려면 PacketFormat에서 변경해주어야한다. PacketFormat으로 이동하여 내용을 변경해주고 GenPackets.bat
파일을 다시 실행시켜준다.
이제 GameRoom 쪽에서 받아온 chat 내용에 어떤 session인지 파악하기 위해 아래와 같이 변경 해주고 테스트를 해본다.
packet.chat = $"{chat} I am {packet.playerId}";
테스트를 해보면 10번 씩 출력을 하고 있는걸 볼 수 있다. 작성한 코드가 foreach를 돌면서 게임룸 안에 있는 유저들에게 10번씩 뿌려주는건데,
이런 방식으로 MMO 게임에 대입하면 굉장히 복잡해질 수 있다. 유저의 수가 증가할 수록 패킷을 뿌리는 양이 계속 증가하며 속도는 느려질 것이니 패킷들을 어떻게 처리할지 관리하는 방법이 쉽지 않다.
이제 서버에 접속하는 클라이언트 수를 100으로 늘리고, BroadCast 하는 부분에 디버그를 잡아보면 엄청 많은 스레드들이 생겼으며
이 스레드들이 모두 lock으로 인해 대기를 하고 있는 걸 볼 수 있다. 그럴 수 밖에 없는게 Thread.Sleep(250)
으로 0.25초에 한 번동작하게 해놨는데
100명의 유저라면 100 * 100 = 10,000이 동작하고 있다. 이는 즉, 1초에 40,000이 동작하고 있는 것이기에 무리가 될 수 밖에 없는 상황이다.
시간의 복잡도에서 예시를 들면 뿌려주는 양이 급격하게 상승하며 속도가 늦어지는 O(n^2) 방식이다.
이와 같이 작업이 엄청 많아서 점점 밀리다보니 쓰레드 관리하는 입장에서는 쓰레드를 보냈는데 일 처리가 되지 않으니 다시 보내고 있는 상황이라 쓰레드가 많이 추가 된 상황이다.
결국 문제점은 Recv를 하자마자 lock을 잡고 패킷을 보내는 것이다.
이를 해결하기 위해서는 다른 쓰레드들은 일감을 Queue에 담아두고, 하나의 쓰레드만 해당 일감을 처리하도록 하는 것이다.
이는 Job이라고도 하고 Task라고 하기도 한다. 중요한 포인트는 패킷을 저장 하고, 하나의 쓰레드에서 처리하는 것이다.
-> 이는 다음에 처리할 것이다.