网络——在网络上发送,接收数据
問題
創建并加入一個網絡會話是一回事,但如果不能發送或接收任何數據那么網絡會話有什么用呢?
解決方案
當玩家連接到會話時,你可以在一個PacketWriter流中存儲所有想要發送的數據。完成這個操作后,你可以使用LocalNetworkPlayer.SendData方法將這個PacketWriter發送給會話中的所有玩家。
在玩家接收數據前,你應該檢查他們的LocalNetworkGamer. IsDataAvailable是否被設為ture,這表示數據已經被接收并做好了處理的準備。
一旦IsDataAvailable為true,你就可以調用LocalNetworkGamer.ReceiveData方法,返回一個包含另一個玩家發送給本地玩家的所有數據的PacketReader。
工作原理
這個教程建立在前一個教程結果的基礎上,前一個教程允許同一網絡上的多個機器通過一個會話互聯。程序結束于InSession狀態,這個狀態只是簡單地調用會話的Update方法。
現在,你將在InSession狀態中做點實際的操作,讓你的玩家可以將一些數據發送到會話中的其他玩家那里。本例中,你將發送程序運行的分鐘數和秒數。
然后,你監聽可用的數據。如果有可用的數據,你會接收兩個數字,將它們放在一個字符串中,顯示在屏幕上。
要發送和接收數據,你需要一個PacketWriter對象和一個PacketReader對象,所以在代碼中添加這兩個變量:
PacketWriter writer = new PacketWriter(); PacketReader reader = new PacketReader();在項目中使用超過一個的PacketWriter對象和PacketReader對象是毫無理由的。
將數據發送到會話中的另一個玩家
你需要在PacketWriter中存儲所有要發送給其他玩家的數據,這可以通過將數據作為Write方法的參數做到:
writer.Write(gameTime.TotalGameTime.Minutes); writer.Write(gameTime.TotalGameTime.Seconds);在將所有數據存儲到PacketWriter之后,你可以使用本地玩家的SendData方法將它發送到所有其他用戶:
LocalNetworkGamer localGamer = networkSession.LocalGamers[0]; localGamer.SendData(writer, SendDataOptions.None);SendDataOptions參數會在教程的最后解釋。更重要的是,SendData有一個重載方法可以只將數據發送到指定玩家而不是會話中的所有玩家。
以上就是將數據發送到會話中的其他玩家需要的所用操作。
從會話中的另一個玩家處接收數據
從其他玩家接收數據大致就是反過程:調用本地玩家的ReceiveData方法,這會返回一個包含其他玩家發送數據的PacketReader。調用PacketReader. Read方法中的一個從PacketReader獲取數據:
NetworkGamer sender; localGamer.ReceiveData(reader, out sender);string playerName = sender.Gamertag; int minutes = reader.ReadInt32(); int seconds = reader.ReadInt32();ReceiveData方法存儲PacketReader流中的數據,發送給你數據的玩家會存儲在第二個參數中,這樣你就可以知道數據來自于誰。
當從PacketReader讀取數據時,你需要確保以與發送相同的順序進行讀取。而且,因為PacketReader只包含字節流,你需要告知你想從字節構建哪個對象。例如,一個整數需要的字節比矩陣少,所以需要在某些時候告知你想恢復為哪種類型的對象。
本例中分鐘數和秒數為整數,所以你想從字節流中重新構建兩個整數。看一下PacketReader的不同Read方法,注意支持哪個對象。如果你想重構矩陣,則應該使用ReadMatrix方法,使用ReadSingle方法重構float,ReadDouble方法重構double,ReadInt16 重構short。
LocalGamer.IsDataAvailable
如果多個玩家向你發送數據,可能會有多個字節流需要被讀取,這種情況也會發生在其他玩家調用SendData的頻率大于你調用ReceiveData的頻率時。
在這種情況下,你可以查詢localGamer.IsDataAvailable屬性,因為只要有一個字節流正在等待本地游戲,這個屬性就會為true。
只要數據對你的玩家可用,下面的代碼就會接收一個新PacketReader并讀取發送數據的玩家的GamerTag屬性。然后,玩家程序運行的分鐘數和秒數就會從PacketReader中讀取。
while (localGamer.IsDataAvailable) ...{NetworkGamer sender;localGamer.ReceiveData(reader, out sender);string gamerTime = "";gamerTime += sender.Gamertag + ": "; gamerTime += reader.ReadInt32() + "m "; gamerTime += reader.ReadInt32() + "s"; gamerTimes[sender.Gamertag] = gamerTime; }要讓這個例子實際干點事情,數據被轉換到一個叫做gamerTime的字符串中,它存儲在一個Dictionary。Dictionary是默認的generic .NET查詢表,可以使用以下代碼創建:
Dictionary<string, string> gamerTimes = new Dictionary<string, string>();前面的代碼會在Dictionary中創建一個數據項,對應發送給你數據的玩家。當從一個玩家接收新數據時,Dictionary中的對應數據項會被更新,你可以在Draw方法中將Dictionary中的字符串顯示在屏幕上。
當玩家離開會話時,你需要將它們對應的數據項從Dictionary移除,這可以在GamerLeft 事件中加以處理:
void GamerLeftEventHandler(object sender, GamerLeftEventArgs e) ...{log.Add(e.Gamer.Gamertag + " left the current session"); gamerTimes.Remove(e.Gamer.Gamertag); }SendDataOptions
當你將數據發送到會話中的其他玩家時,你期望到達接受者的信息的順序與發送的順序是相同的,但是基于Internet的原理,你的信息可能會以不同于發送的順序到達,甚至更糟,有些數據可能根本就傳不到!
幸運的是,你可以為發送的數據包指定兩個重要的系數,在使用前你需要知道它們是什么,好處是什么,更重要的是,它們的缺點是什么:
- 到達的順序(Order of arrival):數據包接收的順序是否與發送的順序相同?
- 安全性(Reliability):你發送的這個數據是否是至關緊要的,如果數據包丟失游戲還能進行嗎?
以上兩個問題不是是就是否,可以提供四種可能性。LocalNetworkGamer.SendData可以將SendDataOptions作為第二個參數,這個參數可以讓你指定四種情況中的一個:
- SendDataOptions.None:發送的數據不是關鍵的,你接收數據的順序也無關緊要。
- SendDataOptions.InOrder:接收數據的順序必須和發送它們的順序相同,但一些數據包丟失無大礙。
- SendDataOptions.Reliable:與SendDatOptions.InOrder相反,你的數據是關鍵的,你發送的所有數據必須到達接收者。但是,接收數據的順序是否和發送的順序相同無關緊要。
- SendDataOptions.ReliableInOrder:所有的數據必須以和發送順序相同的順序到達接收者。
不是很難,我選擇最后一個!有些選項有點缺點,解釋如下:
- SendDataOptions.None:沒有速度損失,只能指望數據能夠成功發送。
- SendDataOptions.InOrder:在數據發送前,所有的數據包被分配了一個序號。接收者檢查這個序號,如果數據包A在一個更加新的數據包B之后被接收,數據包A會被拋棄。這是個簡單的檢查方法,幾乎不花時間,但即使有些數據成功到達了目的地可能也會被拋棄。
- SendDataOptions.Reliable:接收者會檢查丟失了哪個數據包。當數據包C從數據包流ABDE中丟失時,接收者會要求發送者重新發送數據包C,同時,數據包D和E在XNA代碼中可以被訪問。
- SendDataOptions.ReliableInOrder:只有在你的數據需要時才使用這個選項。當接收者法線數據包C從流ABDE中丟失時,它會讓發送者重新發送數據包C。這次,其后的數據包D和E不會被接收者傳遞到XNA中,直到數據包C也被成功的傳遞。這會引起延遲,因為所有后繼的數據包會保存在內存中直至數據包C被重新發送并收到才會被傳遞到XNA程序中。
普遍的原則是,對大多數數據來說SendDataOptions.InOrder是安全的,盡可能不要使用 SendDataOption.ReliableInOrder。
SendDataOption.Chat
在你開始發送數據前,你需要記住一件事情:在Internet上發送的聊天信息(chat message)不可以被加密,法律上是禁止的。
因為默認情況下使用localGamer.SendData方法發送數據都會進行加密,你必須使用SendDataOptions.Chat表示XNA不要加密聊天信息。你也可以使用SendDataOption的組合,如下所示:
localGamer.SendData(write,SengDataOptions.Chat|SendDataOptions.Reliable);注意你可以發送加密和不加密混合的信息。如果你這樣干,排序過的聊天信息只根據聊天數據排序而不是根據加密過的數據。例如,讓我們看一下發送第一條信息時的情況,依次是數據信息、聊天信息、數據信息,如圖8-1左圖所示。
圖8-1 順序發送4個數據包(左圖)和4種接收數據的方式
圖8-1的右圖顯示了數據如何到達接收端的4中可能方式。在a情況中,數據到達的順序與發送的順序一樣。在情況b和c中,數據包的順序發生了改變,但是,這兩種情況中第一個聊天數據包A在在第二個聊天數據包C之前被接收,第一個數據包B在第二個數據包D之前。
因為兩個數據包和兩個聊天數據包可以在一幀中被發送,你需要確保在接收端將它們混合起來,一個方法是在發送數據包前給它們添加一個小說明,表示它們是數據包還是聊天數據包,看一下下面的代碼,其中D表示一個數據包,C表示一個聊天數據包:
writer.Write("D"); writer.Write(gameTime.TotalGameTime.Minutes); writer.Write(gameTime.TotalGameTime.Seconds);LocalNetworkGamer localGamer = networkSession.LocalGamers[0]; localGamer.SendData(writer, SendDataOptions.ReliableInOrder);writer.Write("C"); writer.Write("This is a chat message from " + localGamer.Gamertag); localGamer.SendData(writer, SendDataOptions.Chat|SendDataOptions.ReliableInOrder);在接收端,只是簡單地檢查數據包是D還是C,并處理對應的數據包:
while (localGamer.IsDataAvailable) ...{NetworkGamer sender;localGamer.ReceiveData(reader, out sender);string messageType = reader.ReadString();if (messageType == "D") ... {string gamerTime = "";gamerTime += sender.Gamertag + ": ";gamerTime += reader.ReadInt32() + "m ";gamerTime += reader.ReadInt32() + "s";gamerTimes[sender.Gamertag] = gamerTime;}else if (messageType == "C") ... {lastChatMessage[sender.Gamertag] = reader.ReadString();} }多個本地玩家
如果多個玩家連接在同一個機器上,你需要通過迭代器發送和接受數據。
將數據發送到所有玩家很簡單:
//send data from all local players to all other players in session foreach (LocalNetworkGamer localGamer in networkSession.LocalGamers) ...{writer.Write(gameTime.TotalGameTime.Minutes); writer.Write(gameTime.TotalGameTime.Seconds);localGamer.SendData(writer, SendDataOptions.ReliableInOrder); }記住你可以使用SendData的一個重載方法將數據只發送到一個指定玩家。
接收數據也不難,只需循環代碼直到所有本地玩家的IsDataAvailable為false:
foreach (LocalNetworkGamer localGamer in networkSession.LocalGamers) ...{while (localGamer.IsDataAvailable)...{NetworkGamer sender;localGamer.ReceiveData(reader, out sender);string gamerTime = localGamer.Gamertag + " received from "; gamerTime += sender.Gamertag + ": ";gamerTime += reader.ReadInt32() + "m ";gamerTime += reader.ReadInt32() + "s";gamerTimes[sender.Gamertag] = gamerTime;} }代碼
下面是Update方法的代碼,包括擴展過的InSession狀態,你的機器上的所有玩家將數據發送到會話中的所有玩家。然后,他們會接收發送給他們的數據。
如果你在多個機器上運行這個代碼,他們會自動連接到第一個機器創建的會話上。然后,開始發送時間信息并在Draw方法中將接受到的數據顯示在屏幕上。
protected override void Update(GameTime gameTime) ...{if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit();if (this.IsActive)...{switch (currentGameState) ...{case GameState.SignIn: ... {if (Gamer.SignedInGamers.Count < 1) ... {Guide.ShowSignIn(1, false);log.Add("Opened User SignIn Interface");}else...{currentGameState = GameState.SearchSession;log.Add(Gamer.SignedInGamers[0].Gamertag + " logged in - proceed to SearchSession"); }}break;case GameState.SearchSession: ... {AvailableNetworkSessionCollection activeSessions = NetworkSession.Find(NetworkSessionType.SystemLink, 4, null);if (activeSessions.Count == 0) ... {currentGameState = GameState.CreateSession;log.Add("No active sessions found - proceed to CreateSession");}else ... {AvailableNetworkSession networkToJoin = activeSessions[0];networkSession = NetworkSession.Join(networkToJoin);string myString = "Joined session hosted by " + networkToJoin.HostGamertag; myString += " with " + networkToJoin.CurrentGamerCount.ToString() + " players"; myString += " and " + networkToJoin.OpenPublicGamerSlots.ToString() + " open player slots.";log.Add(myString);HookSessionEvents();currentGameState = GameState.InSession;}}break;case GameState.CreateSession: ... {networkSession = NetworkSession.Create(NetworkSessionType.SystemLink, 4, 16);networkSession.AllowHostMigration = true; networkSession.AllowJoinInProgress = false; log.Add("New session created");HookSessionEvents();currentGameState = GameState.InSession;}break;case GameState.InSession: ... {//send data from all local players to all other players in sessionforeach (LocalNetworkGamer localGamer in networkSession.LocalGamers)...{writer.Write(gameTime.TotalGameTime.Minutes); writer.Write(gameTime.TotalGameTime.Seconds); localGamer.SendData(writer, SendDataOptions.ReliableInOrder);}//receive data from all other players in sessionforeach (LocalNetworkGamer localGamer in networkSession.LocalGamers) ...{while (localGamer.IsDataAvailable) ... {NetworkGamer sender;localGamer.ReceiveData(reader, out sender);string gamerTime = localGamer.Gamertag + " received from "; gamerTime += sender.Gamertag + ": ";gamerTime += reader.ReadInt32() + "m ";gamerTime += reader.ReadInt32() + "s";gamerTimes[sender.Gamertag] = gamerTime;}}networkSession.Update();}break;}base.Update(gameTime);} }轉載于:https://www.cnblogs.com/AlexCheng/archive/2011/03/07/2120083.html
總結
以上是生活随笔為你收集整理的网络——在网络上发送,接收数据的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CUDA 4.0真技术解析
- 下一篇: Linux 终端操作之简明疾速指南(1)