MMO_TCPFighter

2022. 1. 17. 23:23게임서버/namespace univ_dev

320x100
안녕하세요 대학생 개발자입니다
이번글에서는

저번에 AsyncSelect 모델로 만들었었던 클라이언트의 서버를 만들어 볼 예정입니다. 

MMO식으로 바꿔서 약 7천명의 더미 봇들을 넣고 테스트를 해볼겁니다.

 

 

소켓 모델에 대해서 소개를 해야겠습니다.

MMO라고 하면 일반적으로 IOCP를 떠올리시겠지만... 

이번프로젝트는 여전히 싱글스레드 + select모델입니다. (왜냐... 아직 멀티스레드에 대한 언급을 안했기 때문에) iocp의 구현이 안됩니다.

그리고 select모델을 이용해도 7000명수용...뭐 큰 문제 없었습니다.

 

 

간단한 클라이언트 수정부분에 대해서만 설명드리고 본격적으로 서버에 대한 내용으로 넘어가겠습니다.

우선 클라이언트 기존의 경우에는 MO방식이었죠...

그래서 맵 크기도 640 480 이었습니다.

-그 결과 카메라는 한곳에 정적으로 박혀있어도 문제가 안되는 것이었고, 무조건 하나의 화면만 보고있기 때문에 따로 보여줘야할 캐릭터와 안보여줘야할 캐릭터를 구분할 필요가 없었습니다.

 

우선 맵크기를 6400 6400으로 늘려버렸습니다.

그러다 보니 카메라가 고정위치가 아니라 캐릭터의 위치에 맞춰서 이동, 즉 스크롤 되어야 됬습니다.

그리고 스크롤의 확인을 위해서 맵뒤를 요런 타일맵으로 도배해 버렸습니다.

그리고 저 타일 크기 하나는 64 64입니다. ㅋㅋㅋ 가로 세로 100개씩 있는거죠뭐...

 

네... 그리고 카메라가 고정위치가 아니라는 의미는 이제 좌표계가 나눠진다는 것입니다.

카메라에 들어오지 않는(시야에서 사라진) 다른 클라이언트들을 삭제하고 생성하는 부분이 추가로 들어가야 되므로(원래 있는 기능이긴했습니다만 빈도가 늘어났죠) 그부분도 수정이 된 것이구요..

클라이언트 입장에서는 게임 로직에 필요한 월드좌표와 화면 출력을 위해 필요한 스크린 좌표로 나눠지게 될겁니다.

뭐... 그정도 구분 짓는것 외에는 딱히 크게 달라진 부분은 없기때문에 스킵하도록 하고 중요한 부분인 서버로 넘어가도록 하겠습니다.

 

사실 서버도 큰 특이점은 없습니다.

이때까지 제가 만들었던 링버퍼 직렬화버퍼 오브젝트풀... 이런것들을 이용해서 만들었습니다.

거의 큰틀은 채팅서버와 유사합니다.

일단 큰 구조만 간단히 설명을 드린다면

while (!g_ShutDownFlag)
{
	NetworkIOProcess();
	Update(); // <- 채팅서버에서는 볼 수없었던 그...
	ControlServer();
	MonitorServerStatus();
}
NetworkCleanup();

요런식으로 되어있습니다.

 

보시듯 예전 채팅서버와 굵은 로직덩어리가 거의 비슷합니다.

NetworkIOProcess 함수의 경우에는 클라이언트 수 / 64 만큼 돌면서 select를 하고 FD_ISSET 되어있으면 프로시져를 실행하는 함수입니다.

움직이는 패킷이면 Update에서 처리할 수 있게 상태를 바꾸는 정도로 끝이 날것이고...

공격이라면 그자리에서 공격하라는 패킷을 보내고 누군가가 맞았다면 맞았다는 판단정도만 내려주면 될테니까요...

 

하지만 한가지 다른점 Update라는 로직이 드디어 서버에 들어가기 시작했습니다.

채팅서버의 경우에는 Update라는 로직이 필요가없죠...

왜냐면 들어왔던 데이터를 그자리에서 처리해 버릴 수 있었으니까요.

하지만 게임서버의 경우에는 그렇지 않은 경우도 있죠...

즉시 판단할 수 있는 로직이 존재하는가 하는 반면 업데이트를 통해서만 처리 할 수 있는 로직이 존재하기 때문입니다.

 

가령 대포가 날라가는 경우를 생각해보면 쏘자 말자 누가 맞는지 판단을 할 수 없습니다.

날라가는 도중에 그 대포를 피할수도 있을거고...

혹은 원래는 안맞는 궤적이었지만 움직여서 맞게된다던가...

혹은 중간에 장애물이 있어서 혼자서 터질수도 있다던가...

이런 로직들이 존재하게 된다면... 당연히 실제 대포를 쏴야 할것입니다.

그리고 업데이트 로직에서 매프레임 체크해야되겠죠.

장애물에는 걸렸는지 이번프레임에 땅에 떨어졌다면 그 폭발 범위안에 몇명이 있는지 그 순간 판단 할 수있게되는거죠.

뭐... 총알이나 그외 투사체, 혹은 몇초뒤 발생되는 공격등의 경우도 마찬가지겠지만 그냥 패킷 수신후 바로 처리해 버릴 수 없는 경우가 존재하겠죠...

즉 채팅서버의 방식인 받은 즉시 처리라는것이 이제 불가능하다는 겁니다.

일정 시간(혹은 조건) 이후에 처리할 수 있는 데이터가 나오기 때문이죠...

 

그럼 다시 본론으로 넘어가서 이 게임의 경우에는 다행히도 업데이트 로직에서 처리할 정보가 그리 많지가 않습니다.

히트 판정의 경우에는 그 자리에서 맞았는지 안맞았는지 판단할 수 있기 때문입니다.

따로 투사체가 존재하는 게임도 아니구요. 움직임에 대한 정보만을 Update함수에서 취급할겁니다. 

 

void Update(DWORD deltaTime)
{
    while(deltaTime >= 40)
    {
        for(...모든 플레이어에 대한 반복)
        {
            죽었는지 확인
            currentPlayer->Move();
            //혹은
            MovePlayer(currentPlayer);
        }
    }
}

와 같은 로직이 나오게 될겁니다.

 

자 그럼 이렇게 하면 서버 완성입니다~

모든 플레이어들이 잘 작동도 할 뿐더러 문제도 발생하지 않기 때문입니다

그럼 이번글은 여기서 마무리 하겠습니다

 

 

 

라고 할순없겠죠...

이렇게 서버를 완성했다고 끝을 내버리실거라면 사표를 준비하시면됩니다.

물론 최종 완성된 모습도 그닥 멋있는 모습은 아닙니다만... 지금 상황은 너무 말이 안되는거잖아요

0,0 에 있는 플레이어가 6300,6329에 있는 플레이어의 움직임을 굳이 체크할 필요가 있을까요..?

지금 까지 작성한 부분중에는 이부분이 생략되어있어요.

만약 7000명의 클라이언트가 들어와있다면 서버도 그렇고 클라이언트도 그렇고 터지고 말겁니다...

왜냐면 매번 7000명에 대한 정보를 받을테니까요 혹은 내가 움직이면 7000명에게 모두 다 정보를 보내야되니까요...

물론 단순 100명 정도 넣었을때는 잘돌아갑니다. (제가 최초 테스트 시에는 BroadCast로 해봤는데... 뭐 500명 내외까지는 쾌적하게 잘 돌아갔습니다.)

 

그럼 어떻게 하라는건데요??

그럼 이 상황에서 확실한건 서버에서 시야에 대한 처리를 해줘야 된다는 부분인데...

고민해야될 것이있죠... 시야를 어떻게 나눌것인가..?에 대한 고민입니다.

 

뭐... 대부분 이부분을 처음 접하면 가장 먼저 떠올리실수 있는게 당연한거겠지만 거리를 비교한다 라는 로직이 하나 나올 수 있겠네요...

이 경우 장점은 무엇이고 단점은 무엇일까요??

우선 장점이라고 하면... 구현이 매우 쉬울것 같네요 그냥 모든 클라이언트 다돌면서 거리 체크하면 되니까요.

그럼 단점이라면 위에서도 언급했듯이 모든 클라이언트들을 다 뺑뻉뺑 돌리면서 체크를 하는 방법이라는 점입니다.

클라이언트가 목표치인 7000명이라면... 7000제곱이 될테니까요 무려... 이런 로직은 세상에 존재해서도 안되는 로직입니다...

아마... 옛날 MMO류 게임들을 해보신 분들은 이런 경험이 있으실거에요.

나는 분명히 저사람이랑 동일한곳에 서있기를 희망하고 그 곳을 누르면... 아주 살짝 옆으로 가게되거나... 맵자체가 타일맵처럼 되어있고 한 칸에 한명만 들어갈 수 있는 경우를 많이 보셨을거에요

 

그때는 그 타일 한칸이 바로 그 케릭터 자체가 되는겁니다...

그리고 그 타일을 리스트로 만들어놓으면 한 칸의 타일위에 여러명의 플레이어가 설수있는거에요.

저는 이걸 좀더 크게 만들거고 이름을 섹터라고 부를겁니다.

그리고 이 섹터의 개념을 그대로 정확히 적용할겁니다.

void Update(DWORD deltaTime)
{
	while(deltaTime >= 40)
    {
    	for(...모든 플레이어에 대한 반복)
        {
        	죽었는지 확인
        	currentPlayer->Move();
            //혹은
            MovePlayer(currentPlayer);
            SectorUpdate(currentPlayer);
        }
    }
}

자 그럼 코드가 이런식으로 추가되게 됩니다...

뭐... 딱히 달라진건 없어보이지만... SectorUpdate라는 함수가 하는행동이 많으니까요...

 

우선 플레이어를 각 섹터별로 나눌것이구요...

그리고나서 그 섹터를 가지고 데이터를 누구에게 줄지를 결정할겁니다.

그래서 각 섹터위에 서있는 플레이어들을 담을 리스트를 하나 준비할겁니다.

list<Player*> g_Sector[SECTOR_Y_SIZE][SECTOR_X_SIZE] 입니다.

 

그럼 내가 있는 섹터에 플레이어들에게 내가 하는 행동을 전달하면 되겠거니... 라고 생각했지만

아차... 하는 문제가 있었습니다.

이런 경우를 봅시다. 분명 sector1에는 p1과 p2가 있지만 p2의 화면에는 p3가 보여야 정상입니다.

하지만 자신의 섹터에게만 보낸다고 한다면... 문제가 되는점이 바로 화면 안에 있어야되는 p3는 보이지 않게되고 반대로 필요없는 p1의 경우에는 보이게 된다는 겁니다.

p1의 경우에는 섹터의 크기를 줄이면 해결되는 부분이라고 하지만 p3는 나와야하잖아요??

그렇기때문에 나의 섹터와 내 주변 x,y축 +1 -1 섹터들에게도 브로드캐스팅할겁니다.

 

즉 한 이정도 구간쯤에 보내게 될겁니다. 이 안에서 일어나는 일들은 모두 내 시야로간주합니다 하지만 실제로 모니터에서 볼 수있는 시야는 저것보다 더 좁겠죠... 그래야 갑자기 어디선가 이상하게 뚱하고 튀어나오는 상황이 안생길테니까요...

 

그리고 섹터의 이동건도 동일합니다.

다음과 같이 이동을 한다고 하면 old sectors에는 내 케릭터를 delete하라는 메시지를 보내주면 될 것이고

반대로 cur sectors에는 내 케릭터를 create하라는 메시지를 보내고 동시에 나의 action이 있다면 그 액션을 취하는 패킷까지 보내주면 그만일겁니다.

 

이동한 플레이어에게 돌려주는 정보고 비슷합니다.

old sector에 있던 모든 플레이어에 대한 delete패킷을 보낼것이구요.

cur sector에 있는 모든 플레이어에 대한 create를 보내서 생성시킬겁니다.

그럼 섹터의 구현이 끝나게 되는것입니다.

 

뭐... 남은건 하나입니다.

섹터의 크기죠 섹터가 너무 작아버리면 화면보다 더 안쪽에서 플레이어 생성 삭제가 보이게 될거니까요 ㅋㅋㅋ...

그건안되겠죠?

 

어... 그리고 뭐... 딱히 큰건 없습니다.

나머지는 L7에서의 KeepAlive체크 정도밖에 남지 않은것 같습니다.

 

KeepAlive는 너무 단순해서 크게 말씀드릴것도 없습니다.

그냥 패킷이 올때마다 그 시간으로 초기화해서 update를 돌면서... 로직수행중에 마지막으로 패킷왔던 시간을 현재시간에서 빼서 그 값이 내가 지정한 시간보다 많으면 잘라버리는 거니까요...

추후에 클라이언트를 다시 고치게 될 일이있다면 heart beating도 넣을 예정입니다...

나중에 건드리게 될지 안될지는 모르겠다만요 히히

 

일단 이정도면... 아주 처음 만든 게임서버 치고 나쁘지 않았던 것 같습니다...

물론 더미가 5천명 이상씩 들어가있을땐 가끔 서버와 클라간 좌표 동기화가 제대로 안되서 싱크메시지가 날라가긴하지만... 가끔이니까요 뭐... 이정도면 봐줄만하다고 생각합니다. 

 

 

 

영상이 좀 길긴합니다만... 일단 주된 목적은 캐릭터 생성과 삭제가 섹터구분지어서 잘되는지와 동기화가 문제없이 되는지 그리고 히트 판정이 잘되는지와 섹터가 떨어져있는 상황에서 히트판정도 문제가 없는지에 대한 부분을 중점적으로 확인했습니다...

 

아 그리고 사실 제가 싱크가 많이 날까봐 쪽팔려서 안보여드리고 있었는데... 끝나고나서 보니까 분당 1개정도꼴로 나왔더라구요... 15분짜리영상인데 싱크가 16개였어요...

생각보다 많아서 놀랬습니다... ㅋㅋㅋ 새로 수정하는 지금은... 서버를 며칠을 켜놔도 전혀 문제없고 싱크도 나지 않는답니다...

 

후... 오늘도 긴 글 봐주셔서 정말 감사드립니다 다음부터는 좀 이론위주로 다시 넘어갈 것 같은데요... 시간나는대로 글 올리도록 하겠습니다. 그럼 안녕히계세요

 

{추가}

싱크를 완벽하게 잡았습니다 30분정도 러닝타임을 가지고 돌렸을때까지 싱크가 1도 발생하지 않았구요...

문제는 속도탓은 아니구요... 로직이 잘못짜져있었습니다 ^_^...

MoveCheck함수에 연산자 하나가 잘못들어가는 바람에 그랬습니다...

 

{2차 추가}

테스트 해본결과 약 7시간 러닝타임에도 싱크가 발생하지 않았습니다... 뭐 이정도면 문제없다고 생각합니다.

 

{3차 추가}

뭐... 하루종일 켜놔도 문제가 안생깁니다... 저런 단순한 게임에서 생긴다는건 지금생각해보니 말이 안되긴하네요

320x100

'게임서버 > namespace univ_dev' 카테고리의 다른 글

LockFree  (0) 2022.03.22
Network Library - LanServer,LanClient  (0) 2022.03.03
Chatting Server  (0) 2021.12.30
Red-Black tree delete  (2) 2021.12.23
Red-Black Tree (basic + insert)  (0) 2021.12.22