본문 바로가기

- Programming/- C#

★ 13. c# 네트워크 개발 p12

반응형

==================================================================

모든 출처는

- 유니티 개발자를 위한 C#으로 온라인 게임 서버 만들기 - 저자 이석현, 출판사 한빛미디어

그리고 URL :

http://lab.gamecodi.com/board/zboard.php?id=GAMECODILAB_Lecture_series&no=81&z=

==================================================================


C#으로 게임 서버 만들기 - 10. 세균전 - 서버 구현 I

이제 서버를 구현할 차례입니다. 앞장에서 초기 설계와 일부 로직 부분을 작성해봤지만 세세한 부분까지는 살펴보지 못했습니다.
먼저 게임 서버와 게임 방의 클래스 사이의 관계를 살펴보고 유저의 메시지가 게임 방 객체로 전달되기 까지 어떤 과정을 거치는지
알아보도록 하겠습니다. 그 뒤 세균전 게임의 상세 로직을 작성해보도록 하겠습니다.
곧바로 게임 로직을 코딩하기보다 메시지가 처리되는 과정을 이해하는 것이 더 중요하다고 생각되었기 때문에 이런 순서로 구성하였습니다.
밑바탕에 깔려 있는 과정들도 어느정도 이해하고나면 다른 게임을 만들거나 타인의 소스 코드를 분석할 때도 큰 도움이 될 것입니다.

핵심 클래스들의 구성

위 그림은 세균전 게임 서버에서 굵직한 역할을 차지하는 클래스들을 나타낸 그림입니다.

제일 위에 Program 클래스는 게임 서버의 시작점이 되는 클래스입니다. 프로그램이 시작되면 제일 먼저

Program 클래스의 Main 메소드부터 수행됩니다.

Program 클래스는 CGameServer 객체를 멤버 변수로 갖고 있습니다. 이 객체가 하나의 게임 서버를 뜻합니다.

게임의 심장 부분을 맡고 있다고 할 수 있으며 각 게임 로직을 담당하는 객체들에게 메시지를 전달해주는 역할을 합니다.

마치 심장이 혈액을 온 몸으로 내보내 듯 CGameServer 클래스는 유저의 패킷을 게임 방으로 전달해줍니다.


CGameServer는 게임 방들을 관리하는 CBattleRoomManager 객체를 멤버로 갖고 있습니다. 게임 서버 하나에 수많은 게임 방들이

존재하는데 이 방들을 생성하고 삭제하는 역할을 CBattleRoomManager 객체가 담당합니다.

CBattleRoomManager에는 당연히 CBattleRoom 객체가 존재하며 게임 로직에 관련된 부분은 모두 CBattleRoom 클래스에 담겨 있습니다.


CBattleRoom 클래스는 크게 두 가지 중요한 요소가 있습니다. 하나는 플레이어 객체이며 다른 하나는 게임 로직입니다.
게임 로직은 CBattleRoom 클래스 전반에 걸쳐서 작성되어 있으며 서버에서 처리된 내용들을 플레이어들에게 전달해주고
클라이언트의 입력을 받아들여 게임 로직에 반영하는 코드들이 들어갑니다.
게임 서버 입장에서 볼 때 유저들은 단순히 소켓과 매칭된 객체일 뿐이지만 게임 방 안에 그룹을 지어주면 하나의 방 안에
들어와 있는 플레이어들로 여겨집니다. 이 때부터 방 안에 있는 플레이어들과 서로 실시간으로 정보를 주고 받으며
게임을 플레이 할 수 있게 되는 것입니다.

게임 패킷의 전달 과정
클라이언트가 네트워크를 통해 게임 서버에 데이터를 전송하면 게임 서버 객체는 게임 방으로 다시 그 데이터를 전송해줍니다.
그리고 게임 방은 게임 서버 객체로부터 전달 받은 데이터를 분석하여 유저의 요청에 따라 게임 로직을 처리하게 됩니다.
이러한 과정들을 좀 더 상세히 살펴보도록 하겠습니다.

클라이언트에서 보내온 메시지가 CBattleRoom 객체까지 전달되는 과정을 나타낸 그림입니다. 클라이언트가 메시지를 전송하면

네트워크 선로를 통해 게임 서버가 존재하는 머신으로 도착하게 됩니다. 게임 서버에서 사용하는 포트를 통해 서버 어플리케이션으로

데이터가 도착하면 우리가 첫 부분에서 구축했던 네트워크 라이브러리 모듈을 통해 CUserToken 객체로 클라이언트의 데이터가 전달됩니다.
CUserToken 객체는 소켓에 관련된 정보들로 구성되어 있어 게임과는 관련이 적은 비교적 순수한 객체이기 때문에
좀 더 게임성을 띄는 CGameUser 객체로 데이터를 넘겨 주게 됩니다.

CGameUser 객체는 이 데이터를 게임에서 사용할 수 있는 패킷으로 만들어 처리합니다.
네트워크 소켓을 통해 받은 데이터는 순수한 바이트 덩어리들로 이루어져 있는데
이 바이트들을 게임에서 사용할 수 있도록 가공하여 잘 포장해 놓은 것이 게임 패킷 객체라고 할 수 있습니다.
우리가 만든 게임 서버에서는 CPacket 클래스로 표현되며 여기에는 프로토콜 번호와 클라이언트에서 보내온 정보들이 들어있습니다.

CGameUser 객체는 이 패킷을 CGameServer 객체의 메시지 큐로 밀어 넣습니다. 그리고 다시 CGameUser 객체의 메시지 처리 메소드를
수행하여 해당 유저가 들어가 있는 게임 방 객체로 패킷을 전달해 줍니다. 이 게임방 객체에서는 어떤 유저의 요청인지 구분하여
게임 로직을 처리한 뒤 게임 방 내의 유저들에게 해당 내용들을 전달해 주기도 합니다.
하나의 요청에 대한 로직 처리가 종료되면 다시 CGameServer 객체의 메시지 큐를 검사하여 또 다른 요청이 있는지 확인한 뒤
위와 같은 과정을 반복합니다. 각 패킷에는 어떤 유저가 보내온 것인지 기록되어 있기 때문에 해당 유저가 존재하는
게임 방까지 정확하게 패킷을 전달할 수 있는 것입니다.

CGameServer의 메시지 큐 처리
CGameUser 객체에서 만든 패킷을 CGameServer 객체의 메시지 큐로 밀어 준다고 말씀 드렸습니다.
패킷을 받은 즉시 처리하지 않고 메시지 큐에 넣어서 처리하는 이유는 무엇일까요?
CGameUser 객체는 자신이 속해있는 게임 방 객체를 알고 있기 때문에 굳이 CGameServer 객체를 거치지 않고
곧바로 게임 방 객체로 접근할 수 있음에도 말이죠.
물론 직접 게임 방 객체로 전달하여 처리하는 것도 아무 문제가 없습니다. 단, 스레드 간의 동기화 처리만 정확히 해준다면 말이죠.

클라이언트가 보내온 메시지가 게임 방까지 전달되는 과정을 다시 한번 살펴보겠습니다.

위 그림에서 CUserToken - CGameUser까지 연결되는 과정은 워커 스레드에서 처리되는 부분입니다.

.Net 프레임 워크의 비동기 소켓 매커니즘을 이용하면 자동으로 처리되는 과정이죠. 이 스레드는 몇 개가 될지 모르지만

여러 개의 스레드에서 동시에 처리될 수 있다는 것은 알 수 있습니다.

게임 방 객체는 여러 유저들이 공유하는 객체이기 때문에 CGameUser 객체에서 데이터를 받은 즉시 게임 방에 접근해버리면

여러 개의 스레드에서 동시에 게임 방 객체를 건드리는 셈이 되는 것입니다.

이렇게 처리할 경우에는 스레드 간의 동기화 처리를 섬세하게 해줘야합니다. 게임 방 객체에서 게임 로직을 처리할 때마다

lock으로 보호해주어 스레드의 접근을 제어해주어야겠죠.

게이미 방에 속해 있는 메소드 하나 하나에 모두 저런 lock 처리를 두게 된다면 코딩하기도 귀찮아질 뿐만 아니라

실수로 빠뜨리는 경우에는 어느 순간 데이터가 꼬이거나 서버가 죽는 경우가 생기게 될지도 모릅니다.


그렇다면 아예 로직 처리를 단일 스레드로 처리하면 어떨까요?

멀티 코어가 대세인 요즘에 무슨 소리냐면 펄쩍 뛰는 분들이 계실지 모르겠지만 멀티 스레드에서 발생되는 버그를 경험해본

분들이라면 아마 고개를 끄덕이고 계실겁니다.

위 그림을 보면 워커 스레드에서 처리된 패킷들이 CGameServer의 메시지 큐에 차곡 차곡 들어가는 모습을 볼 수 있습니다.

이 패킷들을 큐에 넣는 작업은 각각의 워커 스레드에서 처리되며 큐에 담긴 패킷을 빼내어 게임 방까지 전달하는 부분은

CGameServer 객체의 로직 스레드에서 처리됩니다. 여기서도 최소한 두 개 이상의 스레드가 하나의 큐에 접근하게 되기 때문에

큐에 넣고 빼오는 부분은 lock으로 보호해주어야 합니다.

이렇게 큐에 들어간 패킷은 로직 스레드에서 하나씩 꺼내와 게임방까지 전달해주게 되며 게임 방 객체에서는 하나의 스레드만

작업하고 있으므로 스레드간 동기화에 신경쓰지 않고 코딩할 수 있습니다.

이런 기법을 사용하는 것은 멀티 코어를 활용하는데 아쉬움이 있을 수 있겠지만 유저들끼리 서로를 참조하며 진행되는

게임 로직을 구현하는데 아주 편리하기 때문에 실무에서도 사용되는 구성입니다.

또한 이 예제에서는 등장하지 않지만 실제 상용 게임 서버에는 유저들의 게임 로직 뿐만 아니라 인공지능, 뎅터베이스 처리 등

시간이 오래 걸리는 작업들이 많이 존재하기 때문에 로직 처리를 단일 스레드로 돌리는 것이 효율적이지 않다라고만 볼 수는 없을 것입니다.


이번 장에서는 클라이언트에서 보내온 메시지들이 게임 방까지 전달 되는 과정에 대해서 알아보았습니다.

게임 로직이 처리되기까지 어떤 과정들을 거치는지 이해 한다면 개발 도중 버그가 발생했을 때 좀 더 쉽게 해결 방법에

접근할 수 있게 될겁니다.

다음 시간에는 게임 로직의 세부 구현에 대해서 작성해보겠습니다.

감사합니다.




반응형

'- Programming > - C#' 카테고리의 다른 글

★ 15. c# ref와 out의 차이  (0) 2017.02.16
★ 14. c# 네트워크 개발 p13  (0) 2017.02.16
★ 12. c# 네트워크 개발 p11  (0) 2017.02.16
★ 11. c# 네트워크 개발 p10  (0) 2017.02.15
★ 10. c# 네트워크 개발 p9  (0) 2017.02.14