Deterministic을 구현하기 위해서 Delay와 Rollback을 이용한 방법이 있다고 했었는데요. 이
번에는 실제 실무에서 사용하는 방법을 알아보도록 하겠습니다.
중계서버를 사용하는 방법
서버에 클라이언트들이 접속을 하는데 어떤 클라이언트가 패킷을 보내면 서버가 하는 일은 받은 패킷을 다른 클라이언트에게 전달하는 일만 합니다.
그래서 받아서 바로 전달하기 때문에 Relay서버라고 표현하기도 하고, 하나를 받았는데 모든 클라이언트에게 전달하기 때문에 중계서버(Broadcast Server) 라고도 합니다.
P1이 있고, P2가 있고 중계서버가 있다고 가정하면 P1이 어느 시점에서 버튼을 눌렀을 때 바로 적용시키는게 아니라 서버로 바로 보냅니다.
서버는 이 정보를 모든 클라이언트에게 보내줍니다. 내가 보낸 패킷도 내가 받을 수 도 있는데 내가 보낸 패킷이든 누가 보낸 패킷이든 서버에서 패킷이 왔을 때 그 정보를 기반으로 처리하는 방식입니다.
그래서 내가 버튼을 눌렀다해서 내가 처리하는게 아니라 이 정보는 무조건 서버로 가는 Input을 서버에 의존해서 처리하는 방식입니다.
그렇기 때문에 모든 클라이언트는 똑같은 입력값을 받게 되서 같은 Input Set을 갖게 됩니다.
서버가 하는 일은 되게 간단한데, 시간을 1초에 30개 또는 20개 정도로 쪼갭니다. 30개면 33ms이고 20개면 50ms입니다.
이 시간 간격으로 쪼개서 그 시간 동안 클라이언트들이 보낸 입력을 모읍니다.
그 후 모은것들을 한번에 그 시간의 간격이 끝날 때 보내 줍니다.
이것은 어떻게 보면 턴제와 같다고 보면 됩니다. 이 턴제를 1초에 30번 하기 때문에 턴제라고도 할 수 있지만 워낙 빨리 이루어지기 때문에 실시간으로 동작하는 것처럼 보이게 됩니다. 하지만 엄밀히 따지면 이것은 턴제와 같습니다.
이 방식은 구현하기 쉽고, Deterministic도 잘 할 수 있고, Desync가 잘 일어나지 않습니다.
서버가 똑같은 패킷, Input Set을 똑같은 사람들에게 나누어주기 때문에 Input이 같아 Desync가 잘 일어나지 않습니다.
그런데 이것도 문제가 있는데 P1, P2, 서버가 있다고 가정했을 때 P1이 어느 순간 버튼을 눌러서 이 패킷이 서버로 가고, 이 패킷이 일정 시간 동안 모으게 되고, 그 시간이 끝나면 발송을 하게 되는데
만약 P1이 0ms때 입력을 눌렀고, 그것이 서버에 전송되는데 100ms가 걸리고, 서버에서의 텀이 30ms가 걸린다고 하면 실제로 입력값이 처리되는 시간은 230ms이후에 처리가 됩니다.
저번에도 말했지만 200ms 이상이 되면 일반 사용자도 느낄 수 있게 됩니다.
그래서 이 방식에서는 딜레이가 길어지죠.
그 전에 했던 Delay와 Rollback방식을 보면 내가 입력하면 일정시간 Delay를 준다음에 처리 합니다. 그러니까 내가 입력한건 내가 처리하는 방식이고,
그 입력을 상대방에게 보내서 상대방이 처리하게 되는데 상대방도 입력하게 되면 나에게 보내고, 내가 받았을 때 처리하게 되는데 내가 받았을 때 상대방이 입력한 시점으로 시간을 뒤로 Rollback해서 그 입력을 포함시켜 같이 처리해서 다시 앞으로 가는 방식입니다.
그래서 Delay와 Rollback방식은 내가 먼저 처리할 수 있다는 장점이 생깁니다.
그래서 상대방은 즉각적인 반응을 하지 못하지만 내 캐릭터는 즉각적인 반응을 할 수 있습니다.
유저입장에서 봤을 때 내가 입력한 버튼을 눌렀을 때 즉각적인 반응이 와야 반응이 좋다고 느끼는데 아까 말했던 중계서버를 이용한 방법은 실제 패킷이 갔다가 돌아와야 처리되기 때문에 내가 버튼을 눌렀어도 즉각적인 반응이 오는게 아니라 일정시간 이후에 반응이 되서 더 Delay가 생기게 됩니다.
그래서 정리해보면 Rollback방식은 반응성이 좋지만 Desync가 일어날 가능성이 높고, 복잡성이 높습니다.
하지만 중계서버는 반응성은 느리지만 Desync가 일어날 가능성이 낮고 복잡성도 낮습니다.
그래서 반응성이 높아야 하는 게임들은 Rollback 방식을 많이 사용하고, 중계서버를 이용하는 방식은 그 외 것들 AOS, 스포츠게임, 등에 사용합니다.
Rollback방식 | 중계서버 |
---|---|
Desync가 일어날 가능성이 높다. | Desync가 일어날 가능성이 낮다. |
복잡성이 높다. | 복잡성이 낮다. |
FPS, 격투게임, 등 반응성이 빨라야 하는 게임. | AOS, 스포츠게임, 등 반응성이 조금 느려도 되는 게임. |
실무에서 사용하는 것 하나 더 알아보자면 기본적으로 Rollback방식인데 P1이 일정시간에 버튼을 눌렀고, 일정시간 딜레이를 주고, 이걸 처리하게 됩니다.
P2도 일정시간 버튼을 눌러서 전송되었을 때 P1이 P2의 패킷을 받는 시점에서 P2의 버튼이 눌러진 시점으로 롤백해서 그 버튼 입력을 포함시켜서 같이 처리하는데 그래서 상대방의 패킷이 왔을 때 특별한 처리를 해야하는데 이 때 버그가 많이 생겨서 이 부분을 조금 심플하게 만든 버전인데 상대방의 입력을 기다리는 것이 복잡하기 때문에 입력이 오든 안오든 매 프레임마다 일정시간 만큼 무조건 롤백하는 방식입니다.
이 사이에 상대방 입력이 들어오게 되면 그 입력이 자동적으로 프레임에 끼워 넣기 때문에 롤백할 때 같이 처리가 됩니다.
이렇게 되면 롤백을 더 많이 하지만 어떤 특별한 상황에 처리하라는 부분이 없어지기 때문에 프로그램 로직상 구현이 더 단순해지죠.
여기서 매 프레임마다 롤백을 자주하게 되면 성능상 loss가 일어나게 되지만 롤백 기능을 잘 구현했다면 뒤로 갔다 앞으로 가는건 성능상 loss가 없습니다. (그래픽의 변화가 생기지 않기 때문에)
뒤로 롤백했다가 앞으로 돌아가면 애니메이션이 뒤로갔다 앞으로 가니까 프로그램이 해야 할 일이 많은 것처럼 보이겠지만
화면이 보여지지 않은 상태에서 프레임만 뒤로 갔다가 앞으로 가게 되면 이미지 자체는 똑같은 곳에서 변화가 없어 에니메이션 처리를 할 필요가 없고, 이 중간 과정을 사용하는 유저에게 보여줄 필요가 없습니다. 사용자는 롤백된 결과와 롤백이 된 후 앞감기를 한 결과가 같으면 못 느끼기 때문이죠.
그래서 이 롤백을 잘 구현하면 성능에 대한 큰 차이가 없습니다.
그래서 항상 매 프레임마다 뒤로 가면서 그 사이에 다른사람의 패킷이 껴들면 그거까지 포함해서 진행하는 방식이 훨씬 버그도 적고, 구현하기도 쉬워서 실무에서 많이 사용합니다.
여기까지 Deterministic방식에 대해 알아보았습니다.
정리해보면 Deterministic은 같은 입력 이면 같은 결과가 나온다는 가정하에 시작하는 것입니다.
이것이 지켜지지 않으면 Deterministic은 아예 성립자체가 안됩니다. 그래서 프로그램 상에서 이것을 보장해주어야 하죠.
그러면 이 Input을 맞출거냐는 문제가 생기는데 그 방식에서 딜레이를 주는 방식, 롤백을 하는 방식이 있고, 지금 알아본 중계서버를 이용하는 방식이 있습니다.
그리고 롤백을 할 때 구현이 복잡해서 매 프레임 사이클 만큼 롤백해서 처리하는 방식을 많이 사용한다 보면 됩니다.
깃허브 주소 :
'Networking' 카테고리의 다른 글
[바미] 홀펀칭에 대해 알아보자! (0) | 2020.12.18 |
---|---|
[바미] P2P, Relay 서버 방식에 대해 알아보자! (2) | 2020.12.18 |
[바미] Delay 와 Rollback에 대해 알아보자! (0) | 2020.12.18 |
[바미] Deterministic방식에 대해 알아보자! (0) | 2020.12.18 |
[바미] Protocol TCP,UDP에 대해 알아봅시다. (0) | 2020.12.18 |