多客户端,c# 中的异步套接字,最佳实践?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/284885/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Multi-client, async sockets in c#, best practices?
提问by Jason Miesionczek
I am trying to gain a better understanding of tcp/ip sockets in c#, as i want to challenge myself to see if i can create a working MMO infrastructure (game world, map, players, etc) purely for educational purposes as i have no intention of being another one of those "OMGZ iz gonna make my r0x0r MMORPG that will be better than WoW!!!", you know what im talking about.
我试图更好地了解 c# 中的 tcp/ip 套接字,因为我想挑战自己,看看我是否可以纯粹出于教育目的创建一个有效的 MMO 基础设施(游戏世界、地图、玩家等),因为我没有打算成为“OMGZ iz 会让我的 r0x0r MMORPG 比 WoW 更好!!!”的另一个人,你知道我在说什么。
Anyway, i was wondering if anyone can shed some light as to how one might approach designing this kind of system and what kinds of things are required, and what i should watch out for?
无论如何,我想知道是否有人可以阐明如何设计这种系统以及需要哪些类型的东西,以及我应该注意什么?
My initial idea was to break up the system into seperate client/server connections with each connection (on its own port) performing a specific task, such as updating player/monster positions, sending and receiving chat messages, etc. which to me would make processing the data easier because you wouldn't always need to put a header on the data to know what information the packet contains.
我最初的想法是将系统分解为单独的客户端/服务器连接,每个连接(在其自己的端口上)执行特定任务,例如更新玩家/怪物位置,发送和接收聊天消息等。处理数据更容易,因为您并不总是需要在数据上放置一个标头来了解数据包包含哪些信息。
Does that make sense and is useful or am i just way over complicating things?
这是否有意义并且有用,还是我只是把事情复杂化了?
your responses are very much appreciated.
非常感谢您的回复。
采纳答案by grieve
If you are doing socket level programming, then regardless of how many ports you open for each message type, you still need to have a header of some sort. Even if it is just the length of the rest of the message. Having said that it is easy to add a simple header and tail structure to a message. I would think it is easier to only have to deal with one port on the client side.
如果您正在进行套接字级编程,那么无论您为每种消息类型打开多少个端口,您仍然需要有某种类型的标头。即使它只是消息其余部分的长度。话虽如此,向消息添加简单的头和尾结构很容易。我认为只需要在客户端处理一个端口会更容易。
I believe the modern MMORPGs (and maybe even the old ones) had two levels of servers. The login servers which verify you as a paying client. Once verified these pass you off to the game server which contain all the game world information. Even so this still only requires the client to have one socket open, but does not disallow having more.
我相信现代 MMORPG(甚至可能是旧的)有两个级别的服务器。验证您是付费客户的登录服务器。一旦经过验证,这些就会将您传送到包含所有游戏世界信息的游戏服务器。即便如此,这仍然只需要客户端打开一个套接字,但不会禁止拥有更多套接字。
Additionally most MMORPGS also encrypt all their data. If you are writing this as an exercise for fun, then this will not matter so much.
此外,大多数 MMORPGS 还会加密其所有数据。如果你是为了好玩而写这个练习,那么这就不那么重要了。
For designing/writing protocols in general here are the things I worry about:
对于一般的设计/编写协议,我担心的是:
Endianess
字节序
Are the client and server always guaranteed to have the same endianess. If not I need to handle that in my serialization code. There are multiple ways to handle endianess.
客户端和服务器是否总是保证具有相同的字节序。如果不是,我需要在我的序列化代码中处理它。有多种方法可以处理字节序。
- Ignore it - Obviously a bad choice
- Specify the endianness of the protocol. This is what the older protocols did/do hence the term network order which was always big endian. It doesn't actually matter which endianess you specify just that you specify one or the other. Some crusty old network programmers will get up in arms if you don't use big endianess, but if your servers and most clients are little endian you really aren't buying yourself anything other than extra work by making the protocol big endian.
- Mark the Endianess in each header - You can add a cookie which will tell you the endianess of the client/server and let each message be converted accordingly as needed. Extra work!
- Make your protocol agnostic - If you send everything as ASCII strings, then endianess is irrelevant, but also a lot more inefficient.
- 忽略它 - 显然是一个糟糕的选择
- 指定协议的字节序。这是旧协议所做的/所做的,因此术语网络顺序始终是大端。您指定哪种字节序实际上并不重要,只需指定一个或另一个即可。如果你不使用大字节序,一些顽固的老网络程序员会站起来,但如果你的服务器和大多数客户端是小字节序的,你真的不会通过使协议大端序来购买额外的工作之外的任何东西。
- 在每个标头中标记字节序 - 您可以添加一个 cookie,它会告诉您客户端/服务器的字节序,并根据需要相应地转换每条消息。加班!
- 使您的协议不可知 - 如果您将所有内容作为 ASCII 字符串发送,则字节顺序无关紧要,但效率也会低得多。
Of the 4 I would usually choose 2, and specify the endianess to be that of the majority of the clients, which now days will be little endian.
在这 4 个中,我通常会选择 2 个,并将字节序指定为大多数客户端的字节序,现在这些字节序将是小字节序。
Forwards and Backwards Compatibility
向前和向后兼容性
Does the protocol need to be forwards and backwards compatible. The answer should almost always be yes. In which case this will determine how I design the over all protocol in terms of versioning, and how each individual message is created to handle minor changes that really shouldn't be part of the versioning. You can punt on this and use XML, but you lose a lot of efficiency.
协议是否需要向前和向后兼容。答案应该几乎总是肯定的。在这种情况下,这将决定我如何在版本控制方面设计整个协议,以及如何创建每个单独的消息来处理真正不应该成为版本控制一部分的微小更改。您可以在此基础上使用 XML,但会损失很多效率。
For the overall versioning I usually design something simple. The client sends a versioning message specifying that it speaks version X.Y, as long as the server can support that version it sends back a message acknowledging the version of the client and everything proceeds forward. Otherwise it nacks the client and terminates the connection.
对于整体版本控制,我通常会设计一些简单的东西。客户端发送版本控制消息,指定它使用 XY 版本,只要服务器可以支持该版本,它就会发回一条消息,确认客户端的版本,然后一切继续进行。否则它会拒绝客户端并终止连接。
For each message you have something like the following:
对于每条消息,您都有如下内容:
+-------------------------+-------------------+-----------------+------------------------+
| Length of Msg (4 bytes) | MsgType (2 bytes) | Flags (4 bytes) | Msg (length - 6 bytes) |
+-------------------------+-------------------+-----------------+------------------------+
The length obviously tells you how long the message is, not including the length itself. The MsgType is the type of the message. Only two bytes for this, since 65356 is plenty of messages types for applications. The flags are there to let you know what is serialized in the message. This field combined with the length is what gives you your forwards and backwards compatibility.
长度显然会告诉您消息的长度,不包括长度本身。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);
Then your deserialization code can do something like the following:
然后您的反序列化代码可以执行以下操作:
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);
This allows you to addnew fields to the endof the messages without having to revision the entire protocol. It also ensures that old servers and clients will ignore flags they don't know about. If they have to use the new flags and fields, then you simply change the overall protocol version.
这允许您在消息末尾添加新字段,而无需修改整个协议。它还确保旧服务器和客户端将忽略他们不知道的标志。如果他们必须使用新的标志和字段,那么您只需更改整个协议版本。
Use a Frame Work or Not
是否使用框架工作
There are various network frameworks I would consider using for a business application. Unless I had a specific need to scratch I would go with a standard framework. In your case you want to learn socket level programming, so this is a question already answered for you.
我会考虑将各种网络框架用于业务应用程序。除非我有特殊的需要从头开始,否则我会使用标准框架。在您的情况下,您想学习套接字级编程,所以这是一个已经为您解答的问题。
If one does use a framework make sure it addresses the two concerns above, or at least does not get in your way if you need to customize it in those areas.
如果确实使用了框架,请确保它解决了上述两个问题,或者如果您需要在这些领域对其进行自定义,则至少不会妨碍您。
Am I dealing with a third party
我是否与第三方打交道
In many cases you may be dealing with a third party server/client you need to communicate with. This implies a few scenarios:
在许多情况下,您可能正在与需要与之通信的第三方服务器/客户端打交道。这意味着一些场景:
- They already have a protocol defined - Simply use their protocol.
- You already have a protocol defined (and they are willing to use it) - again simple use the defined protocol
- They use a standard Framework (WSDL based, etc) - Use the framework.
- Neither party has a protocol defined - Try to determing the best solution based on all the factors at hand (all the ones I mentioned here), as well as their level of competencey (at least as far as you can tell). Regardless make sure both sides agree on and understand the protocol. From experience this can be painful or pleasant. It depends on who you are working with.
- 他们已经定义了一个协议 - 只需使用他们的协议。
- 您已经定义了一个协议(并且他们愿意使用它) - 再次简单地使用定义的协议
- 他们使用标准框架(基于 WSDL 等) - 使用框架。
- 双方都没有定义协议 - 尝试根据手头的所有因素(我在这里提到的所有因素)以及他们的能力水平(至少据您所知)确定最佳解决方案。无论如何确保双方同意并理解协议。根据经验,这可能是痛苦的,也可能是愉快的。这取决于你和谁一起工作。
In either case you will not be working with a third party, so this is really just added for completeness.
在任何一种情况下,您都不会与第三方合作,因此这实际上只是为了完整性而添加的。
I feel as if I could write much more about this, but it is already rather lengthy. I hopes this helps and if you have any specific questions just ask on Stackoverflow.
我觉得我可以写更多关于这个的东西,但它已经相当长了。我希望这会有所帮助,如果您有任何具体问题,请在 Stackoverflow 上提问。
An edit to answer knoopx's question:
编辑回答 knoopx 的问题:
回答by Nicholas Mancuso
I would advise against multiple connections for the different information. I would design some protocol that contains a header with information to be processed. Use as little resources as possible. Also, you may not want to send updates for various positions and stats from the client to the server. Otherwise you may end up in a situation where a user can modify their data being sent back to the server.
我建议不要针对不同的信息进行多个连接。我会设计一些包含要处理信息的标头的协议。尽可能少地使用资源。此外,您可能不想将各种位置和统计数据的更新从客户端发送到服务器。否则,您可能会遇到这样的情况:用户可以修改他们发送回服务器的数据。
Say a user falsifies a monster's location to get past something. I would only update the user's position vector, and actions. Let the rest of the information be processed and validated by the server.
假设用户伪造怪物的位置以通过某物。我只会更新用户的位置向量和动作。让其余的信息由服务器处理和验证。
回答by Jason Miesionczek
Yeah i think you're right about the single connection, and of course the client wouldnt be sending any actual data to the server, more like just simple commands like 'move forward', 'turn left', etc. and the server would move the character on the map and send the new co-ordinates back to the client.
是的,我认为您对单个连接的看法是正确的,当然客户端不会向服务器发送任何实际数据,更像是“向前移动”、“向左转”等简单命令,然后服务器就会移动地图上的角色并将新坐标发送回客户端。
回答by Tim
I think you need to crawl before you walk and before you run. First get the design of the framework and connections right, then worry about scalability. If your goal is to learn C# and tcp/ip programming, then don't make this harder on yourself.
我认为你需要先爬,然后再走路,然后再跑。首先正确设计框架和连接,然后担心可扩展性。如果您的目标是学习 C# 和 tcp/ip 编程,那么不要让自己变得更难。
Go ahead with your initial thoughts and keep the data streams separate.
继续您的初步想法并保持数据流分开。
have fun and good luck
玩得开心,祝你好运