View a markdown version of this page

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

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

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 信标域和端口信息,请使用 ListLocationsAPI 操作。此 API 返回的位置集取决于 Amazon Web Services 区域 您在调用它时指定的位置(如果您未指定,则取决于您的默认区域)。

示例

aws gamelift list-locations --region cn-north-1

API 返回该区域的 ping 信标信息。以下是返回值的示例:

{ "LocationName": "cn-north-1", "PingBeacon": { "UDPEndpoint": { "Domain": "gamelift-ping.cn-north-1.api.amazonwebservices.com.cn", "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()