问题

创建并加入一个网络会话是一回事,但如果不能发送或接收任何数据那么网络会话有什么用呢?

解决方案

当玩家连接到会话时,你可以在一个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

网络——在网络上发送,接收数据相关推荐

  1. 使用c#实现tcp的连接和发送接收数据

    最近有个小项目,需要调用装置的录波数据,使用tcp模式,在这里整理了下如何使用c#实现tcp连接并实现发送接收数据,分享出来. 我这里使用的tcpclient ,终端是tcpserver模式. 首先自 ...

  2. 安卓Socket连接实现连接实现发送接收数据,openwrt wifi转串口连接单片机实现控制...

    安卓Socket连接实现连接实现发送接收数据,openwrt wifi转串口连接单片机实现控制 socket 连接采用流的方式进行发送接收数据,采用thread线程的方式. 什么是线程?  详细代码介 ...

  3. Python3树莓派连接阿里云物联网设备发送接收数据

    Python3连接阿里云物联网设备发送接收数据(树莓派) 阿里云物联网IOT 代码部分 库文件 Windows下安装环境 树莓派安装环境 可能遇到的错误 代码 效果展示 阿里云物联网IOT 首先,准备 ...

  4. Android发送接收WiFi,安卓Socket连接实现连接实现发送接收数据,openwrt wifi转串口连接单片机实现控制,安卓openwrt...

    安卓Socket连接实现连接实现发送接收数据,openwrt wifi转串口连接单片机实现控制,安卓openwrt 安卓Socket连接实现连接实现发送接收数据,openwrt wifi转串口连接单片 ...

  5. 求android 中串口的发送接收数据代码

    RT,求高手帮忙! 就是 /dev/ttyS0 和/dev/ttyS1 两个设备的通信问题.. 同求~ 这个是不是需要串口驱动啊?最近正在搞这个串口通信的案子,头疼 同样也没有搞出来,老是报:不能扫描 ...

  6. 微信小程序连接蓝牙 并分包发送 接收数据完整版

    微信小程序连接蓝牙并分包发送接收数据 初始化蓝牙 初始化蓝牙设备 搜索蓝牙设备 连接蓝牙设备 获取蓝牙设备所有service(支持读写的) 向蓝牙发送数据 断开蓝牙 停止搜索蓝牙 转16进制 Arra ...

  7. html表单发送json,在HTML表单上发送JSON数据提交

    我有一个html表单,该表单有两个字段(名称,说明).当用户点击该表单的提交按钮时,我想以json格式提交表单数据.在HTML表单上发送JSON数据提交 我试过如下: function submitD ...

  8. 51单片机模拟串口发送接收数据(不使用SBUF)

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 51单片机.模拟串口.串口发送.串口接收.逻辑分析仪 前言 一.配置定时器 二.串口发送 三.串口接收 四.主函数 五.波形图 5.1 ...

  9. jrtplib开源库系列之三:jrtplib发送接收数据流程

    说明 前面2篇文章主要说明了如何安装jrtplib库,以及对example1进行了说明,这篇文章主要说下jrtplib库数据的收发流程. 数据收发流程 从例子1就可以很好的说明jrtplib的使用是非 ...

  10. 无线433发送接收数据测试程序原理,有杂波解决方案

    无线433发送接收测试程序 433Mhz无线通信相关知识不做介绍,网上很多避坑:MCU的接收引脚Data脚,是否配置上拉要参考MCU内部上拉电阻的大小,使用时最好拿示波器测量高低电平对应的电压. 此测 ...

最新文章

  1. [BTS06]BizTalk2006 SDK阅读笔记(五) 管道-Pipeline
  2. LeetCode Flatten a Multilevel Doubly Linked List(dfs)
  3. unbuntu18 netplan 配置固定IP
  4. 带你了解Java这么火爆的真实原因!
  5. 王彪-20162321《程序设计与数据结构2nd》-第十一周学习总结与实验报告
  6. 机器学习笔记——偏差vs方差
  7. java 继承 String类
  8. jmeter聚合报告详解
  9. Windows Server 2012安装vc++组件失败
  10. 软件测试到底要不要报培训班?
  11. APISpace 空号检测API接口 免费好用
  12. 【Bye-Bye】MMD镜头+动作打包下载.zip
  13. 第一次出书的经验分享
  14. 液压与气压传动原理及实验装置,QY-QD11
  15. 连接WiFi电脑却无法上网
  16. 明日方舟登录时服务器显示泰拉,明日方舟泰拉档案馆使用说明
  17. HDFS存储大量小文件居然有这样的问题!看我怎么搞定它!
  18. ABP入门系列之1——ABP总体介绍
  19. lte tm模式_LTE 的传输模式及各自的区别和作用
  20. 报错:Error: module property was removed from Dependency

热门文章

  1. k8s灰度更新_通过rancher部署k8s过程实战分享
  2. nacos oaut服务地址_用户认证的例子:Spring Security oAuth2 + Spring Cloud Gateway + Nacos + Dubbo...
  3. 进程wait()与waitpid()
  4. 51nod 1118 机器人走方格 解题思路:动态规划 1119 机器人走方格 V2 解题思路:根据杨辉三角转化问题为组合数和求逆元问题
  5. Apache(2)——进程与模块
  6. Linux命令(11)—— 给文件增加和减少权限chomod命令
  7. VMWare安装DOS系统实现文件共享
  8. CF-1207 F. Remainder Problem(分块)
  9. 阿里云centos mysql_阿里云ECS服务器CentOS7上安装MySql服务-阿里云开发者社区
  10. linux服务器cuda,cudnn的安装与卸载