如何在C#中获取服务器和客户端之间的延迟?

我正在为我在ActionScript 3中编写的游戏引擎开发C#Server应用程序。我正在使用权威的服务器模型来防止作弊并确保公平游戏。 到目前为止,一切运作良好:

当客户端开始移动时,它会告诉服务器并在本地开始渲染; 然后,服务器告诉其他人客户端X已开始移动,其中包含详细信息,以便他们也可以开始渲染。 当客户端停止移动时,它会告诉服务器,该服务器根据客户端开始移动的时间执行计算,并且客户端呈现滴答延迟并回复所有人,因此他们可以使用正确的值进行更新。

问题是,当我在服务器计算上使用默认的20ms滴答延迟时,当客户端移动相当长的距离时,当它停止时会有明显的向前倾斜。 如果我将延迟略微增加到22ms,在我的本地网络上一切都运行得非常顺利,但在其他地方,倾斜仍然存在。 经过一番实验后,我注意到所需的额外延迟几乎与客户端和服务器之间的延迟有关。 我甚至把它煮成了一个非常好的公式:延迟= 20 +(延迟/ 10)。

那么,我将如何继续获取某个客户端和服务器之间的延迟(我正在使用异步套接字)。 CPU的工作量不能太大,因为没有服务器运行缓慢。 此外,这真的是最好的方式,还是有更有效/更简单的方法来做到这一点?

很抱歉,这不是直接回答您的问题,但一般来说,您不应过分依赖测量延迟,因为它可能变化很大。 不仅如此,你不知道你测量的ping时间是否是对称的,这很重要。 如果事实certificate,20ms的ping时间实际上是从服务器到客户端的19ms,从客户端到服务器的1ms,则没有必要应用10ms的延迟校正。 应用程序术语中的延迟与网络术语中的延迟不同 – 您可能能够ping某台计算机并在20ms内获得响应,但如果您正在联系该计算机上的服务器,该服务器每秒只处理网络输入50次,那么你的回复将被延迟0到20毫秒,这将变化相当不可预测。

这并不是说延迟测量它没有平滑预测的地方,但它不会解决你的问题,只是清理一下。

从表面上看,这里的问题似乎是您在第一条消息中发送了信息,用于从收到最后一条消息之前推断数据。 如果所有其他方法保持不变,则第一条消息中给出的移动向量乘以消息之间的时间将为服务器提供客户端大致现在所处的正确结束位置 – (延迟/ 2)。 但是如果延迟发生变化,则消息之间的时间会增加或缩小。 客户可能知道他移动了10个单位,但服务器模拟他移动了9或11个单位,然后被告知将他重新安置回10个单位。

对此的一般解决方案是不假设延迟将保持不变,而是发送定期位置更新,这允许服务器validation并纠正客户端的位置。 现在只有2条消息,在第二条消息之后找到并纠正了所有错误。 随着更多消息的出现,错误会分布在更多的采样点上,从而实现更平滑,更不可见的校正。

它永远不会是完美的:它只需要在最后一毫秒的运动中出现滞后峰值,服务器的表示就会超调。 如果你根据过去的事件预测未来的运动,你无法解决这个问题,因为没有真正的选择来选择正确但迟到或不正确但是及时,因为信息需要时间旅行。 (责备爱因斯坦。)

使用基于ICMP的ping时要记住的一件事是,网络设备通常会使ICMP流量的优先级低于普通数据包,尤其是当数据包跨越网络边界(如WAN链路)时。 这可能导致ping丢失或显示出比流量实际经历的延迟更高的延迟,并且有助于成为问题的指标而不是测量工具。

在网络中越来越多地使用服务质量 (QoS)只会加剧这一点,因此尽管ping仍然是一个有用的工具,但需要理解的是,它可能不是真正反映非基于ICMP的网络延迟。交通。

Itrinegy博客上有一篇很好的post这些天你如何测量网络中的延迟(RTT)? 对这个。

您可以使用已经可用的Ping类。 应该比写你自己的恕我直言更受欢迎。

有一个“ping”命令,您可以从服务器向客户端发送消息,然后计算获取响应所需的时间。 除非CPU过载情况,它应该是非常可靠的。 要获得单程旅行时间,只需将时间除以2即可。

我们可以使用.NET Framework的Ping类来测量往返时间 。

实例化Ping并订阅PingCompleted事件:

 Ping pingSender = new Ping(); pingSender.PingCompleted += PingCompletedCallback; 

添加代码以配置和操作ping。

我们的PingCompleted事件处理程序( PingCompletedEventHandler )有一个PingCompletedEventArgs参数。 PingCompletedEventArgs.Reply我们提供了一个PingReply对象。 PingReply.RoundtripTime返回往返时间(“发送Internet控制消息协议(ICMP)回应请求所花费的毫秒数,并接收相应的ICMP回送回复消息”):

 public static void PingCompletedCallback(object sender, PingCompletedEventArgs e) { ... Console.WriteLine($"Roundtrip Time: {e.Reply.RoundtripTime}"); ... } 

基于MSDN示例的完整工作示例的代码转储。 我已将其修改为将RTT写入控制台:

 public static void Main(string[] args) { string who = "www.google.com"; AutoResetEvent waiter = new AutoResetEvent(false); Ping pingSender = new Ping(); // When the PingCompleted event is raised, // the PingCompletedCallback method is called. pingSender.PingCompleted += PingCompletedCallback; // Create a buffer of 32 bytes of data to be transmitted. string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; byte[] buffer = Encoding.ASCII.GetBytes(data); // Wait 12 seconds for a reply. int timeout = 12000; // Set options for transmission: // The data can go through 64 gateways or routers // before it is destroyed, and the data packet // cannot be fragmented. PingOptions options = new PingOptions(64, true); Console.WriteLine("Time to live: {0}", options.Ttl); Console.WriteLine("Don't fragment: {0}", options.DontFragment); // Send the ping asynchronously. // Use the waiter as the user token. // When the callback completes, it can wake up this thread. pingSender.SendAsync(who, timeout, buffer, options, waiter); // Prevent this example application from ending. // A real application should do something useful // when possible. waiter.WaitOne(); Console.WriteLine("Ping example completed."); } public static void PingCompletedCallback(object sender, PingCompletedEventArgs e) { // If the operation was canceled, display a message to the user. if (e.Cancelled) { Console.WriteLine("Ping canceled."); // Let the main thread resume. // UserToken is the AutoResetEvent object that the main thread // is waiting for. ((AutoResetEvent)e.UserState).Set(); } // If an error occurred, display the exception to the user. if (e.Error != null) { Console.WriteLine("Ping failed:"); Console.WriteLine(e.Error.ToString()); // Let the main thread resume. ((AutoResetEvent)e.UserState).Set(); } Console.WriteLine($"Roundtrip Time: {e.Reply.RoundtripTime}"); // Let the main thread resume. ((AutoResetEvent)e.UserState).Set(); } 

您可能希望执行多次ping,然后根据您的要求计算平均值。