网络——在网络上发送,接收数据
問題
創(chuàng)建并加入一個網(wǎng)絡(luò)會話是一回事,但如果不能發(fā)送或接收任何數(shù)據(jù)那么網(wǎng)絡(luò)會話有什么用呢?
解決方案
當(dāng)玩家連接到會話時,你可以在一個PacketWriter流中存儲所有想要發(fā)送的數(shù)據(jù)。完成這個操作后,你可以使用LocalNetworkPlayer.SendData方法將這個PacketWriter發(fā)送給會話中的所有玩家。
在玩家接收數(shù)據(jù)前,你應(yīng)該檢查他們的LocalNetworkGamer. IsDataAvailable是否被設(shè)為ture,這表示數(shù)據(jù)已經(jīng)被接收并做好了處理的準(zhǔn)備。
一旦IsDataAvailable為true,你就可以調(diào)用LocalNetworkGamer.ReceiveData方法,返回一個包含另一個玩家發(fā)送給本地玩家的所有數(shù)據(jù)的PacketReader。
工作原理
這個教程建立在前一個教程結(jié)果的基礎(chǔ)上,前一個教程允許同一網(wǎng)絡(luò)上的多個機(jī)器通過一個會話互聯(lián)。程序結(jié)束于InSession狀態(tài),這個狀態(tài)只是簡單地調(diào)用會話的Update方法。
現(xiàn)在,你將在InSession狀態(tài)中做點(diǎn)實(shí)際的操作,讓你的玩家可以將一些數(shù)據(jù)發(fā)送到會話中的其他玩家那里。本例中,你將發(fā)送程序運(yùn)行的分鐘數(shù)和秒數(shù)。
然后,你監(jiān)聽可用的數(shù)據(jù)。如果有可用的數(shù)據(jù),你會接收兩個數(shù)字,將它們放在一個字符串中,顯示在屏幕上。
要發(fā)送和接收數(shù)據(jù),你需要一個PacketWriter對象和一個PacketReader對象,所以在代碼中添加這兩個變量:
PacketWriter writer = new PacketWriter(); PacketReader reader = new PacketReader();在項(xiàng)目中使用超過一個的PacketWriter對象和PacketReader對象是毫無理由的。
將數(shù)據(jù)發(fā)送到會話中的另一個玩家
你需要在PacketWriter中存儲所有要發(fā)送給其他玩家的數(shù)據(jù),這可以通過將數(shù)據(jù)作為Write方法的參數(shù)做到:
writer.Write(gameTime.TotalGameTime.Minutes); writer.Write(gameTime.TotalGameTime.Seconds);在將所有數(shù)據(jù)存儲到PacketWriter之后,你可以使用本地玩家的SendData方法將它發(fā)送到所有其他用戶:
LocalNetworkGamer localGamer = networkSession.LocalGamers[0]; localGamer.SendData(writer, SendDataOptions.None);SendDataOptions參數(shù)會在教程的最后解釋。更重要的是,SendData有一個重載方法可以只將數(shù)據(jù)發(fā)送到指定玩家而不是會話中的所有玩家。
以上就是將數(shù)據(jù)發(fā)送到會話中的其他玩家需要的所用操作。
從會話中的另一個玩家處接收數(shù)據(jù)
從其他玩家接收數(shù)據(jù)大致就是反過程:調(diào)用本地玩家的ReceiveData方法,這會返回一個包含其他玩家發(fā)送數(shù)據(jù)的PacketReader。調(diào)用PacketReader. Read方法中的一個從PacketReader獲取數(shù)據(jù):
NetworkGamer sender; localGamer.ReceiveData(reader, out sender);string playerName = sender.Gamertag; int minutes = reader.ReadInt32(); int seconds = reader.ReadInt32();ReceiveData方法存儲PacketReader流中的數(shù)據(jù),發(fā)送給你數(shù)據(jù)的玩家會存儲在第二個參數(shù)中,這樣你就可以知道數(shù)據(jù)來自于誰。
當(dāng)從PacketReader讀取數(shù)據(jù)時,你需要確保以與發(fā)送相同的順序進(jìn)行讀取。而且,因?yàn)镻acketReader只包含字節(jié)流,你需要告知你想從字節(jié)構(gòu)建哪個對象。例如,一個整數(shù)需要的字節(jié)比矩陣少,所以需要在某些時候告知你想恢復(fù)為哪種類型的對象。
本例中分鐘數(shù)和秒數(shù)為整數(shù),所以你想從字節(jié)流中重新構(gòu)建兩個整數(shù)。看一下PacketReader的不同Read方法,注意支持哪個對象。如果你想重構(gòu)矩陣,則應(yīng)該使用ReadMatrix方法,使用ReadSingle方法重構(gòu)float,ReadDouble方法重構(gòu)double,ReadInt16 重構(gòu)short。
LocalGamer.IsDataAvailable
如果多個玩家向你發(fā)送數(shù)據(jù),可能會有多個字節(jié)流需要被讀取,這種情況也會發(fā)生在其他玩家調(diào)用SendData的頻率大于你調(diào)用ReceiveData的頻率時。
在這種情況下,你可以查詢localGamer.IsDataAvailable屬性,因?yàn)橹灰幸粋€字節(jié)流正在等待本地游戲,這個屬性就會為true。
只要數(shù)據(jù)對你的玩家可用,下面的代碼就會接收一個新PacketReader并讀取發(fā)送數(shù)據(jù)的玩家的GamerTag屬性。然后,玩家程序運(yùn)行的分鐘數(shù)和秒數(shù)就會從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; }要讓這個例子實(shí)際干點(diǎn)事情,數(shù)據(jù)被轉(zhuǎn)換到一個叫做gamerTime的字符串中,它存儲在一個Dictionary。Dictionary是默認(rèn)的generic .NET查詢表,可以使用以下代碼創(chuàng)建:
Dictionary<string, string> gamerTimes = new Dictionary<string, string>();前面的代碼會在Dictionary中創(chuàng)建一個數(shù)據(jù)項(xiàng),對應(yīng)發(fā)送給你數(shù)據(jù)的玩家。當(dāng)從一個玩家接收新數(shù)據(jù)時,Dictionary中的對應(yīng)數(shù)據(jù)項(xiàng)會被更新,你可以在Draw方法中將Dictionary中的字符串顯示在屏幕上。
當(dāng)玩家離開會話時,你需要將它們對應(yīng)的數(shù)據(jù)項(xiàng)從Dictionary移除,這可以在GamerLeft 事件中加以處理:
void GamerLeftEventHandler(object sender, GamerLeftEventArgs e) ...{log.Add(e.Gamer.Gamertag + " left the current session"); gamerTimes.Remove(e.Gamer.Gamertag); }SendDataOptions
當(dāng)你將數(shù)據(jù)發(fā)送到會話中的其他玩家時,你期望到達(dá)接受者的信息的順序與發(fā)送的順序是相同的,但是基于Internet的原理,你的信息可能會以不同于發(fā)送的順序到達(dá),甚至更糟,有些數(shù)據(jù)可能根本就傳不到!
幸運(yùn)的是,你可以為發(fā)送的數(shù)據(jù)包指定兩個重要的系數(shù),在使用前你需要知道它們是什么,好處是什么,更重要的是,它們的缺點(diǎn)是什么:
- 到達(dá)的順序(Order of arrival):數(shù)據(jù)包接收的順序是否與發(fā)送的順序相同?
- 安全性(Reliability):你發(fā)送的這個數(shù)據(jù)是否是至關(guān)緊要的,如果數(shù)據(jù)包丟失游戲還能進(jìn)行嗎?
以上兩個問題不是是就是否,可以提供四種可能性。LocalNetworkGamer.SendData可以將SendDataOptions作為第二個參數(shù),這個參數(shù)可以讓你指定四種情況中的一個:
- SendDataOptions.None:發(fā)送的數(shù)據(jù)不是關(guān)鍵的,你接收數(shù)據(jù)的順序也無關(guān)緊要。
- SendDataOptions.InOrder:接收數(shù)據(jù)的順序必須和發(fā)送它們的順序相同,但一些數(shù)據(jù)包丟失無大礙。
- SendDataOptions.Reliable:與SendDatOptions.InOrder相反,你的數(shù)據(jù)是關(guān)鍵的,你發(fā)送的所有數(shù)據(jù)必須到達(dá)接收者。但是,接收數(shù)據(jù)的順序是否和發(fā)送的順序相同無關(guān)緊要。
- SendDataOptions.ReliableInOrder:所有的數(shù)據(jù)必須以和發(fā)送順序相同的順序到達(dá)接收者。
不是很難,我選擇最后一個!有些選項(xiàng)有點(diǎn)缺點(diǎn),解釋如下:
- SendDataOptions.None:沒有速度損失,只能指望數(shù)據(jù)能夠成功發(fā)送。
- SendDataOptions.InOrder:在數(shù)據(jù)發(fā)送前,所有的數(shù)據(jù)包被分配了一個序號。接收者檢查這個序號,如果數(shù)據(jù)包A在一個更加新的數(shù)據(jù)包B之后被接收,數(shù)據(jù)包A會被拋棄。這是個簡單的檢查方法,幾乎不花時間,但即使有些數(shù)據(jù)成功到達(dá)了目的地可能也會被拋棄。
- SendDataOptions.Reliable:接收者會檢查丟失了哪個數(shù)據(jù)包。當(dāng)數(shù)據(jù)包C從數(shù)據(jù)包流ABDE中丟失時,接收者會要求發(fā)送者重新發(fā)送數(shù)據(jù)包C,同時,數(shù)據(jù)包D和E在XNA代碼中可以被訪問。
- SendDataOptions.ReliableInOrder:只有在你的數(shù)據(jù)需要時才使用這個選項(xiàng)。當(dāng)接收者法線數(shù)據(jù)包C從流ABDE中丟失時,它會讓發(fā)送者重新發(fā)送數(shù)據(jù)包C。這次,其后的數(shù)據(jù)包D和E不會被接收者傳遞到XNA中,直到數(shù)據(jù)包C也被成功的傳遞。這會引起延遲,因?yàn)樗泻罄^的數(shù)據(jù)包會保存在內(nèi)存中直至數(shù)據(jù)包C被重新發(fā)送并收到才會被傳遞到XNA程序中。
普遍的原則是,對大多數(shù)數(shù)據(jù)來說SendDataOptions.InOrder是安全的,盡可能不要使用 SendDataOption.ReliableInOrder。
SendDataOption.Chat
在你開始發(fā)送數(shù)據(jù)前,你需要記住一件事情:在Internet上發(fā)送的聊天信息(chat message)不可以被加密,法律上是禁止的。
因?yàn)槟J(rèn)情況下使用localGamer.SendData方法發(fā)送數(shù)據(jù)都會進(jìn)行加密,你必須使用SendDataOptions.Chat表示XNA不要加密聊天信息。你也可以使用SendDataOption的組合,如下所示:
localGamer.SendData(write,SengDataOptions.Chat|SendDataOptions.Reliable);注意你可以發(fā)送加密和不加密混合的信息。如果你這樣干,排序過的聊天信息只根據(jù)聊天數(shù)據(jù)排序而不是根據(jù)加密過的數(shù)據(jù)。例如,讓我們看一下發(fā)送第一條信息時的情況,依次是數(shù)據(jù)信息、聊天信息、數(shù)據(jù)信息,如圖8-1左圖所示。
圖8-1 順序發(fā)送4個數(shù)據(jù)包(左圖)和4種接收數(shù)據(jù)的方式
圖8-1的右圖顯示了數(shù)據(jù)如何到達(dá)接收端的4中可能方式。在a情況中,數(shù)據(jù)到達(dá)的順序與發(fā)送的順序一樣。在情況b和c中,數(shù)據(jù)包的順序發(fā)生了改變,但是,這兩種情況中第一個聊天數(shù)據(jù)包A在在第二個聊天數(shù)據(jù)包C之前被接收,第一個數(shù)據(jù)包B在第二個數(shù)據(jù)包D之前。
因?yàn)閮蓚€數(shù)據(jù)包和兩個聊天數(shù)據(jù)包可以在一幀中被發(fā)送,你需要確保在接收端將它們混合起來,一個方法是在發(fā)送數(shù)據(jù)包前給它們添加一個小說明,表示它們是數(shù)據(jù)包還是聊天數(shù)據(jù)包,看一下下面的代碼,其中D表示一個數(shù)據(jù)包,C表示一個聊天數(shù)據(jù)包:
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);在接收端,只是簡單地檢查數(shù)據(jù)包是D還是C,并處理對應(yīng)的數(shù)據(jù)包:
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();} }多個本地玩家
如果多個玩家連接在同一個機(jī)器上,你需要通過迭代器發(fā)送和接受數(shù)據(jù)。
將數(shù)據(jù)發(fā)送到所有玩家很簡單:
//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的一個重載方法將數(shù)據(jù)只發(fā)送到一個指定玩家。
接收數(shù)據(jù)也不難,只需循環(huán)代碼直到所有本地玩家的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方法的代碼,包括擴(kuò)展過的InSession狀態(tài),你的機(jī)器上的所有玩家將數(shù)據(jù)發(fā)送到會話中的所有玩家。然后,他們會接收發(fā)送給他們的數(shù)據(jù)。
如果你在多個機(jī)器上運(yùn)行這個代碼,他們會自動連接到第一個機(jī)器創(chuàng)建的會話上。然后,開始發(fā)送時間信息并在Draw方法中將接受到的數(shù)據(jù)顯示在屏幕上。
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);} }轉(zhuǎn)載于:https://www.cnblogs.com/AlexCheng/archive/2011/03/07/2120083.html
總結(jié)
以上是生活随笔為你收集整理的网络——在网络上发送,接收数据的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CUDA 4.0真技术解析
- 下一篇: 榜单类应用我所喜欢的算法