문제

나는 C#에서 tcp/ip 소켓에 대해 더 잘 이해하려고 노력하고 있습니다. 왜냐하면 내가 가진 것이 없기 때문에 순전히 교육 목적으로 작동하는 MMO 인프라(게임 세계, 지도, 플레이어 등)를 만들 수 있는지 확인하고 싶기 때문입니다. "OMGZ iz는 WoW보다 더 나은 r0x0r MMORPG를 만들 것입니다!!!" 중 하나가 되겠다는 의도입니다. 제가 무슨 말을 하는지 아시겠습니까?

어쨌든, 이런 종류의 시스템을 설계하는 데 어떻게 접근해야 하는지, 어떤 종류의 것들이 필요하며 무엇을 주의해야 하는지에 대해 누군가가 밝힐 수 있는지 궁금합니다.

나의 초기 아이디어는 플레이어/몬스터 위치 업데이트, 채팅 메시지 보내기 및 받기 등과 같은 특정 작업을 수행하는 각 연결(자체 포트)을 사용하여 시스템을 별도의 클라이언트/서버 연결로 나누는 것이었습니다.패킷에 어떤 정보가 포함되어 있는지 알기 위해 항상 데이터에 헤더를 넣을 필요가 없기 때문에 데이터 처리가 더 쉬워질 것입니다.

그게 말이 되고 유용한가요? 아니면 제가 일을 너무 복잡하게 만든 건가요?

귀하의 답변에 매우 감사드립니다.

도움이 되었습니까?

해결책

소켓 수준 프로그래밍을 수행하는 경우 각 메시지 유형에 대해 열려 있는 포트 수에 관계없이 여전히 일종의 헤더가 필요합니다.메시지의 나머지 부분의 길이일지라도 마찬가지입니다.간단한 헤더와 꼬리 구조를 메시지에 추가하는 것은 쉽습니다.클라이언트 측에서 하나의 포트만 처리하는 것이 더 쉽다고 생각합니다.

나는 현대 MMORPG(어쩌면 오래된 MMORPG)에도 두 가지 수준의 서버가 있다고 생각합니다.귀하를 유료 클라이언트로 확인하는 로그인 서버.확인되면 모든 게임 세계 정보가 포함된 게임 서버로 전달됩니다.그럼에도 불구하고 여전히 클라이언트는 하나의 소켓만 열어야 하며 더 많은 소켓을 허용하지 않습니다.

또한 대부분의 MMORPGS는 모든 데이터를 암호화합니다.재미삼아 연습삼아 이 글을 쓴다면 별 문제가 되지 않을 것입니다.

일반적으로 프로토콜을 설계/작성할 때 제가 걱정하는 사항은 다음과 같습니다.

엔디아네스

클라이언트와 서버가 항상 동일한 엔디안을 갖도록 보장됩니까?그렇지 않은 경우 직렬화 코드에서 이를 처리해야 합니다.엔디안을 처리하는 방법에는 여러 가지가 있습니다.

  1. 무시하세요 - 분명히 나쁜 선택입니다
  2. 프로토콜의 엔디안을 지정합니다.이것이 이전 프로토콜이 수행한 작업이므로 항상 빅 엔디안인 네트워크 순서라는 용어가 사용됩니다.실제로 어떤 엔디안을 지정하는지는 중요하지 않습니다. 단지 둘 중 하나를 지정하면 됩니다.일부 딱딱한 오래된 네트워크 프로그래머는 빅 엔디안을 사용하지 않으면 무기를 들고 일어날 것입니다. 그러나 서버와 대부분의 클라이언트가 리틀 엔디안이라면 프로토콜을 빅 엔디안으로 만들어 추가 작업 외에는 아무것도 사지 않을 것입니다.
  3. 각 헤더에 Endianess 표시 - 클라이언트/서버의 endianess를 알려주고 각 메시지가 필요에 따라 그에 따라 변환되도록 하는 쿠키를 추가할 수 있습니다.추가 업무!
  4. 프로토콜을 불가지론적으로 만드십시오 - 모든 것을 ASCII 문자열로 보내는 경우 엔디아는 관련이 없지만 훨씬 더 비효율적입니다.

4개 중 일반적으로 2개를 선택하고 엔디안을 대다수 클라이언트의 엔디안으로 지정합니다. 현재는 리틀 엔디안이 됩니다.

순방향 및 역방향 호환성

프로토콜은 순방향 및 역방향 호환이 필요합니까?대답은 거의 항상 '예'입니다.어떤 경우에는 버전 관리 측면에서 전체 프로토콜을 설계하는 방법과 실제로 버전 관리에 포함되어서는 안 되는 사소한 변경 사항을 처리하기 위해 각 개별 메시지를 생성하는 방법이 결정됩니다.이것을 펀트하고 XML을 사용할 수 있지만 효율성이 많이 떨어집니다.

전반적인 버전 관리를 위해 나는 보통 간단한 것을 디자인합니다.클라이언트는 버전 X.Y를 말한다는 것을 지정하는 버전 관리 메시지를 보냅니다. 단, 서버가 해당 버전을 지원할 수 있는 한 클라이언트 버전을 확인하는 메시지를 다시 보내고 모든 것이 앞으로 진행됩니다.그렇지 않으면 클라이언트를 중단시키고 연결을 종료합니다.

각 메시지에는 다음과 같은 내용이 있습니다.

+-------------------------+-------------------+-----------------+------------------------+
| Length of Msg (4 bytes) | MsgType (2 bytes) | Flags (4 bytes) | Msg (length - 6 bytes) |
+-------------------------+-------------------+-----------------+------------------------+

길이는 길이 자체를 포함하지 않고 메시지의 길이를 분명히 알려줍니다.MsgType은 메시지 유형입니다.65356은 애플리케이션에 대한 메시지 유형이 많기 때문에 이를 위한 2바이트만 필요합니다.플래그는 메시지에 직렬화된 내용을 알려주기 위해 존재합니다.길이와 결합된 이 필드는 순방향 및 역방향 호환성을 제공합니다.

const uint32_t FLAG_0  = (1 << 0);
const uint32_t FLAG_1  = (1 << 1);
const uint32_t FLAG_2  = (1 << 2);
...
const uint32_t RESERVED_32 = (1 << 31);

그런 다음 역직렬화 코드는 다음과 같은 작업을 수행할 수 있습니다.

uint32 length = MessageBuffer.ReadUint32();
uint32 start = MessageBuffer.CurrentOffset();
uint16 msgType = MessageBuffer.ReadUint16();
uint32 flags = MessageBuffer.ReadUint32();

if (flags & FLAG_0)
{
    // Read out whatever FLAG_0 represents.
    // Single or multiple fields
}
// ...
// read out the other flags
// ...

MessageBuffer.AdvanceToOffset(start + length);

이를 통해 다음을 수행할 수 있습니다. 추가하다 새로운 분야 끝까지 전체 프로토콜을 수정하지 않고도 메시지를 수정할 수 있습니다.또한 기존 서버와 클라이언트가 자신이 모르는 플래그를 무시하도록 보장합니다.새로운 플래그와 필드를 사용해야 하는 경우 전체 프로토콜 버전을 변경하기만 하면 됩니다.

프레임 워크 사용 여부

비즈니스 애플리케이션에 사용을 고려하고 있는 다양한 네트워크 프레임워크가 있습니다.특별히 긁어야 할 필요가 없다면 표준 프레임워크를 사용하겠습니다.귀하의 경우 소켓 수준 프로그래밍을 배우고 싶으므로 이것은 이미 답변된 질문입니다.

프레임워크를 사용하는 경우 위의 두 가지 문제를 해결하는지 확인하거나 최소한 해당 영역에서 프레임워크를 사용자 정의해야 하는 경우 방해가 되지 않도록 하세요.

나는 제3자와 거래하고 있는가?

대부분의 경우 통신이 필요한 제3자 서버/클라이언트를 상대하게 될 수 있습니다.이는 몇 가지 시나리오를 의미합니다.

  • 이미 프로토콜이 정의되어 있습니다. 해당 프로토콜을 사용하면 됩니다.
  • 당신은 이미 프로토콜을 정의했습니다(그리고 그들은 그것을 사용할 의향이 있습니다) - 정의된 프로토콜을 다시 간단하게 사용하십시오
  • 표준 프레임워크(WSDL 기반 등)를 사용합니다. - 프레임워크를 사용합니다.
  • 어느 당사자도 정의된 프로토콜이 없습니다. 현재 모든 요소(여기서 언급한 모든 요소)와 역량 수준(적어도 알 수 있는 한)을 기반으로 최상의 솔루션을 결정하려고 노력하십시오.그럼에도 불구하고 양측이 프로토콜에 동의하고 이해하는지 확인하십시오.경험상 이것은 고통스러울 수도 있고 즐거울 수도 있습니다.누구와 함께 일하느냐에 따라 다릅니다.

두 경우 모두 제3자와 협력하지 않으므로 이는 완전성을 위해 추가된 것입니다.

이것에 대해 더 많이 쓸 수 있을 것 같지만 이미 꽤 길어졌습니다.이것이 도움이 되기를 바라며, 구체적인 질문이 있으면 Stackoverflow에 문의하세요.

knoopx의 질문에 대한 답변 편집:

다른 팁

걷기 전에, 뛰기 전에 기어야 한다고 생각합니다.먼저 프레임워크와 연결을 올바르게 디자인한 다음 확장성에 대해 걱정하세요.C#과 TCP/IP 프로그래밍을 배우는 것이 목표라면 이 일을 스스로 어렵게 만들지 마세요.

초기 생각을 계속 진행하고 데이터 스트림을 별도로 유지하십시오.

즐겁고 행운을 빌어요

다른 정보에 대해 여러 연결을 사용하지 않는 것이 좋습니다.처리할 정보가 포함된 헤더가 포함된 일부 프로토콜을 설계하겠습니다.가능한 한 적은 자원을 사용하십시오.또한 다양한 위치 및 통계에 대한 업데이트를 클라이언트에서 서버로 보내고 싶지 않을 수도 있습니다.그렇지 않으면 사용자가 서버로 다시 전송되는 데이터를 수정할 수 있는 상황이 발생할 수 있습니다.

사용자가 무언가를 지나가기 위해 몬스터의 위치를 ​​위조한다고 가정해 보겠습니다.사용자의 위치 벡터와 동작만 업데이트하겠습니다.나머지 정보는 서버에서 처리되고 검증됩니다.

네, 단일 연결에 대해서는 당신 말이 맞다고 생각합니다. 물론 클라이언트는 실제 데이터를 서버에 보내지 않을 것입니다. '앞으로 이동', '좌회전' 등과 같은 단순한 명령과 비슷합니다.서버는 지도에서 캐릭터를 이동하고 새 좌표를 클라이언트에 다시 보냅니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top