C# 游戏服务器架构

标签 c# design-patterns tcp tcpclient tcplistener

我想知道如何编写一个“好的”游戏服务器。我有一些想法,但我从来没有制作过服务器,我不想最终写出 brainfuck 代码。

我知道如何处理 TCP 连接等等,但我的问题是如何在服务器和客户端之间进行通信。

例如:我写了一个像 TicTacTow 这样的游戏。现在用户点击了一个单元格,我想告诉那个服务器。服务器应验证用户是否可以单击该单元格并告诉客户端。如果服务器告诉是;您可以单击客户端将其显示为“X”。

现在我的问题是:我如何准确地告诉服务器我想单击该字段。我在这里遇到了另一个问题,他们最终使用了命令模式。但如果我理解正确,我将不得不创建一个实现接口(interface)的命令。我序列化该命令的一个实例并将其发送到服务器。服务器执行命令。但我必须解决主要问题:

  1. 如果命令将 tictacto 的单元格设置为 X。我如何告诉服务器他必须将我需要将单元格设置为 X 的 GameBoard 传递到接口(interface) ICommand 的调用方法。
  2. 这不是极度不安全吗?我的意思是我可以编写一个命令来删除我的所有文件或停止服务器并将其发送到服务器。我不认为命令模式是个好主意。

所以我正在寻找更好的东西。我只想为我的客户端和服务器提供一个简单且可扩展的架构。有什么好的模式吗?

哦,还有另一个问题:您会使用序列化器还是自己对数据进行编码?

最佳答案

是什么让您产生了直接使用对象序列化的想法?这对于游戏服务器来说是一件非常糟糕的事情,因为它很慢,很容易崩溃(特别是如果你有一个复杂的对象图要序列化)并且通常是 hackish。解决方案是从头开始设计和实现您自己的游戏通信协议(protocol) - 幸运的是这很容易。

序列化主要在枯燥的业务应用程序和数据层中完成,其目标是轻松地传输数据,通常是在异构系统之间,这就是为什么经常使用基于 XML 的序列化。这些要求不适用于游戏。

请注意,序列化对象只会序列化数据成员,不会序列化方法。因此,您对恶意代码被下线发送的担忧是没有根据的。

无论如何,服务器设计的第一条规则是永远不要信任客户端 - 这意味着游戏服务器需要在内存中维护当前游戏状态的副本并将游戏规则应用于每个从客户端发送的游戏移动消息。

从鸟瞰的角度来看,我将如何设计您的井字游戏系统:

协议(protocol)

因为游戏状态的变化是事件驱动的并且是回合制的,所以基于动词和参数的协议(protocol)效果最好。我会有以下动词,假设这是一个严格的 2 人游戏(使用 2 个客户端和一个服务器)

客户端到服务器

JOIN
READY
MOVE (x, y)
QUIT

服务器到客户端

JOINED
ISREADY
MOVED (player, x, y)
DENIED (reason)
LEFTGAME (player)

客户端到服务器的动词应该是不言自明的。您会注意到服务器到客户端动词是原始客户端到服务器消息的代理版本,除了 MOVED 包含移动玩家的 ID,还包含 DENIED 以通知客户端他们的移动被拒绝。 LEFTGAME 动词用于表示其他玩家已退出。

服务器

这是一个假设的 Tic-tac-toe 服务器的不完整伪代码实现。我编写的代码只涉及应用程序的网络层,所有围绕井字游戏规则和移动的实际逻辑都包含在 TicTacToeBoard 类中,此处不予描述,然而,实现起来应该是微不足道的。

服务器逻辑发生在单个线程中,如果您使用现代 Task/async IO,则可以很容易地在单个线程中完成多路分解传入消息的逻辑.NET 中的 API,否则使用每个远程连接一个线程也可以是处理它的快速简便的方法(2 人游戏不需要担心缩放 - 但这不会很好地扩展到数百个玩家,只是说)。 (处理这些远程连接的代码在这里不详述,它隐藏在抽象的 WaitForAndGetConnectionGetLastIncomingMessageSendMessage 方法后面)。

    // Ready-room state
    connection1 = WaitForAndGetConnection();
    connection2 = WaitForAndGetConnection();
    SendMessage( connection1, "JOINED" ); // inform player 1 that another player joined
    
    p1Ready = false, p2Ready = false;
    while(message = GetLastIncomingMessage() && !p1Ready && !p2Ready) {
        if( message.From == connection1 && message.Verb == "READY" ) p1Ready = true;
        if( message.From == connection2 && message.Verb == "READY" ) p2Ready = true;        
    }
    
    SendMessage( connection1, "ISREADY" ); // inform the players the game has started
    SendMessage( connection2, "ISREADY" ); // inform the players the game has started
    
    // Game playing state
    TicTacToeBoard board = new TicTacToeBoard(); // this class represents the game state and game rules
    
    p1Move = true; // indicates whose turn it is to move
    while(message = GetLastIncomingMessage()) {
        
        if( message.Verb == "MOVE" ) {
        
            if( p1Move && message.From == connection1 ) {
                 if( board.Player1Move( message.X, message.Y ) ) {
                    SendMessage( connection1, "MOVED (1, " + message.X + "," + message.Y + " )");
                    SendMessage( connection2, "MOVED (1, " + message.X + "," + message.Y + " )");
                    p1Move = false;
                } else {
                    SendMessage( message.From, "DENIED \"Disallowed move.\".");
                }
                
            } else if( !p1Move && message.From == connection2 ) {
                
                if( board.Player2Move( message.X, message.Y ) ) {
                    SendMessage( connection1, "MOVED (2, " + message.X + "," + message.Y + " )");
                    SendMessage( connection2, "MOVED (2, " + message.X + "," + message.Y + " )");
                    p1Move = true;
                } else {
                    SendMessage( message.From, "DENIED \"Disallowed move.\".");
                }
                
            } else {
                SendMessage( message.From, "DENIED \"It isn't your turn to move\".");
            }
            if( board.IsEnded ) { 
                // handle game-over...
            } 
        } else if( message.Verb == ... // handle all of the other verbs, like QUIT 
        }
    }

关于C# 游戏服务器架构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14908762/

相关文章:

C# - 连接 : keep-alive Header is Not Being Sent During HttpWebRequest

c# - Mvvm后退按钮: Page and ViewModel lose command binding

java - 类型转换可以作为适配器设计模式的一个例子吗?

sockets - Unix 中的 TCP 套接字 - 通知服务器我已完成发送

java - 从套接字的 byte[] 开头解析 int

c# - 我不断收到相同的错误, “CS1525: Unexpected symbol ` string'。我只是找不到我出错的地方

c# - 如何在 Word 文件中查找数学方程式,如果找到则使用 vsto c# 突出显示它

jquery - 如何制作 jQuery 插件(正确的方法)?

php - 何时在模型 (MVC) 中包含辅助方法?

python - 为什么 raw_input 提示不正确?