

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

# Amazon GameLift Servers UDP ping 信标
<a name="reference-udp-ping-beacons"></a>

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

## UDP ping 信标的工作原理
<a name="reference-how-beacons-work"></a>

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

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

## UDP ping 信标的常见使用案例
<a name="reference-beacons-common-use-cases"></a>

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

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

**根据玩家延迟放置游戏会话**  
在请求新的游戏会话时包括玩家延迟数据，以帮助选择延迟最低，体验最佳的位置。

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

**注意**  
创建对战请求时，请勿向没有实例集的位置提供延迟信息。否则，Amazon GameLift Servers 可能会尝试将游戏会话放置在没有实例集容量的位置，从而导致对战请求失败。

## 获取信标端点
<a name="reference-beacons-getting-endpoints"></a>

要检索Amazon GameLift Servers位置的 ping 信标域和端口信息，请使用 [ListLocations](https://docs.aws.amazon.com/gameliftservers/latest/apireference/API_ListLocations.html)API 操作。此 API 返回的位置集取决于 AWS 区域 您在调用它时指定的位置（如果您未指定，则取决于您的默认区域）。

当您从以下位置调用 API 时：
+ **支持多位置的实例集的主区域**：API 返回“所有”**托管位置的信息
+ **支持单位置的实例集的主区域**：API 返回该位置的信息

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

请查阅[支持 AWS 的地点](gamelift-regions.md#gamelift-regions-hosting-home)中支持的位置表，以确定支持单位置实例集和多位置实例集的主区域。

**示例**

```
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 并非设计用于处理大量请求。

## 实施延迟测量
<a name="reference-beacons-measuring-latency"></a>

在使用 UDP ping 信标实施延迟测量时，请遵循以下最佳实践：

1. 使用以下方法之一存储 ping 信标信息：
   + 在游戏客户端中对端点进行硬编码。
   + 在游戏后端缓存信息。
   + 实施定期更新机制（每天/每周）以刷新信息。

1. 发送 UDP ping 消息：
   + 消息正文可包含任意内容（不可为空），并且消息大小不超过 300 字节。
   + 请遵守每个位置的以下速率限制：
     + 每个唯一发件人 IP 地址和端口组合：每秒 3 个事务（TPS）
     + 每个唯一发件人 IP 地址：每秒 1000 个事务

1. 计算延迟：
   + 向每个位置发送多个 ping 以计算平均延迟。
   + 考虑向多个位置发送并发 ping，以更快地获得结果。
   + 由于 UDP 不能 100% 保证传送，因此对于任何未在短时间内（通常为 1-3 秒）返回的数据包，应根据需要使用重试逻辑发送新数据包。
   + 计算发送消息和接收响应之间的时间差。
   + 如果发送到某个位置的大部分 UDP ping 请求始终未收到响应，请使用 ICMP ping 针对标准 [Amazon GameLift Servers 服务端点](https://docs.aws.amazon.com/general/latest/gr/gamelift.html#gamelift_region)计算延迟，作为备用方案。

**提示**  
我们建议您在记录玩家必须在本地网络上开放的端口列表时，添加端口 7770。这也正是您需要准备备用延迟测量方案（例如使用 ICMP）的另一个原因，以防此端口被阻塞。

## 代码示例
<a name="reference-beacons-code-examples"></a>

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

------
#### [ C\$1\$1 ]

```
#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\$1 ]

```
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()
```

------