

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 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 信標測量延遲可提供更準確的結果。網路裝置通常會以不同於 UDP 封包的方式處理 ICMP 封包，這可能會導致延遲測量，而不會反映玩家將體驗到的真正效能。

使用 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 將傳回錯誤，因為該類型的位置沒有服務端點。

請參閱 中支援的位置表格[支援 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 TPS

1. 計算延遲：
   + 將多個 Ping 傳送至每個位置，以計算平均延遲。
   + 考慮將並行 ping 傳送到多個位置，以提高結果。
   + 視需要使用重試邏輯，為未在短時間內傳回的任何封包傳送新封包 （通常是 1 - 3 秒），因為 UDP 沒有 100% 保證交付。
   + 計算傳送訊息和接收回應之間的時間差異。
   + 如果對某個位置的大部分 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()
```

------