1

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

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

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

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

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

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

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

4

1 回答 1

7

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

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

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

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

从鸟瞰的角度来看,这是我设计井字游戏系统的方式:

协议

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

客户端到服务器

JOIN
READY
MOVE (x, y)
QUIT

服务器到客户端

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

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

服务器

这是假设井字游戏服务器的不完整伪代码实现。我写的代码只涉及应用程序的网络层,所有围绕井字游戏规则和动作的实际逻辑都包含在TicTacToeBoard类中,这里不做描述,但是实现起来应该很简单。

Task服务器逻辑发生在单个线程中,如果您使用 .NET 中的现代/ IO API,则可以非常轻松地在单个线程中完成多路分离传入消息的逻辑async,否则使用每个远程连接一个线程可以很快以及处理它的简单方法(2人游戏不需要担心缩放 - 但这不会很好地扩展到数百名玩家,只是说)。(处理这些远程连接的代码这里不详述,它隐藏在 abstract 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 
        }
    }
于 2013-02-16T09:42:01.587 回答