소켓을 통해 n 개의 클라이언트와 통신하는 턴 기반 서버를 작성하기위한 패턴이 있습니까?


14

게임을하는 임의의 수의 TCP 소켓 네트워크 클라이언트에 대한 게임을 관리하는 일반 게임 서버에서 일하고 있습니다. 나는 작동하는 덕트 테이프와 함께 해킹 된 '디자인'을 가지고 있지만 깨지기 쉽고 유연하지 않은 것처럼 보입니다. 강력하고 유연한 클라이언트 / 서버 통신을 작성하는 방법에 대해 잘 정립 된 패턴이 있습니까? (그렇지 않으면 아래 내용을 어떻게 개선하겠습니까?)

대략 나는 이것을 가지고있다 :

  • 게임을 설정하는 동안 서버에는 클라이언트의 동기 요청 및 서버의 응답을 처리하는 각 플레이어 소켓마다 하나의 스레드가 있습니다.
  • 그러나 게임이 시작되면 한 번의 휴면을 제외한 모든 스레드가 해당 스레드가 이동에 대해 한 번에 하나씩 모든 플레이어를 순환합니다 (요청 된 응답-응답).

여기 내가 현재 가지고있는 것의 도표가있다; 더 큰 / 판독 가능한 버전 또는 66kB PDF를 보려면 클릭하십시오 .

흐름의 순서도

문제 :

  • 플레이어는 정확한 메시지로 정확하게 차례대로 응답해야합니다. (나는 각 플레이어가 무작위 쓰레기로 응답하고 그들이 나에게 유효한 움직임을 주면 움직일 수 있다고 가정합니다.)
  • 플레이어가 자신의 차례가 아닌 한 서버와 대화 할 수 없습니다. (서버가 다른 플레이어에 대한 업데이트를 보내도록 할 수는 있지만 비동기식 요청은 처리 할 수 ​​없습니다.)

최종 요구 사항 :

  • 성능이 가장 중요하지 않습니다. 이것은 주로 비 실시간 게임에 사용되며, 까다로운 인간이 아니라 서로 AI를 뚫는 데 주로 사용됩니다.

  • 게임 플레이는 항상 턴제 상태입니다 (매우 높은 해상도 일지라도). 각 플레이어는 항상 다른 하나의 플레이어가 턴을하기 전에 한 번의 움직임을 처리합니다.

차이가 나는 경우 서버 구현은 Ruby로 이루어집니다.


클라이언트 당 TCP 소켓을 사용하는 경우 (65535-1024) 클라이언트에이 제한이 적용되지 않습니까?
o0 '.

1
왜 문제가 있습니까, 로호리스? 대부분의 사람들은 60000 명의 동시 사용자를 얻기 위해 고심 할 것입니다. 60000은 신경 쓰지 마십시오.
Kylotan

@ 킬로 탄 동시 AI가 10 개 이상인 경우 놀랄 것입니다. :)
Phrogz

호기심으로 그 다이어그램을 작성하기 위해 어떤 도구를 사용 했습니까?
matthias

@matthias 슬프게도, 그것은 Adobe Illustrator에서 너무 많은 사용자 정의 작업이었습니다.
Phrogz

답변:


10

정확히 달성하려는 것이 무엇인지 잘 모르겠습니다. 그러나 게임 서버에서 지속적으로 사용되는 패턴 중 하나가 도움이 될 수 있습니다. 메시지 대기열을 사용하십시오.

보다 구체적으로 말하면, 클라이언트가 서버에 메시지를 보낼 때 즉시 처리하지 마십시오. 오히려 이들을 구문 분석하고이 특정 클라이언트의 큐에 넣습니다. 그런 다음 일부 메인 루프에서 (다른 스레드에서도) 모든 클라이언트를 차례로 살펴보고 대기열에서 메시지를 검색하여 처리합니다. 처리가이 클라이언트의 차례가 완료되었다고 표시하면 다음 단계로 넘어갑니다.

이런 식으로, 고객은 엄격하게 단계별로 작업 할 필요가 없습니다. 클라이언트가 처리 될 때까지 대기열에 무언가를 갖도록 충분히 빠릅니다 (물론 클라이언트를 기다리거나 지연되는 경우 건너 뛸 수 있음). "비동기"대기열을 추가하여 비동기 요청에 대한 지원을 추가 할 수 있습니다. 클라이언트가 특수 요청을 보내면이 특수 대기열에 추가됩니다. 이 큐는 클라이언트의 큐보다 더 자주 점검되고 처리됩니다.


1

하드웨어 스레드는 "플레이어 당 1 명"을 3 자리 수의 플레이어에게 합리적인 아이디어로 만들기에 충분히 확장되지 않으며, 언제 깨어날지를 아는 논리는 복잡해질 것입니다. 더 나은 아이디어는 읽기 또는 쓰기 작업이 수행되는 동안 전체 프로그램 스레드를 일시 중지하지 않고도 데이터를주고받을 수있는 Ruby 용 비동기 I / O 패키지를 찾는 것입니다. 또한 읽기 작업에 스레드가 걸려 있지 않으므로 플레이어가 응답하기를 기다리는 문제도 해결합니다. 대신 서버는 시간 제한이 만료되었는지 확인한 다음 그에 따라 다른 플레이어에게 알릴 수 있습니다.

기본적으로 '비동기 I / O'는 찾고자하는 '패턴'이지만 실제로는 패턴이 아니며 접근 방식입니다. 소켓에서 명시 적으로 'read'를 호출하고 데이터가 도착할 때까지 프로그램을 일시 중지하는 대신, 데이터가 준비 될 때마다 'onRead'핸들러를 호출하도록 시스템을 설정하고 해당 시간까지 처리를 계속합니다.

각 소켓에는 차례가 있습니다

각 플레이어는 턴을 가지고 있으며, 각 플레이어에는 데이터를 보내는 소켓이 있습니다. 언젠가는 플레이어 당 하나의 소켓을 원하지 않을 수 있습니다. 소켓을 전혀 사용하지 않을 수 있습니다. 책임 영역을 별도로 유지하십시오. 이것이 중요하지 않은 디테일처럼 들리지만 디자인에 다른 개념을 혼동시킬 때 더 나은 접근법을 찾고 논의하기가 더 어려워지면 죄송합니다.


1

분명히 여러 가지 방법이 있지만 개인적으로 별도의 스레드를 건너 뛰고 이벤트 루프를 사용합니다. 이를 수행하는 방법은 사용중인 I / O 라이브러리에 따라 다르지만 기본적으로 기본 서버 루프는 다음과 같습니다.

  1. 새 연결을위한 연결 풀 및 청취 소켓을 설정하십시오.
  2. 무언가 일어날 때까지 기다리십시오.
  3. 새로운 연결 인 경우 풀에 추가하십시오.
  4. 무언가가 클라이언트의 요청 인 경우 즉시 처리 할 수 ​​있는지 확인하십시오. 그렇다면, 그렇게하십시오. 그렇지 않은 경우 대기열에 넣고 (선택적으로) 승인을 클라이언트에 보냅니다.
  5. 또한 대기열에 처리 할 수있는 것이 있는지 확인하십시오. 그렇다면 그렇게하십시오.
  6. 2 단계로 돌아갑니다.

예를 들어, 게임에 관련된 클라이언트 가 n 명 이라고 가정합니다 . 그런 다음 첫 번째 n-1 이 이동을 보내면 이동이 유효한지 확인하고 이동이 수신되었다는 메시지를 다시 보내지 만 여전히 다른 플레이어의 이동을 기다리고 있습니다. 결국 n 개의 플레이어가 이동 한, 당신은 당신이 저장 한 모든 선수들에게 결과를 전송 한 모든 이동을 처리합니다.

또한 시간 초과를 포함하도록이 체계를 세분화 할 수 있습니다. 대부분의 I / O 라이브러리에는 새 데이터가 도착 하거나 지정된 시간이 경과 할 때까지 기다리는 메커니즘이 있어야합니다 .

물론 루프를 실행하는 중앙 스레드 (게임당 하나 또는 서버 당 하나)로 직접 처리 할 수없는 요청에 해당 스레드가 전달되도록하여 각 연결마다 개별 스레드로 이와 같은 것을 구현할 수도 있습니다. 클라이언트와 직접 연결하지 않고 연결 핸들러 스레드와 통신한다는 점을 제외하고는 위에 표시된대로 단일 스레드 접근 방식보다이 단순하거나 복잡한 방법을 찾는 것은 실제로 당신에게 달려 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.