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 将返回错误,因为该类型的位置没有服务端点。
请查阅中支持的地点表,以确定支持单地点和多地点舰队的主区域。
示例
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 信标实现延迟测量时,请遵循以下最佳实践:
-
使用以下方法之一存储 ping 信标信息:
-
在游戏客户端中对端点进行硬编码。
-
在游戏后端缓存信息。
-
实施定期更新机制(每天/每周)以刷新信息。
-
发送 UDP ping 消息:
-
计算延迟:
-
向每个位置发送多个 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()