是什么让您产生了直接使用对象序列化的想法?对于游戏服务器来说这将是一件非常糟糕的事情,因为它很慢,很容易崩溃(特别是如果你有一个复杂的对象图要序列化)并且通常是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 WaitForAndGetConnection
、GetLastIncomingMessage
和SendMessage
方法后面)。
// 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
}
}