我试图更好地理解c#中的tcp/ip套接字,因为我想挑战自己,看看我是否可以创建一个有效的MMO基础设施(游戏世界,地图,玩家等)纯粹用于教育目的,因为我没有意图成为另一个"OMGZ iz将让我的r0x0r MMORPG比魔兽更好!!!",你知道我在说什么.
无论如何,我想知道是否有人可以阐明一个人如何设计这种系统以及需要什么样的东西,以及我应该注意什么?
我最初的想法是将系统分解为单独的客户端/服务器连接,每个连接(在自己的端口上)执行特定任务,例如更新玩家/怪物位置,发送和接收聊天消息等等.处理数据更容易,因为您不总是需要在数据上放置标题以了解数据包包含哪些信息.
这是否有意义且有用,还是我只是简单地复杂化了事情?
非常感谢您的回复.
如果您正在进行套接字级编程,那么无论您为每种消息类型打开多少端口,您仍然需要具有某种类型的标头.即使它只是消息其余部分的长度.已经说过很容易为消息添加简单的头和尾结构.我认为只需处理客户端的一个端口就更容易了.
我相信现代MMORPG(甚至可能是旧版)有两级服务器.登录服务器验证您是付费客户端.一旦验证,这些将您带到包含所有游戏世界信息的游戏服务器.即便如此,这仍然只需要客户端打开一个套接字,但不允许有更多.
此外,大多数MMORPGS还加密他们的所有数据.如果你把它写成一个有趣的练习,那么这并不重要.
一般来说,设计/编写协议是我担心的事情:
字节序
客户端和服务器是否始终保证具有相同的endianess.如果不是,我需要在序列化代码中处理它.有多种方法可以处理结束.
忽略它 - 显然是一个糟糕的选择
指定协议的字节顺序.这就是旧协议所做的/做的事情,因此称为网络顺序这个术语总是大端的.实际上,只要您指定一个或另一个,您指定哪个字节顺序并不重要.如果你不使用大的endianess,一些狡猾的老网络程序员会站起来,但如果你的服务器和大多数客户端都是小端,你真的不会通过制定协议大端来购买除了额外工作之外的任何东西.
在每个标题中标记Endianess - 您可以添加一个cookie,它将告诉您客户端/服务器的endianess,并根据需要相应地转换每条消息.额外的工作!
使您的协议不可知 - 如果您将所有内容作为ASCII字符串发送,那么endianess是无关紧要的,但也会更低效.
在我通常选择2的4中,并指定endianess为大多数客户端,现在几天将是小端.
前向和后向兼容性
协议是否需要向前和向后兼容.答案应该几乎总是肯定的.在这种情况下,这将决定我如何在版本控制方面设计整个协议,以及如何创建每个单独的消息来处理实际上不应该成为版本控制的一部分的微小更改.你可以利用这个并使用XML,但是你会失去很多效率.
对于整体版本控制,我通常设计一些简单的东西.客户端发送一个版本控制消息,指出它说的是版本XY,只要服务器可以支持该版本,它就会发回一条确认客户端版本的消息,一切都在前进.否则它会解密客户端并终止连接.
对于每条消息,您可以使用以下内容:
+-------------------------+-------------------+-----------------+------------------------+ | 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的问题:
http://en.wikipedia.org/wiki/Protocol_Buffers
http://en.wikipedia.org/wiki/Thrift_(protocol)
http://en.wikipedia.org/wiki/Etch_(protocol)
http://en.wikipedia.org/wiki/Adaptive_Communication_Environment
http://en.wikipedia.org/wiki/CORBA
还有很多