[翻译]XNA 3.0 Game Programming Recipes之fifty-six
PS:自己翻译的,转载请著明出处
8-4 在网络上发送/接收数据
问题
创建和加入一个网络会话是一回事,但是如果你没有发送和接收任何数据,网络又有什么好处呢?
解决方案
当玩家已经连接到了这个会话,你可以保存所有你想要发送到PacketWriter流里的数据。一旦这个已经被完成,你可以发送这个PacketWriter到所有的玩家在这个会话中,使用这个LoaclNetworkPlayer.SendData方法。
在你为一个在本地机器上的玩家接收任何数据之前,你应该检查LocalNetworkGamer.IsDataAvailable的标志是否设置成true,它表示数据已经接收到了并且准备被处理。
只要这个标志是true,你应该调用LocalNetworkGamer.ReceiveData方法,返回一个PacketReader包含其他玩家发送到本地玩家的所有的数据。
它是如何工作的
这节创建在前面章节的结果上,它允许多台机器在同一网络通过会话互连。这个程序结束在InSession状态,它简单调用会话的Update方法。
这里,你将会使InSession状态其实做了些事情,所以你的玩家将发送一些数据到所有其他的玩家在这个会话中。作为一个例子,你发送程序已经运行的分钟数和秒数。
接下来,你监听到你的玩家的任何数据变量。如果这里有数据变量,你将会接收两个数并且把他们翻译成字符串,然后打印在屏幕上。
为了发送和接收数据,你需要一个PacketWriter对象和一个PacketReader对象,这样添加这些两个变量到你的代码中:
2 PackReader reader=new PacketReader();
这里没有理由有更多的一个PacketWriter对象和一个PacketReader对象在你的项目中。
Sending Data to Other Players in the Session(在会话中发送数据到其他的玩家)
你应该首先保存所有在你想要发送给其他玩家的数据在PacketWriter中.你可以通过传递你的数据作为参数到它的Write方法中来实现它:
2 writer.Write(gameTime.TotalGameTime.Seconds);
2 localGamer.SendData(writer,SendDataOptions.None);
SendDataOptions参数将会解释在本章节的末尾。相当重要,这个SendData方法有一个重载版本,它允许你发送数据到唯一一个指定玩家,而不是到所有连接在这个会话中的玩家。
这是你所需要去做的,使你的数据发送到其他连接在这个会话中的玩家!
Receiving Data from Other Players in the Session(在会话中接收其他玩家的数据)
基本上,为了从其他玩家那里接收数据,你做这个反转:你调用你的本地玩家的ReceiveData方法,它将返回一个PacketReader包含所有通过其他玩家发送的数据。调用PacketReader.Read方法中的一个去从PacketReader中获得你的数据
2 localGamer.ReceiveData(reader,out sender);
3 string playerName=sender.Gamertag;
4 int minutes=reader.ReadInt32();
5 int seconds=reader.ReadInt32();
这个ReceiveData方法将保存数据在PacketReader流里面。作为奖励,玩家它发送这个数据将会保存在第二个参数中。这样你知道数据来自于哪里。
当你从你的PacketReader里读取数据时,你需要确保你用与保存它的同样的顺序去读它。同样,因为PacketReader简单的包含一个字节流,你需要去指明哪个对象你想从字节里去构造它。例如,一个整型要求的比一个矩阵的字节少,所以在某些时候,你需要告诉PacketReader哪种对象你想要被保存。
你在这个例子上发送分钟和秒都是整型,所以你想要从字节流里重构两个整型。仔细看下PacketReader的Read方法的不同,注意它支持哪种对象。如果你发送一个Matrix,你想要从字节流里使用ReadMatrix方法去重构它。使用这个ReadSignle方法去重构float,这个ReadDouble方法去读一个double,并且ReadInt16方法去读一个short.由于一般int使用的是32bit(4字节),你使用这个在前面的代码中的ReadInt32()方法。
LocalGamer.IsDataAvailable
如果多个玩家正在发送给你数据,可能你有多个字节流通过你的代码正在等待被读。这同样会发生如果其他玩家正在调用SendData比正在调用ReceiveDatag更高频率。
在些情况下,你可以询问localGamer.IsDataAvailable属性,因为这将一直保持为true,当这里有一个字节流当代本地玩家去读。
只要数据对你的玩家是可用的,下面的代码将接收一个新的PacketReader和读玩家的GamerTag属性,它发送这个数据,接下来,玩家程序已经运行的分钟和秒数从PacketReader里被读出。
2 {
3 NetworkGamer sender;
4 localGamer.ReceiveData(reader,out sender);
5 string gamerTime="";
6 gamerTime+=sender.Gamertag+":";
7 gamerTime+=sender.ReadInt32()+"m";
8 gamerTime+=sender.ReadInt32()+"s";
9 gamerTimes[sender.Gamertag]=gamerTime;
10 }
只要一个玩家离开会话,你想要从Dictionary移动到相应的条目中。一个非常棒的方式可以作到是通过捕获相应的GamerLeft事件:
2 {
3 log.Add(e.Gamer.Gamertag+"left the current session");
4 gamerTimes.Remove(e.Gamer.Gamertag);
5 }
SendDataOptions
在这个会话中,当你发送数据到其他的玩家,你盼望所有你的消息到达接收者用你发送它们的同样顺序。由于因特网的结构,你的信息可以以与你发送它们的不同的顺序到达。甚至更糟,你的某些数据也许根本就没有到!
幸运的是,你可以指定两个重要的因素为你发送的每个包。在你可以决定他们之前,你应该知道他们是什么,他们的益处是什么,这是十分重要的,其缺点又是什么:
1.Order of arrival:数据包应该以他们发出的同样被接收吗?
2.Reliability:你发送的数据绝对至关重要,或者游戏生存的某些数据包丢失了?
这两个问题的答案是是/否,给你四种可能的组合。LocalNetworkGamer.SendData接收一个SendDataOptions标志作为一个第二个参数,它允许你去指定他们四个中的一个组合(它同样接收一个第5个标志,SendDataOptions.Chat,它在本章后面在讨论):
1.SendDataOptions.None:指定你将要发送的数据包并不重要,接收顺序也没有太大关系。
2.SendDataOptions.InOrder:指定你将要发送的数据包用与发送的相同的顺序去接收,但是如果丢失一两个数据包也没有什么问题。
3.SendDataOptions.Reliable:指定相反的SendDatOptions.InOrder.使用这个如果你的数据非常重要,意思是所有你发送的数据已经被到达接收器。不管,不关你怎么发送它们,数据包以不同的接收顺序接受也没有多大关系。
4.SendDataOptions.ReliabeInOrder:表明所有的数据已经到达接收器,并且以发送的顺序接收的。
好,这就容易了;我将要拾取最后一个!不幸的是,某些操作有它的缺点,这里解释一下:
1.SendDataOption.None:这里没有什么速度处罚;数据只希望简单发送。
2.SendDataOptions.InOrder:在你发送它们之前,所有的包被分配一个序列号码。接收器检查它们的号码,并且如果一个包A在新的包B后面被收到,包A被简单的丢弃。这很容易去检查并且不需要花费时间,但是你的某些数据可以通过它被丢掉,尽管它们已经成功的到达了目的地。
3.SendDataOptions.Reliable:在这种情况下,接收器检查去看看哪个包丢失了。当包C似乎从包流ABDE中丢失,接收器要求发送者去发送包C。在此期间,包D和E可以访问你的XNA代码。
4.SendDataOptions.ReliableInOrder:使用这个当你的数据要求它。当这个接收器检测到包C从流ABDE中丢失,它要求发送者去重新发送包C。这时,后面的包D和E不会被传知道包C已经成功被接收。这将导致严重的迟缓,因为所有的后续的包被停止并且保存在内存中,直到包C已经被重新发送并且被接收到。
作为一般的规则,它是很安全的使用SendDataOptions.InOrder为大多数游戏数据,同时SendDataOption.ReliableInOrder应该被用来作为一个小的可能。
SendDataOptions.Chat
在你开始通过网络发送数据时,你需要注意一件事:人对人的聊天信息通过因特网发送没有被加密,因为它被法律禁止的。直到密码学出现,默认的,为所有你发送的数据使用localGamer.SendData方法,你应该使用SendDataOptions.Chat去表示XNA不一定加密这样的信息。如果你想要,这可以被用在联合其他的SendDataOptions,就象这样:
在图8-1的右部分显示四个可能的方式,数据如何到达接收器。在a的情况下,这个包以发出时的准确顺序已经到达接收器。在b和c的情况下,包的顺序改变了。重要的事要注意,不过,在这两种情况下,第一个聊天包A在第二个聊天包C之前接收到,并且第一个数据包B在第二个数据包D之间接收到。
由于两个数据和聊天包可以被发送在同样的祯期间,你需要确保你不能混合它们在接收器端。一个可能的方式去做这个是在你发送它们之前,添加一个小的导言到你的包中,表示它是否是数据或者聊天包。在下面代码中将显示出来,其中D代表一个数据包和C代表聊天包:
2 writer.Write(gameTime.TotalGameTime.Minutes);
3 writer.Write(gameTime.TotalGameTime.Seconds);
4 LocalNetworkGamer localGamer=networkSession.LocalGamers[0];
5 LocalGamer.SendData(writer,SendDataOptions.ReliableInOrder);
6 writer.Write("C");
7 writer.Write("This is a chat message from"+localGamer.Gamertag);
8 localGamer.SendData(writer,SendDataOptions.Chat|SendDataOptions.ReliableInOrder);
2 {
3 NetworkGamer sender;
4 localGamer.ReceiveData(reader,out sender);
5 string messageType=reader.ReadString();
6 if(messageType=="D")
7 {
8 string gamerTime="";
9 gamerTime+=sender.Gamertag+":";
10 gamerTime+=sender.ReadInt32()+"m";
11 gamerTime+=sender.ReadInt32()+"s";
12 gamerTimes[sender.Gamertag]=gamerTime;
13 }
14 else if(mssageType=="C")
15 {
16 lastChatMessage[sender.Gamertag]=reader.ReadString();
17 }
18 }
Multiple Local Players
如果多个玩家被连接到同一台机器上,你想要通过它们叠代发送和接收它们的数据。
发送它们的数据到所有的玩家是非常简单的:
2 foreach(LocalNetworkGamer localGamer in networkSession.LocalGamers)
3 {
4 writer.Write(gameTime.TotalGameTime.Munutes);
5 writer.Write(gameTime.TotalGameTime.Seconds);
6 localGamer.SendData(writer,SendDataOptions.ReliableInOrder);
7 }
在同样的机器上,为你的所有的本地玩家接收数据,一点也不难。只要确保你循环你的代码直到所有你的本地玩家的IsDataAvailable标志被设置成false:
2 {
3 while(localGamer.IsDataAvailable)
4 {
5 NetworkGamer sender;
6 localGamer.ReceiveData(reader,out sender);
7 string gamerTime=localGamer.Gamertag+"received from";
8 gamerTime+=sender.Gamertag+":";
9 gamerTime+=sender.ReadInt32()+"m";
10 gamerTime+=sender.ReadInt32()+"s";
11 gamerTimes[sender.Gamertag]=gamerTime;
12 }
13 }
代码
(略)
源代码:http://shiba.hpe.cn/jiaoyanzu/WULI/soft/xna.aspx?classId=4
(完)