Amazon GameLift ServersUDP ping 信标 - Amazon GameLift Servers
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

Amazon GameLift ServersUDP ping 信标

UDP ping 信标提供了一种测量玩家设备和Amazon GameLift Servers托管位置之间的网络延迟的方法。借助 ping 信标,您可以收集准确的延迟数据,以便就游戏服务器的放置做出明智的决定,并根据延迟要求改善玩家配对。

UDP ping 信标的工作原理

Amazon GameLift Servers在每个可以部署游戏服务器的托管位置提供固定的 UDP 端点(ping 信标)。由于大多数游戏服务器都使用 UDP 进行通信,因此与使用 ICMP ping 信标测量延迟相比,使用 UDP ping 信标测量延迟可以获得更准确的结果。网络设备处理 ICMP 数据包的方式通常与 UDP 数据包不同,这可能会导致延迟测量无法反映玩家将体验到的真实性能。

借助 UDP ping 信标,您的游戏客户端可以向这些端点发送 UDP 消息并接收异步响应,从而为您提供延迟测量值,更好地反映玩家设备和潜在托管位置之间的实际游戏流量状况。这些端点是永久性的,只要Amazon GameLift Servers支持在该位置托管游戏,它们就会一直可用。

UDP ping 信标的常见用例

您可以通过多种方式使用 UDP ping 信标来优化游戏的联网体验。

选择最佳托管地点

收集不同地理区域的延迟数据,为您的玩家群确定托管游戏服务器的最佳主位置和备用位置。

根据玩家延迟放置游戏会话

在请求新的游戏会话时包括玩家延迟数据,以帮助选择延迟体验最低的地点。

根据延迟优化配对

在请求配对时提供玩家延迟数据,以帮助匹配具有相似延迟特征的玩家,并将游戏会话放置在匹配玩家的最佳位置。

注意

创建配对请求时,您不应向没有车队的地点提供延迟信息。如果你这样做,Amazon GameLift Servers可能会尝试将游戏会话放置在没有舰队容量的地方,从而导致配对请求失败。

获取信标端点

要检索Amazon GameLift Servers位置的 ping 信标域和端口信息,请使用 ListLocationsAPI 操作。此 API 返回的位置集取决于 Amazon Web Services 区域 您在调用它时指定的位置(如果您未指定,则取决于您的默认区域)。当你从以下地址打电话时:

  • 支持多地点的舰队的主区域:API 返回所有托管地点的信息

  • 支持单一地点的舰队的主区域:API 返回该地点的信息

请注意,如果您使用只能作为多地点队列中的远程位置的位置调用此 API,则 API 将返回错误,因为该类型的位置没有服务端点。

请查阅中支持的地点表,以确定支持单地点和多地点舰队的主区域。

示例

aws gamelift list-locations --region ap-northeast-2

这 Amazon Web Services 区域 支持多地点舰队,因此将返回多个地点。以下是其中一个返回值的示例:

[...] { "LocationName": "ap-northeast-1", "PingBeacon": { "UDPEndpoint": { "Domain": "gamelift-ping.ap-northeast-1.api.aws", "Port": 7770 } } }
重要

缓存 ping 信标信息,而不是ListLocations在每次延迟测量之前调用。域和端口信息是静态的,API 不是为大量请求而设计的。

实现延迟测量

使用 UDP ping 信标实现延迟测量时,请遵循以下最佳实践:

  1. 使用以下方法之一存储 ping 信标信息:

    • 在游戏客户端中对端点进行硬编码。

    • 在游戏后端缓存信息。

    • 实施定期更新机制(每天/每周)以刷新信息。

  2. 发送 UDP ping 消息:

    • 在邮件正文中放入任何你想要的内容,只要它不为空,并且将消息的大小控制在最大 300 字节以下。

    • 请遵守每个地点的以下速率限制:

      • 每个唯一的发件人 IP 地址和端口组合每秒 3 个交易 (TPS)

      • 每个唯一的发件人 IP 地址 1000 TPS

  3. 计算延迟:

    • 向每个位置发送多个 ping 以计算平均延迟。

    • 考虑向多个位置发送并发 ping 以获得更快的结果。

    • 由于 UDP 不能 100% 保证传送,因此根据需要使用重试逻辑为任何未在短时间内(通常为 1-3 秒)返回的数据包发送新数据包。

    • 计算发送消息和接收响应之间的时间差。

    • 如果对某个位置的很大一部分 UDP ping 一直没有得到响应,请使用对我们的标准Amazon GameLift Servers服务端点的 ICMP ping 作为后备来计算延迟。

提示

我们建议您在记录玩家必须在本地网络上打开的端口列表时添加端口 7770。这也是在该端口被阻塞的情况下,您应该使用备用功能来测量延迟(例如使用 ICMP)的另一个原因。

代码示例

以下是一些简单的示例,展示了如何发送 UDP ping 和计算延迟。

C++
#include <iostream> #include <cstring> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <unistd.h> #include <chrono> int main() { // Replace with Amazon GameLift Servers UDP ping beacon domain for your desired location const char* domain = "gamelift-ping.ap-south-1.api.aws"; const int port = 7770; const char* message = "Ping"; // Your message const int num_pings = 3; // Number of pings to send // Create socket int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { std::cerr << "Error creating socket" << std::endl; return 1; } // Resolve domain name to IP address struct hostent* host = gethostbyname(domain); if (host == nullptr) { std::cerr << "Error resolving hostname" << std::endl; close(sock); return 1; } // Set up the server address structure struct sockaddr_in server_addr; std::memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port); std::memcpy(&server_addr.sin_addr, host->h_addr, host->h_length); // Set socket timeout struct timeval tv; tv.tv_sec = 1; // 1 second timeout tv.tv_usec = 0; if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { std::cerr << "Error setting socket timeout" << std::endl; close(sock); return 1; } double total_latency = 0; int successful_pings = 0; for (int i = 0; i < num_pings; ++i) { auto start = std::chrono::high_resolution_clock::now(); // Send the message ssize_t bytes_sent = sendto(sock, message, std::strlen(message), 0, (struct sockaddr*)&server_addr, sizeof(server_addr)); if (bytes_sent < 0) { std::cerr << "Error sending message" << std::endl; continue; } // Receive response char buffer[1024]; socklen_t server_addr_len = sizeof(server_addr); ssize_t bytes_received = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&server_addr, &server_addr_len); auto end = std::chrono::high_resolution_clock::now(); if (bytes_received < 0) { std::cerr << "Error receiving response or timeout" << std::endl; } else { std::chrono::duration<double, std::milli> latency = end - start; total_latency += latency.count(); successful_pings++; std::cout << "Received response, latency: " << latency.count() << " ms" << std::endl; } // Wait a bit before next ping usleep(1000000); // 1s } // Close the socket close(sock); if (successful_pings > 0) { double avg_latency = total_latency / successful_pings; std::cout << "Average latency: " << avg_latency << " ms" << std::endl; } else { std::cout << "No successful pings" << std::endl; } return 0; }
C#
using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Diagnostics; using System.Threading.Tasks; class UdpLatencyTest { static async Task Main() { // Replace with Amazon GameLift Servers UDP ping beacon domain for your desired location string domain = "gamelift-ping.ap-south-1.api.aws"; int port = 7770; string message = "Ping"; // Your message int numPings = 3; // Number of pings to send int timeoutMs = 1000; // Timeout in milliseconds await MeasureLatency(domain, port, message, numPings, timeoutMs); } static async Task MeasureLatency(string domain, int port, string message, int numPings, int timeoutMs) { using (var udpClient = new UdpClient()) { try { // Resolve domain name to IP address IPAddress[] addresses = await Dns.GetHostAddressesAsync(domain); if (addresses.Length == 0) { Console.WriteLine("Could not resolve domain name."); return; } IPEndPoint endPoint = new IPEndPoint(addresses[0], port); byte[] messageBytes = Encoding.UTF8.GetBytes(message); // Set receive timeout udpClient.Client.ReceiveTimeout = timeoutMs; double totalLatency = 0; int successfulPings = 0; var stopwatch = new Stopwatch(); for (int i = 0; i < numPings; i++) { try { stopwatch.Restart(); // Send message await udpClient.SendAsync(messageBytes, messageBytes.Length, endPoint); // Wait for response UdpReceiveResult result = await ReceiveWithTimeoutAsync(udpClient, timeoutMs); stopwatch.Stop(); double latency = stopwatch.Elapsed.TotalMilliseconds; totalLatency += latency; successfulPings++; string response = Encoding.UTF8.GetString(result.Buffer); Console.WriteLine($"Ping {i + 1}: {latency:F2}ms - Response: {response}"); } catch (SocketException ex) { Console.WriteLine($"Ping {i + 1}: Failed - {ex.Message}"); } catch (TimeoutException) { Console.WriteLine($"Ping {i + 1}: Timeout"); } // Wait before next ping await Task.Delay(1000); // 1s between pings } if (successfulPings > 0) { double averageLatency = totalLatency / successfulPings; Console.WriteLine($"\nSummary:"); Console.WriteLine($"Successful pings: {successfulPings}/{numPings}"); Console.WriteLine($"Average latency: {averageLatency:F2}ms"); } else { Console.WriteLine("\nNo successful pings"); } } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } } } static async Task<UdpReceiveResult> ReceiveWithTimeoutAsync(UdpClient client, int timeoutMs) { using var cts = new System.Threading.CancellationTokenSource(timeoutMs); try { return await client.ReceiveAsync().WaitAsync(cts.Token); } catch (OperationCanceledException) { throw new TimeoutException("Receive operation timed out"); } } }
Python
import socket import time import statistics from datetime import datetime def udp_ping(host, port, timeout=2): """ Send a UDP ping and return the round trip time in milliseconds. Returns None if timeout occurs. """ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(timeout) message = b'ping' try: # Resolve hostname first try: socket.gethostbyname(host) except socket.gaierror as e: print(f"Could not resolve hostname: {e}") return None start_time = time.time() sock.sendto(message, (host, port)) # Wait for response data, server = sock.recvfrom(1024) end_time = time.time() # Calculate round trip time in milliseconds rtt = (end_time - start_time) * 1000 return rtt except socket.timeout: print(f"Request timed out") return None except Exception as e: print(f"Error: {type(e).__name__}: {e}") return None finally: sock.close() def main(): # Replace with Amazon GameLift Servers UDP ping beacon domain for your desired location host = "gamelift-ping.ap-south-1.api.aws" port = 7770 num_pings = 3 latencies = [] print(f"\nPinging {host}:{port} {num_pings} times...") print(f"Start time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") for i in range(num_pings): print(f"Ping {i+1}:") rtt = udp_ping(host, port) if rtt is not None: print(f"Response from {host}: time={rtt:.2f}ms") latencies.append(rtt) # Wait 1 second between pings if i < num_pings - 1: time.sleep(1) print() # Calculate and display statistics print("-" * 50) print(f"Ping statistics for {host}:") print(f" Packets: Sent = {num_pings}, Received = {len(latencies)}, " f"Lost = {num_pings - len(latencies)} " f"({((num_pings - len(latencies)) / num_pings * 100):.1f}% loss)") if latencies: print("\nRound-trip latency statistics:") print(f" Minimum = {min(latencies):.2f}ms") print(f" Maximum = {max(latencies):.2f}ms") print(f" Average = {statistics.mean(latencies):.2f}ms") print(f"\nEnd time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") if __name__ == "__main__": main()