2008-11-12 132 views
17

我想获得对c#中tcp/ip套接字的更好的理解,因为我想挑战自己,看看我是否可以纯粹为了教育目的创建一个可用的MMO基础结构(游戏世界,地图,玩家等)无意再成为那些“OMGZ会让我的r0x0r MMORPG比WoW更好”的人,你知道我在说什么。c#中的多客户端,异步套接字,最佳实践?

无论如何,我想知道是否有人可以阐明如何设计这种系统以及需要什么类型的东西,以及我应该注意什么?

我最初的想法是将系统拆分为独立的客户端/服务器连接,并与每个连接(在其自己的端口上)执行特定任务(例如更新播放器/怪物位置,发送和接收聊天消息等)对我来说会更轻松地处理数据,因为你并不总是需要在数据上加一个头部来知道数据包包含的信息。

这是有道理的,是有用的,或者我只是复杂的事情的方式吗?

您的回复非常感谢。

回答

30

如果你正在做的插座级编程,那么不管你有多少端口打开每个消息类型,你仍然需要有某种形式的头。即使它只是消息其余部分的长度。话虽如此,将一个简单的头部和尾部结构添加到消息很容易。我认为只需处理客户端的一个端口就简单多了。

我相信现代MMORPG游戏(甚至旧的)有服务器的两个层次。验证您为付费客户端的登录服务器。一旦确认这些信息会传递给包含所有游戏世界信息的游戏服务器。即便如此,这仍然只需要在客户端有一个套接字开放,但并没有禁止拥有更多。

此外大多数MMORPGS也加密所有的数据。如果你将这写成练习以获得乐趣,那么这并不重要。

设计/一般书面协议,这里的事情,我担心:

端模式

是客户端和服务器始终保证有相同的字节顺序。如果没有,我需要在我的序列化代码中处理。处理永恒性有多种方式。

  1. 忽略它 - 显然,一个坏的选择
  2. 指定协议的字节序。这是旧协议所做/做的,因此术语网络订单总是大端的。它并不重要,只是你指定了哪一个或哪一个。如果你不使用大型的endianess,一些顽固的旧的网络程序员会站起来,但是如果你的服务器和大多数客户端是小端的话,那么除了通过使协议变成大端,你实际上不会为自己购买任何东西。
  3. 标记每个头中的Endianess - 您可以添加一个cookie来告诉您客户端/服务器的永久性,并根据需要对每个消息进行相应转换。加班!
  4. 使你的协议不可知 - 如果你把所有的东西都发送成ASCII字符串,那么endianess是无关紧要的,但是效率更低。

在4我通常会选择2,并指定endianess是大多数客户端,现在的日子将是小端。

向前和向后兼容

是否协议需要是向前和向后兼容。答案几乎总是肯定的。在这种情况下,这将决定我如何设计整个协议的版本控制,以及如何创建每个单独的消息来处理实际上不应该成为版本控制一部分的微小更改。你可以通过这种方式来使用XML,但是你会失去很多效率。

对于整体版本,我通常设计一些简单的东西。客户端发送版本信息,指定它说X.Y版本,只要服务器支持该版本,它就会发回一条确认客户端版本的消息,并且所有内容都会继续前进。否则它会暂停客户端并终止连接。

对于每一个消息,你有类似如下:

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

长度明显告诉你的消息是多久,不包括本身的长度。 MsgType是消息的类型。对于这个,只有两个字节,因为65356有很多应用程序的消息类型。这些标志让你知道消息中的序列化内容。这个字段与长度结合在一起就是你的向前兼容性和向后兼容性。

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); 

这可以让你添加新领域到底的消息,而不必修改整个协议。它还确保旧服务器和客户端将忽略他们不知道的标志。如果他们必须使用新的标志和字段,那么您只需更改总体协议版本。

使用框架的工作或没有

有我会考虑使用的业务应用程序的各种网络框架。除非我有特殊的需求,否则我会选择标准框架。在你的情况下,你想学习套接字级编程,所以这个问题已经为你解答。

如果你确实使用了框架,请确保它解决了上述两个问题,或者至少在你需要在这些区域中定制它时阻碍你。

我是与第三方

处理在很多情况下,你可能会处理与第三方服务器/客户端需要与沟通。这意味着一些场景:

  • 他们已经有一个协议定义 - 只需使用他们的协议。
  • 您已经定义了一个协议(他们愿意使用它) - 再次简单地使用定义的协议它们使用标准框架(基于WSDL等) - 使用框架。
  • 任何一方都没有定义协议 - 尝试根据手边的所有因素(我在这里提到的所有参数)以及他们的能力水平(至少据您所知)来确定最佳解决方案。无论如何确保双方都同意并理解协议。从经验来看,这可能是痛苦的或愉快的。这取决于你与谁一起工作。

无论哪种情况,您都不会与第三方合作,所以这只是为了完整性而添加的。

我感觉好像我可以写更多的关于这个,但它已经很长。我希望这会有所帮助,如果您有任何具体问题,请向Stackoverflow询问。

一个编辑回答knoopx的问题:

+0

你能说出任何这些网络框架吗? – knoopx 2009-08-10 12:56:27

1

我会建议针对不同信息的多个连接。我将设计一些协议,其中包含一个包含要处理信息的标题。尽可能少使用资源。此外,您可能不希望将来自客户端的各种职位和统计数据更新发送到服务器。否则,最终可能会导致用户可以修改其发送回服务器的数据。

说一个用户伪造怪物的位置以通过某物。我只会更新用户的位置矢量和动作。让其余的信息由服务器处理和验证。

0

是啊,我认为你是正确的单一连接,当然还有客户端不会是发送任何实际数据到服务器,更像就像“前进”简单的命令,“左转”等,服务器会移动地图上的角色并将新的坐标发送回客户端。

2

我认为你需要在你走路之前和跑步之前爬行。首先得到正确的框架和连接的设计,然后担心可伸缩性。如果你的目标是学习C#和tcp/ip编程,那么不要让自己更难。

继续您的初步想法,并保持数据流分开。

有乐趣和好运