本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
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 将返回错误,因为该类型的位置没有服务端点。
请查阅支持 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 信标实施延迟测量时,请遵循以下最佳实践:
-
使用以下方法之一存储 ping 信标信息:
-
在游戏客户端中对端点进行硬编码。
-
在游戏后端缓存信息。
-
实施定期更新机制(每天/每周)以刷新信息。
-
发送 UDP ping 消息:
-
计算延迟:
-
向每个位置发送多个 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()