[게임 서버 프로그래밍 교과서] 5장. 게임 네트워킹

조재훈·2024년 11월 4일
0

5장. 게임 네트워킹

5.1 UML

UML은 플로우 차트보다 더 많은 역할을 한다

UML을 사용하면 데이터와 프로세스의 정의가 편해져 프로그램 구조 명세를 표현하는 대표적인 수단이라고 할 수 있다

5.1.1 UML 시퀀스 다이어그램

여기선 객체와 메시지를 사용한다

객체는 상호 작용의 주체이고 메시지는 상호 작용의 내용이며 화살표 방향을 보고 송신과 수신을 구분할 수 있다

시퀀스도의 주요 용도는 객체 간 메시징의 흐름이 어떻게 되는지 한 눈에 보기 위해서임

직관적이지만 메시지를 어떻게 처리하는지 표현하는 것은 한계가 있지만 시퀀스도에 플로차트를 함께 사용하면 자세하게 실행 방식을 설명할 수 있다

시퀀스 다이어그램 참고 블로그

액티비티 다이어그램

실린더 도형은 활동 상태를 의미하며 맨 위에 있는 것은 최초 상태를 의미한다. 플로우 차트와 비슷하게 화살표를 사용해 활동 상태의 변화 과정인 실행 흐름을 표기한다. 조건 분기도 사용할 수 있다

액티비티 다이어그램 참고 블로그

5.2 게임 플레이 네트워킹

5.2.1 모든 역할을 서버에서 하기

과거에는 보통 네트워킹과 관련된 것을 서버에서 다 했다

이 방법에서 클라이언트는 그저 사용자 입력과 화면 출력만 담당한다. 서버에서 하는 역할은 게임 로직과 화면 렌더링, 화면 송출이 있다

이전에는 화면에 텍스트만 출력해도 됐지만 최근에는 고사양 게임들이 많아 1초당 30~60번의 렌더링을 하게 된다. 이러한 렌더링과 게임 로직 모두를 서버에서 처리하기는 버겁다

온라인 게임을 방해하는 레이턴시가 길어지는 다양한 요인은

  1. 서버가 멀리 있어 네트워킹 중 레이턴시가 추가됨
  2. 클라우드 서버 안에서 가상 머신은 다른 가상 머신이 CPU 사용량을 늘리면서 지연 시간이 생김
  3. 패킷 드롭으로 인한 재송신
  4. 인구가 낮은 국가
  5. 무선 네트워크에서는 레이턴시와 패킷 드롭률이 크게 증가

서버 운영의 경제성 문제도 있음

  1. 60프레임으로 서버에서 렌더링하려면 서버에 접속한 사용자수만큼 그래픽카드를 동원해야 한다
  2. 일반적 MMORPG 서버 컴퓨터는 플레이어 처리를 2000개에서 2만개까지 해야 경제성이 나옴

5.2.2 렌더링은 클라이언트에서 하기

게임 서버의 역할이었던 렌더링을 클라이언트가 담당하게 하자

  • 서버는 렌더링을 위한 최소 정보인 게임 월드의 상태만 클라이언트에게 보내고 월드 상태의 연산은 서버에서 한다
  • 렌더링은 클라이언트에서 담당하며 그래픽 리소스는 클라이언트에서 보유한다
  • 서버와 클라이언트의 월드 상태를 동일하게 유지한다 -> 동기화한다

서버는 처음에 월드를 알고 있지만 클라이언트는 알지 못한다. 그래서 서버가 클라이언트에게 씬 상태 전체를 전송한다(ex. 월드에 있는 캐릭터 정보) 이를 통해 서버와 클라이언트가 동일한 월드 상태를 가짐

플레이어가 어떤 행동을 하면 메시지가 서버에 전송됨. 서버는 이를 받아 월드 상태를 변화시킴. 월드 상태가 변하면 서버는 변한 부분을 클라이언트에게 보내고 클라이언트는 이를 월드 데이터에 반영한다

플레이어가 이동하는 것처럼 서버에서 클라이언트에게 일정 시간마다 반복해 보내는 메시지가 있는데 클라이언트가 이것을 받으면 캐릭터 상태를 계속 업데이트해야 한다. 이렇게 씬의 상태에 영구적인 변화를 가하는 것을 지속성 이벤트라고 한다

특정 이벤트가 잠깐 나타나는 것을 단발성 이벤트라고 하는데 이런 결과론적인 이벤트를 지속성 이벤트로 만드는 것보다 단발성 이벤트로 만드는 것이 효과적임

지금까지 설명한 방식이 원할하게 작동하려면 다음과 같이 해야한다

  1. 서버는 1/60초마다 월드를 업데이트한다
  2. 서버는 1/60초마다 월드 상태의 변화를 클라이언트에게 보낸다
  3. 클라이언트는 이를 수신한다
  4. 클라는 받은 메시지를 월드에 반영해 다음 렌더링 프레임에서 그린다

이는 서버와 클라이언트 사이 레이턴시가 1/60초보다 훨씬 낮아야하고 균일해야 한다. 하지만 LAN이 아닌 이상 쉽지 않다

서버에 접속한 클라이언트가 많을수록 서버의 부담이 늘어나고 클라이언트에서 받는 메시지도 많아질 것임. 이 문제를 해결할 방법으로 월드 변화의 텀을 조금 늘리는 것이다. 하지만 뚝뚝 끊겨 보일 것이다

끊겨 보이는 것을 개선하려면 상태 값 보정이 있는데 상태 변화 메시지를 즉시 반영하는 것이 아니라 서서히 목적 값으로 변하게 하는 것이다. 선형 보간이나 스플라인같은 곡선형 보간이 있다

5.2.3 추측항법

위의 방법도 좋지는 못하다. 한 템포씩 느려 보일것이다. FPS 게임이나 레이싱같은 경우 문제가 될 것이다

이 문제를 해결하기 위해 생각해보자. 이쪽에서 저쪽 캐릭터의 위치를 받았을 때는 약간의 시간이 지났을 것이다. 그러면 저쪽 캐릭터의 위치 정보를 지난 시간만큼 예측해주면 된다. 이를 추측항법이라고 한다. 상대방의 움직임을 예상해 보정하는 방법이다

추측항법을 사용하려면 두 기기 간 레이턴시를 알고 있어야 한다. 레이턴시 측정 방법의 대표적인 것은 라운드 트립 레이턴시를 측정하는 방법이다

  1. 기기 A에서 B에 패킷을 보낸다
  2. B는 이를 받으면 A에 패킷을 보낸다
  3. 기기 A는 1 과정의 시간과 현재 시간의 차이를 구하여 2로 나눈다

예제 P254

레이턴시 자체를 완전히 없앨 수 없지만 레이턴시로 발생하는 문제를 감출 수는 있다. 추측항법을 포함한 이런 방법을 레이턴시 마스킹이라고 한다

5.3 레이턴시 마스킹

게임이 다음과 같이 만들어졌을 때

  1. 클라이언트가 플레이어를 조작하는 명령을 서버에 보냄
  2. 서버는 캐릭터 이동 연산을 함
  3. 일정 시간마다 클라이언트에 이동 정보 메시지를 보냄
  4. 클라이언트는 메시지를 받으면 추측항법을 통해 캐릭터 위치를 부드럽게 만든 후 업데이트

이 상황에서 네트워크가 좋지 못하면 약간의 지연시간이 있을 수도 있다. 서버와 클라이언트 사이 왔다갔다 하니까

이를 위해 '사소한 것은 클라이언트가 판단하기'를 사용한다. 플레이어 자신의 위치 계산을 클라이언트에서 판단하고 결과를 서버에게 전달한다

이를 통해 쾌적하게 캐릭터를 조작할 수 있고 서버도 처리량이 줄어든다. 하지만 단점이 뒤따르는데 클라이언트가 해킹당할 수 있음

이를 해결하기 위해

  • 클라이언트가 이동 후 서버에 보내고 서버는 정보를 받아 정상적인 값 범위인지 체크하는 방법이 있고
  • 클라이언트가 캐릭터 명령 정보를 서버에 보내고 캐릭터를 일방적으로 움직인 다음 이동 정보 메시지는 서버에 보내지 않는다
    • 서버는 명령 정보에 따라 캐릭터를 이동시키고 이동 정보 메시지를 클라이언트에게 보낸다
    • 클라이언트가 서버에서 메시지를 받으면 일방적으로 움직였던 캐릭터 위치를 무시하고 메시지에 따라 캐릭터를 이동시킨다

두 번째 방법은 해킹을 막는 효율적인 방법이지만 레이턴시가 커질 때마다 캐릭터 움직임이 손상되어 레이턴시가 균일하고 낮은 경우에 한해 채택하는 것이 일반적이다

레이턴시 마스킹을 위한 또 다른 방법은 '보여주고 나중에 얼렁뚱땅하기'가 있다

  1. 플레이어의 행동에 따른 메시지를 서버에 보내고 행동을 연출하는 모습을 즉시 시작
  2. 서버가 메시지를 받아 처리하고, 플레이어 캐릭터에 가해야 하는 행동을 클라이언트에 메시지로 보냄
  3. 클라이언트는 메시지를 받으면 연출해야 하는 나머지 부분들을 클라이언트에서 보여 주기 시작

심리적인 방법으로 유저가 즉각적인 피드백을 받게 하기 위해 쓸 수 있는 방법이다

5.4 넓은 월드, 많은 캐릭터 처리

월드에 캐릭터가 너무 많으면 통신량이 증가한다. 그런데 월드는 넓고 캐릭터도 많지만 정작 플레이어의 화면에는 다 담지 못한다. 그래서 이를 가시 영역 필터링으로 해결할 수 있다

한편 서버는 모든 플레이어가 모든 캐릭터 정보를 가지고 있어야 한다. 모든 플레이어의 가시 영역 정보도 가지고 있어야 한다. 그래야 서버는 캐릭터에 변화가 생길 때 그 캐릭터를 볼 수 있는 다른 플레이어들의 클라이언트에 메시지를 보낼 수 있음

서버는 다음 정보를 가지고 있어야 한다

  • 플레이어 각각에 대한 각 플레이어가 볼 수 있는 캐릭터 목록
  • 캐릭터 각각에 대해 자기 자신을 볼 수 있는 플레이어 목록

서버는 이 정보를 계속 가지며 업데이트해야 한다. 이러한 연산이 무거울 것 같지만 모든 플레이어에게 모든 캐릭터 정보를 보낼 때 발생하는 처리량 부담 및 네트워킹 부담에 비하면 가볍다

5.5 실시간 전략 시뮬레이션 게임에서 네트워크 동기화

전략 시뮬레이션 게임에는 등장하는 캐릭터가 매우 많다. 지금까지 알아본 방식으로 네트워크 동기화를 할 경우 통신량이 늘어남. 이때는 락스텝 동기화 알고리즘을 사용하는 것이 좋다

락스텝 : 누군가 신호를 주면 그 신호에 맞추어 모두가 똑같은 행동을 하는 것

락스텝의 구동 원리는

  1. 각 플레이어는 다른 플레이어들에게 입력 명령을 보낸다
  2. 플레이어의 입력 명령에 따라 모든 클라이언트가 동시에 씬 업데이트를 한다

이것으로 얻는 효과는 다음과 같다

  1. 각 클라이언트 플레이어의 입력 명령만 주고받으며, 씬을 구성하는 캐릭터의 이동 상태를 주고 받지 않는다
  2. 입력 명령은 통신량이 상대적으로 매우 적음

락스텝 동기화 방식이 어떻게 작동하는지 알아보자. 네트워크 지연 시간이 없다고 가정해보면

플레이어가 명령을 게임 월드에 내리면 명령은 즉시 실행되어 네트워크를 통해 상대방에게 전송됨. 상대방 컴퓨터는 명령을 바로 받아 월드를 바꿀 수 있어 다른 플레이어들의 월드와 같은 상태를 유지가 가능하다

플레이어가 자기 컴퓨터를 해킹해 월드가 동일한 상태가 되지 않으면 플레이어마다 월드가 다를 수 있기에 각 플레이어는 월드 상태에 대한 체크섬을 계산해 업데이트마다 상대방에게 보냄

상대방에게 메시지가 오지 않으면 업데이트하지 않고 대기한다. 모든 기기에서 상대방에게 1/60초마다 메시지를 보내며 모든 기기에서 메시지가 온 후 게임 월드를 업데이트한다

이것은 레이턴시가 전혀 없을때 완벽하게 작동한다는 한계가 있어 이를 해결하려면 입력 명령을 보내되 언제 실행해야 하는지에 대한 미래 시간을 같이 보내야 한다

미래 시간 = 현재 시간 + 왕복 레이턴시 / 2 + 임의의 일정 값

미래 시간은 너무 멀지도 가깝지도 않아야 한다. 멀면 명령을 주고 실행하기까지 오래 걸린다. 너무 가까우면 플레이 화면이 중간중간 멈춰 움직임이 튀는 형태의 랙이 발생한다

락스텝 동기화 알고리즘은 적은 통신량으로도 정확히 동기화된다는 장점이 있지만 다음과 같은 한계가 있다

  1. 다른 플레이어가 플레이하고 있는 중간에 입장하는 것을 만들기 까다로움
  2. 물리 엔진 등 게임 플레이에 관여하는 연산에 부동소수점을 쓸 수 없음. 부동소수점이 연산에 약간의 오차가 있을 수 있는데 이게 누적되면 큰 차이로 이어진다. 그래서 고정소수점으로 해야 한다
  3. 플레이어 수가 많아지기 어렵다. 통신량이 플레이어 수에 비례해 증가하기 때문
  4. 씬 업데이트가 일시 정지할 확률이 높다. 게임을 플레이하려면 플레이어 중 가장 레이턴시가 높은 사람을 기준으로 해야 한다
  5. 입력 명령의 속도에 민감한 게임에 부적합하다. 방향키로 이동하는 방식에 이 알고리즘을 적용하면 매우 답답하다. 그래서 이동 목표 지점을 찍어서 이동하는 방식에 적합함(스타크래프트)

5.6 실제 레이턴시 줄이기

실제 레이턴시를 줄이는 방법을 알아보자

  • TCP 대신 UDP를 사용하는 것도 한 방법임. TCP는 패킷 유실 시 패킷 재전송을 위한 지연시간이 발생하지만 UDP는 재전송을 위한 지연 시간이 생기지 않지만 UDP 데이터그램 유실을 피할 수 없음
    • UDP에서 계속된 이동같은 경우 패킷이 계속 들어오므로 패킷 유실이 생겨도 오차는 금방 극복된다
  • 똑같은 양의 데이터를 보내도 적은 수의 패킷으로 보내는 것이 좋다. 메시지를 적절히 뭉쳐서 보내는 것이 효과적임
  • 클라이언트-서버 간 통신과 클라이언트끼리 통신하는 것을 섞어 쓰는 방법도 좋다
    • 가급적 많은 지역에 게임 서버를 골고루 분포시킨 후 플레이어들은 자신과 가까운 지역의 게임 서버에 접속해 플레이하면 위와 같이 안해도 됨
    • 그렇게 할 여유가 없으면 두 가지 네트워킹 방식을 혼용해 중요한 연산은 클라이언트-서버에서 처리하며 캐릭터 이동이나 채팅같이 덜 중요한 연산은 P2P로 처리한다. 클라이언트가 가까이 있지만 서버는 멀 때 이 방식이 효과적임

5.7 게임 플레이 이외의 네트워킹

이번에는 게임 플레이 이외의 네트워킹 처리를 살펴보자

로그인

로그인같은 경우 클라이언트와 서버 간 통신 절차는 다음과 같다

  1. 로그온 요청을 서버로 보냄
  2. 서버는 파일이나 데이터베이스에서 유저 정보를 식별해 받아옴
  3. 처리 결과를 클라이언트에 통보함
  4. 클라이언트가 통보를 받으면 플레이어 정보를 데이터베이스에서 로딩해 게임 서버 메모리에 보관한다(이 과정은 있을 수도 있고 없을 수도 있음)

이런 통신 순서같은것은 시퀀스 다이어그램같은 문서를 작성하는 것이 좋다

서버에서 데이터베이스에 기록할 때 결과를 기다릴 필요가 없을 때는 데이터베이스에 기록하는 함수를 별도의 스레드나 비동기 함수 호출로 처리해도 된다

매치메이킹

롤 큐와 같이 비슷한 실력의 플레이어를 매칭하는 방법은 뒤에도 설명할 것이라 여기선 수동으로 방을 만들어 방에 들어가는 방식을 설명한다

  1. 해킹 방지 목적으로 방 만들기 혹은 들어가기는 클라이언트에서 판단하지 말고 서버에서 판단할 것
  2. 클라이언트는 일방적 판단하지 말고 서버에 요청해 결과에 따라 행동할 것
  3. 방 만들기 혹은 들어가기로 서버 내부의 방 목록이나 방 안의 플레이어 목록이 변할 때 클라이언트는 그 변화를 통보받을 것

이를 위해 서버는 방 목록과 각 방에 들어가있는 플레이어 목록을 가지고 있어야 하며 게임 플레이 중인 방의 상태 데이터도 갖고 있어야 한다

P271의 시퀀스 다이어그램 참고

아이템

아이템 사용은 특히 현금으로 구매한 아이템이 있을 수 있으므로 해킹에 주의해야 한다

이는 웬만하면 클라이언트에서 판단하지 않게 하는 것이 좋으며 클라이언트는 그저 아이템을 사용하고 싶다는 메시지만 서버에 전송한다

서버는 이 요청을 받아 "너가 이 아이템을 가지고 있으며, 사용할 준비가 되어 있는가"를 검사한다. 서버는 클라이언트에 그 결과를 응답한다

5.8 해킹과 보안

게임 개발에서 가장 골치 아픈 것은 해킹이다. 온라인 게임에선 특히 중요하게 방어해야한다

해킹의 유형에는 크게 두 가지가 있다

  1. 크래킹 : 게임 외 분야에서 흔히 이야기하는 해킹. 다른 사람의 회원 정보를 빼오는 것과 같은
  2. 치트 혹은 조작 : 게임에서만 발견되는 해킹. 능력치를 비정상적으로 높이거나 다른 사람의 플레이를 망가뜨림

5.8.1 네트워크 해킹

인터넷은 많은 네트워크의 집합체이며 각 네트워크 기기는 여러 사람들이 공유해서 사용한다. 이러한 구조 특성상 다른 컴퓨터의 통신을 도청하는 것은 막을 수 없다

많이 사용되는 암호화 알고리즘은 암호 키를 이용해 암호화를 하는 것이다

  • 암호화 : 평문 + 암호 키 -> 암호문
  • 복호화 : 암호문 + 암호 키 -> 평문

해커는 비밀번호를 알려면 서버 간 통신할 때 사용되는 암호 키를 얻어야 하며 찾으려면 매우 오래걸린다

어떻게 해커가 유저와 서버 간의 암호 키를 모르게 할까? 이를 위해 또 다른 암호화 알고리즘으로 암호 키를 2개 두는 것이 있다

암호 키 1로 만든 암호문은 암호 키 2로만 풀 수 있는 알고리즘이다

  • 평문 + 암호 키 1 -> 암호문
  • 암호문 + 암호 키 2 -> 평문

각 용어를 살펴보면

  • 암호 키 : 대칭 키 혹은 세션 키
  • 암호 키 1 : 공개 키
  • 암호 키 2 : 개인 키

암호 키로 암호화를 복호화하는 알고리즘을 대칭 키 알고리즘이라고 하며 주로 쓰이는 알고리즘은 AES이다

암호 키 1,2로 암호화, 복호화하는 알고리즘을 비대칭 키 알고리즘이라고 하며 주로 RSA 알고리즘을 쓴다

하지만 이래도 모든 것을 암호화되지는 않는다. 중간자 공격같은 다른 방법들이 있기 때문임

네트워크를 해킹하는 공격에는 DDOS(서비스 거부)도 있다. 서버에 대량의 네트워크 데이터를 쏴 서버에 과부하가 걸리게 해 다른 사람들이 정상적인 서비스를 받지 못한다

5.8.2 클라이언트 컴퓨터 해킹

해커가 많이 쓰는 방법이다. 다른 사람의 컴퓨터에 몰래 악성 프로그램을 심어 버린다. 이메일이나 문자 메시지로 첨부 파일이나 인터넷 주소를 보내 이것을 열면 악성 프로그램이 컴퓨터에서 실행된다. 이 프로그램은 다른 프로그램이나 디스크에 있는 내용을 도청하고 조작한다. 흔히 멀웨어바이러스가 이에 해당한다

운영체제나 응용 프로그램의 결함을 이용해 악성 프로그램을 전파하기도 한다. 이렇게 되면 앞서 암호화 알고리즘이 소용이 없다

5.8.3 서버 컴퓨터 해킹

게임 서버도 종종 해킹의 대상이 되는데 게임 서버의 경우 피해가 막심하다. 서버 쪽 운영체제나 응용 프로그램의 결함이나 보안 설정의 구멍을 이용해 해킹한다. 유지 관리가 필요하다

서버 고유의 역할인 웹 서버나 데이터베이스 서버는 클라이언트와 다른 종류의 해킹을 추가로 예방해야 한다. 질의 구문 인젝션 등이 있다

게임 서버는 일반적인 유저의 접속 방법인 리스닝 포트를 제외하고 모두 방화벽으로 막아야 한다. 방화벽은 일종의 네트워크 기기이며 통신책을 감시하고 차단하는 역할을 한다

5.8.4 게임 치트

게임 치트는 온라인 게임에서 흔하다

치트 역시 네트워크 공격, 즉 클라이언트 공격을 이용하는 것이 일반적임. 네트워크 도청 및 조작을 통해 해킹하는 경우를 살펴보자

어떤 캐릭터가 공격 메시지를 서버에 보낼 때 이 메세지를 갈무리해 복제해서 서버에 보내면 서버가 메시지를 처리해 한 번의 공격이 엄청나게 많은 공격이 된다

이를 해결하려면 서버에서 받는 메시지가 시간 차이도 유효한지 살펴봐야 한다. 게임 시스템이 복잡해질수록 해커는 다양한 빈틈을 노릴 것이다

다른 방법으로 전투 시 디도스 공격으로 다른 유저들을 제압하는 경우도 있다

화면에는 표시되지 않아도 다른 플레이어들의 정보가 클라이언트 메모리에 있다. 해커는 이를 해킹해 알아내고 상대방의 위치를 알 수 있다

에임 핵같이 적 플레이어의 위치를 찾아 자동으로 마우스를 조작해 공격하는 경우도 있다

이러한 종류의 해킹을 차단하려면 게임 클라이언트 프로그램 측에 자기가 조작되고 있는 지를 알아야 한다. 해킹을 감시하는 프로그램이 필요함

profile
나태지옥

0개의 댓글