[翻译]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对象,这样添加这些两个变量到你的代码中:
1 PackWriter writer=new PacketWriter();
2 PackReader reader=new PacketReader();

                                 这里没有理由有更多的一个PacketWriter对象和一个PacketReader对象在你的项目中。

Sending Data to Other Players in the Session(在会话中发送数据到其他的玩家)
                                 你应该首先保存所有在你想要发送给其他玩家的数据在PacketWriter中.你可以通过传递你的数据作为参数到它的Write方法中来实现它:

1 writer.Write(gameTime.TotalGameTime.Minutes);
2 writer.Write(gameTime.TotalGameTime.Seconds);
                                 在你保存所有的你的数据在PacketWriter中,你可以使用这个你本地玩家的SendData方法发送它到所有的其他的玩家:
1 LocalNetworkGamer localGamer=networkSession.LocalGamer[0];
2 localGamer.SendData(writer,SendDataOptions.None);

                                 SendDataOptions参数将会解释在本章节的末尾。相当重要,这个SendData方法有一个重载版本,它允许你发送数据到唯一一个指定玩家,而不是到所有连接在这个会话中的玩家。
                                 这是你所需要去做的,使你的数据发送到其他连接在这个会话中的玩家!

Receiving Data from Other Players in the Session(在会话中接收其他玩家的数据)
                                 基本上,为了从其他玩家那里接收数据,你做这个反转:你调用你的本地玩家的ReceiveData方法,它将返回一个PacketReader包含所有通过其他玩家发送的数据。调用PacketReader.Read方法中的一个去从PacketReader中获得你的数据

1 NetworkGamer sender;
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里被读出。

 1 while(localGamer.IsDataAvailable)
 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 }
                                  为了让这个例子实际上可以做些事情,这个数据被分解成一个字符串,调用gamerTime,它被保存在一个Dictionary的内部。一个Dictionary是默认的类.Net查找别,通过添加这个变量到你的代码中你可以创建它:
1 Dictionary<string,string> gamerTimes=new Dictionary<string,string>();
                                  前面的代码将为每一个玩家创建一个条目在Dictionary,这些玩家发送数据给你。每次新的数据从一个玩家那里被接收,玩家的条目在Dictionary里被更新。在Draw方法里,你可以打印这个在这个Dictinary里字符串到屏幕上。
                                  只要一个玩家离开会话,你想要从Dictionary移动到相应的条目中。一个非常棒的方式可以作到是通过捕获相应的GamerLeft事件:
1 void GamerLeftEventHandler(object sender,GamerLeftEventArgs e)
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,就象这样:

1 localGamer.SendData(writer,SendDataOptions.Chat|SendDataOptions.Reliable);
                                 请注意,你可以发送加密和不加密的信息混合在一起。一件事要注意,如果你这样做了,安排好聊天数据将只对应聊天的数据,而不是加密的数据。例如,让我们看下这个情况,你首先发送一个聊天信息,其次是数据信心,聊天信息,和数据信息,正如8-1左边所示:

                                 在图8-1的右部分显示四个可能的方式,数据如何到达接收器。在a的情况下,这个包以发出时的准确顺序已经到达接收器。在b和c的情况下,包的顺序改变了。重要的事要注意,不过,在这两种情况下,第一个聊天包A在第二个聊天包C之前接收到,并且第一个数据包B在第二个数据包D之间接收到。
                                 由于两个数据和聊天包可以被发送在同样的祯期间,你需要确保你不能混合它们在接收器端。一个可能的方式去做这个是在你发送它们之前,添加一个小的导言到你的包中,表示它是否是数据或者聊天包。在下面代码中将显示出来,其中D代表一个数据包和C代表聊天包:
1 writer.Write("D");
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);
                                在接收的一端,简单的检查包是否是一个D或者C包,并且因此处理这个包:
 1 while(localGamer.IsDataAvialable)
 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
                                 如果多个玩家被连接到同一台机器上,你想要通过它们叠代发送和接收它们的数据。
                                 发送它们的数据到所有的玩家是非常简单的:

1 //send data from all local players to all other player in session
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 }
                                 请记住,你可以使用一个SendData方法重载的版本去发送数据到指定的玩家。
                                 在同样的机器上,为你的所有的本地玩家接收数据,一点也不难。只要确保你循环你的代码直到所有你的本地玩家的IsDataAvailable标志被设置成false:
 1 foreach(LocalNetworkGamer localGamer in networkSession.LocalGamers)
 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
(完)

posted on 2009-09-06 15:49  一盘散沙  阅读(297)  评论(0编辑  收藏  举报

导航