

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# Amazon GameLift Servers UDP 핑 비콘
<a name="reference-udp-ping-beacons"></a>

UDP 핑 비콘은 플레이어 디바이스와 Amazon GameLift Servers 호스팅 위치 간의 네트워크 지연 시간을 측정할 수 있는 방법을 제공합니다. 핑 비콘을 사용하면 정확한 지연 시간 데이터를 수집하여 게임 서버 배치에 대해 정보에 입각한 결정을 내리고, 지연 시간 요구 사항에 따라 플레이어 매치메이킹을 개선할 수 있습니다.

## UDP 핑 비콘 작동 방식
<a name="reference-how-beacons-work"></a>

Amazon GameLift Servers는 게임 서버를 배포할 수 있는 각 호스팅 위치에 고정 UDP 엔드포인트(핑 비콘)를 제공합니다. 대부분의 게임 서버는 UDP를 사용하여 통신하므로 UDP 핑 비콘을 사용하여 지연 시간을 측정하면 ICMP 핑을 사용하는 것보다 더 정확한 결과를 얻을 수 있습니다. 네트워크 디바이스는 종종 ICMP 패킷을 UDP 패킷과 다르게 처리하므로 플레이어가 경험할 실제 성능을 반영하지 않는 지연 시간 측정이 발생할 수 있습니다.

UDP 핑 비콘을 사용하면 게임 클라이언트가 이러한 엔드포인트에 UDP 메시지를 보내고 비동기 응답을 수신하여 플레이어의 디바이스와 잠재적 호스팅 위치 간의 실제 게임 트래픽 조건을 더 잘 나타내는 지연 시간 측정값을 제공할 수 있습니다. 엔드포인트는 영구적이며 Amazon GameLift Servers가 해당 위치에서 게임 호스팅을 지원하는 한 계속 사용할 수 있습니다.

## UDP 핑 비콘의 일반적인 사용 사례
<a name="reference-beacons-common-use-cases"></a>

여러 가지 방법으로 UDP 핑 비콘을 사용하여 게임의 네트워킹 경험을 최적화할 수 있습니다.

**최적의 호스팅 위치 선택**  
다양한 지리적 리전에서 지연 시간 데이터를 수집하여 플레이어 기반에 맞게 게임 서버를 호스팅하는 데 가장 적합한 기본 및 백업 위치를 식별합니다.

**플레이어 지연 시간을 기반으로 게임 세션 배치**  
새 게임 세션을 요청할 때 플레이어 지연 시간 데이터를 포함하면 지연 시간이 가장 짧은 환경을 제공하는 위치를 선택하는 데 도움이 됩니다.

**지연 시간을 기반으로 매치메이킹 최적화**  
매치메이킹을 요청할 때 플레이어 지연 시간 데이터를 제공하여 유사한 지연 시간 프로필을 가진 플레이어를 매칭하고 매치된 플레이어를 위한 최적의 위치에 게임 세션을 배치합니다.

**참고**  
매치메이킹 요청을 생성할 때 플릿이 없는 위치에 지연 시간 정보를 제공해서는 안 됩니다. 이렇게 하면 Amazon GameLift Servers가 플릿 용량이 없는 위치에 게임 세션을 배치하려고 시도하여 매치메이킹 요청에 실패할 수 있습니다.

## 비콘 엔드포인트 가져오기
<a name="reference-beacons-getting-endpoints"></a>

Amazon GameLift Servers 위치에 대한 핑 비콘 도메인 및 포트 정보를 검색하려면 [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
        }
    }
}
```

**중요**  
각 지연 시간 측정 전에 `ListLocations`를 호출하는 대신 핑 비콘 정보를 캐시합니다. 도메인 및 포트 정보는 정적이며 API는 대용량 요청을 위해 설계되지 않았습니다.

## 지연 시간 측정 구현
<a name="reference-beacons-measuring-latency"></a>

UDP 핑 비콘을 사용하여 지연 시간 측정을 구현할 때는 다음 모범 사례를 따르세요.

1. 다음 방법 중 하나를 사용하여 핑 비콘 정보를 저장합니다.
   + 게임 클라이언트의 엔드포인트를 하드코딩합니다.
   + 게임 백엔드의 정보를 캐시합니다.
   + 정기 업데이트 메커니즘(일별/주별)을 구현하여 정보를 새로 고칩니다.

1. UDP 핑 메시지 전송:
   + 비어 있지 않은 한 메시지 본문에 원하는 내용을 넣고 메시지를 최대 크기인 300바이트로 유지합니다.
   + 각 위치에 대해 다음 속도 제한을 준수합니다.
     + 고유한 발신자 IP 주소 및 포트 조합당 초당 3개의 트랜잭션(TPS)
     + 고유한 발신자 IP 주소당 1000TPS

1. 지연 시간 계산:
   + 각 위치에 여러 핑을 전송하여 평균 지연 시간을 계산합니다.
   + 더 빠른 결과를 얻으려면 동시 핑을 여러 위치로 보내는 것이 좋습니다.
   + UDP는 전송을 100% 보장하지 않으므로 필요에 따라 재시도 로직을 사용하여 짧은 시간(일반적으로 1\$13초) 내에 반환되지 않은 패킷에 대해 새 패킷을 전송합니다.
   + 메시지 전송과 응답 수신 간의 시간 차이를 계산합니다.
   + 위치에 대한 UDP 핑의 대부분이 지속적으로 응답을 받지 못하는 경우 표준 [Amazon GameLift Servers 서비스 엔드포인트](https://docs.aws.amazon.com/general/latest/gr/gamelift.html#gamelift_region)에 대한 ICMP 핑을 대체로 사용하여 지연 시간을 계산합니다.

**작은 정보**  
플레이어가 로컬 네트워크에서 열어야 하는 포트 목록을 문서화하는 위치에 포트 7770을 포함하는 것이 좋습니다. 이는 이 포트가 차단된 경우 지연 시간을 측정하기 위한 대체(예: ICMP 사용)가 있어야 하는 또 다른 이유입니다.

## 코드 예제
<a name="reference-beacons-code-examples"></a>

다음은 UDP 핑을 보내고 지연 시간을 계산하는 방법을 보여주는 몇 가지 간단한 예제입니다.

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

------