Amazon GameLift Servers UDP ping 信标 - Amazon GameLift Servers

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

Amazon GameLift Servers UDP 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 信标域和端口信息,请使用 ListLocations API 操作。此 API 返回的位置集取决于您调用时指定的 AWS 区域(如果您未指定区域,则为默认区域)。当您从以下位置调用 API 时:

  • 支持多位置的实例集的主区域:API 返回“所有”托管位置的信息

  • 支持单位置的实例集的主区域:API 返回该位置的信息

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

请查阅支持 AWS 的地点中支持的位置表,以确定支持单位置实例集和多位置实例集的主区域。

示例

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

此 AWS 区域支持多位置实例集,因此将返回多个位置。以下是其中一个返回值示例:

[...] { "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 个事务

  3. 计算延迟:

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

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

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

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

    • 如果发送到某个位置的大部分 UDP ping 请求始终未收到响应,请使用 ICMP ping 针对标准 Amazon GameLift Servers 服务端点计算延迟,作为备用方案。

提示

我们建议您在记录玩家必须在本地网络上开放的端口列表时,添加端口 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()