> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 使用服务器回填工单 - Multiplayer SDK

> 了解如何使用 Multiplayer SDK 创建并使用 PlayFab 服务器回填工单,以填补正在进行的匹配游戏会话中的空缺位置。

服务器上托管的游戏有时会需要搜索额外的玩家。这种情况最常发生在游戏进行过程中一名或多名玩家断开连接时。服务器回填工单允许游戏服务器搜索适合当前正在进行的游戏的额外玩家。

服务器回填工单在多个方面与常规匹配工单不同:

1. 匹配
   * 回填工单彼此之间不能匹配。
   * 回填工单在搜索期间被赋予优先级,可减少玩家群体的碎片化。
2. 契约
   * 可使用 `ServerDetails` 字段创建回填工单。这允许服务器指示已匹配的玩家应如何连接到该服务器。
   * 可使用团队分配创建回填工单。这允许具有团队机制的游戏保持其团队信息。
3. 队列属性
   * 回填工单不会触发 [Multiplayer Server 分配](/services/playfab/multiplayer/matchmaking/multiplayer-servers)。
   * 回填工单不会反映在[队列统计信息](/services/playfab/multiplayer/matchmaking/display-statistics)中,因为其玩家已经在进行游戏,会不准确地影响等待时间。
4. 所有权
   * 回填工单由游戏服务器所有,而非用户。用户无法以任何方式查看或操作回填工单。

## 先决条件

* 熟悉 PlayFab Multiplayer SDK 的基本知识。有关更多信息,请参阅 [Matchmaking SDK 快速入门](/services/playfab/multiplayer/matchmaking/quickstart-client-sdk)。
* 在包含匹配头文件之前定义 `PFMULTIPLAYER_INCLUDE_SERVER_APIS`。例如:

```cpp theme={null}
#define PFMULTIPLAYER_INCLUDE_SERVER_APIS
#include <PFMatchmaking.h>
```

## 配置服务器回填工单

创建并填充一个 [PFMatchmakingServerBackfillTicketConfiguration](/services/playfab/multiplayer/lobby/playfabmultiplayerreference-cpp/pfmatchmaking/structs/pfmatchmakingserverbackfillticketconfiguration) 结构,包含必要的详细信息:

* **`timeoutInSeconds`**:尝试填充工单的时长(以秒为单位)。

* **`queueName`**:匹配队列的名称。

* **`memberCount`**:当前对局中的成员数量。

* **`members`**:当前对局中的 [PFMatchmakingMatchMember](/services/playfab/multiplayer/lobby/playfabmultiplayerreference-cpp/pfmatchmaking/structs/pfmatchmakingmatchmember) 成员。

* **`serverDetails`**(可选):使用有关服务器的信息(FQDN、IP 地址、端口、区域)填充 [PFMultiplayerServerDetails](/services/playfab/multiplayer/lobby/playfabmultiplayerreference-cpp/pfmatchmaking/structs/pfmultiplayerserverdetails),这些信息将提供给客户端。

<Note>
  `PFMultiplayerServerDetails::ipv4Address` 字段不会被验证,可用于向客户端提供任意的连接字符串信息。
</Note>

```cpp theme={null}
// Define the server port configuration.
PFMultiplayerPort serverPorts[] = {
    { "portname", 12345, PFMultiplayerProtocolType::Udp }
};

// Populate the server details.
PFMultiplayerServerDetails serverDetails = {};
serverDetails.fqdn = "your.server.fqdn.com";
serverDetails.ipv4Address = "123.234.123.234";
serverDetails.ports = serverPorts;
serverDetails.portCount = sizeof(serverPorts) / sizeof(serverPorts[0]);
serverDetails.region = "EastUS";

// Set up the backfill ticket configuration.
PFMatchmakingServerBackfillTicketConfiguration backfillConfig = {};
backfillConfig.timeoutInSeconds = 60;                 // Try for 60 seconds
backfillConfig.queueName = "YourQueueName";
backfillConfig.memberCount = currentMatchMemberCount; // e.g., 4
backfillConfig.members = currentMatchMembers;         // Pointer to an array of PFMatchmakingMatchMember
backfillConfig.serverDetails = &serverDetails;        // Optional; can be nullptr if not needed
```

## 创建服务器回填工单

使用 [PFMultiplayerCreateServerBackfillTicket](/services/playfab/multiplayer/lobby/playfabmultiplayerreference-cpp/pfmatchmaking/functions/pfmultiplayercreateserverbackfillticket) 创建服务器回填工单时,需要传入游戏服务器的实体(作为 [PFEntityKey](/services/playfab/multiplayer/lobby/playfabmultiplayerreference-cpp/pfmultiplayer/pfentitykey_clientsdk))以及在上一步中创建的回填配置。

```cpp theme={null}
PFMatchmakingTicketHandle backfillTicket = nullptr;
HRESULT hr = PFMultiplayerCreateServerBackfillTicket(
    multiplayerHandle,            // The handle of the PFMultiplayer API instance.
    &serverEntity,                // PFEntityKey for your game server entity
    &backfillConfig,              // The backfill ticket configuration.
    nullptr,                      // Optional async context
    &backfillTicket               // The resulting ticket object.
);

if (FAILED(hr))
{
    // handle ticket creation failure
}
```

## 检查匹配工单的状态

必须通过调用 [PFMultiplayerStartProcessingMatchmakingStateChanges](/services/playfab/multiplayer/lobby/playfabmultiplayerreference-cpp/pfmatchmaking/functions/pfmultiplayerstartprocessingmatchmakingstatechanges) 来接收状态变更,并在处理完这些状态变更后调用 [PFMultiplayerFinishProcessingMatchmakingStateChanges](/services/playfab/multiplayer/lobby/playfabmultiplayerreference-cpp/pfmatchmaking/functions/pfmultiplayerfinishprocessingmatchmakingstatechanges),以检查工单的更新。

每当工单状态发生变化时,SDK 会返回 **TicketStatusChanged** 状态变更;当匹配已完成时,SDK 会返回 **TicketCompleted** 状态变更。

```cpp theme={null}
HRESULT hrTicketError = S_OK;

uint32_t stateChangeCount;
const PFMatchmakingStateChange * const * stateChanges;
HRESULT hr = PFMultiplayerStartProcessingMatchmakingStateChanges(g_pfmHandle, &stateChangeCount, &stateChanges);
if (FAILED(hr))  
{  
    return;  
}  

for (uint32_t i = 0; i < stateChangeCount; ++i)
{
    const PFMatchmakingStateChange& stateChange = *stateChanges[i];

    switch (stateChange.stateChangeType)
    {
        case PFMatchmakingStateChangeType::TicketStatusChanged:
        {
            const auto& ticketStatusChanged = static_cast<const PFMatchmakingTicketStatusChangedStateChange&>(stateChange);

            PFMatchmakingTicketStatus status;
            if (SUCCEEDED(PFMatchmakingTicketGetStatus(ticketStatusChanged.ticket, &status)))
            {
                printf("Ticket status is now: %i.\n", status);
            }

            break;
        }
        case PFMatchmakingStateChangeType::TicketCompleted:
        {
            const auto& ticketCompleted = static_cast<const PFMatchmakingTicketCompletedStateChange&>(stateChange);

            printf("PFMatchmaking completed with Result 0x%08x.\n", ticketCompleted.result);

            if (FAILED(ticketCompleted.result))
            {
                // On failure, we must record the HRESULT so we can return the state change(s) and then bail
                // out of this function.
                hrTicketError = ticketCompleted.result;
            }

            break;
        }
    }
}

hr = PFMultiplayerFinishProcessingMatchmakingStateChanges(g_pfmHandle, stateChangeCount, stateChanges);
if (FAILED(hr))  
{  
    return;  
}  

// Now that we've returned the state change(s), bail out if we detected ticket failure.
if (FAILED(hrTicketError))  
{  
    return;  
}  
```

## 获取匹配

在收到 **PFMatchmakingStateChangeType::TicketCompleted** 状态变更后,调用 [PFMatchmakingTicketGetMatch](/services/playfab/multiplayer/lobby/playfabmultiplayerreference-cpp/pfmatchmaking/functions/pfmatchmakingticketgetmatch) 以获取对局的详细信息。这些详细信息包含匹配 ID、已匹配到一起的用户、对局的首选区域,以及与对局关联的大厅编排字符串。

从 **PFMatchmakingMatchDetails** 结构中检索所需的信息后,应使用 [PFMultiplayerDestroyMatchmakingTicket](/services/playfab/multiplayer/lobby/playfabmultiplayerreference-cpp/pfmatchmaking/functions/pfmultiplayerdestroymatchmakingticket) 销毁工单。

```cpp theme={null}
const PFMatchmakingMatchDetails* match;
HREULT hr = PFMatchmakingTicketGetMatch(ticket, &match);
if (FAILED(hr))  
{  
    return;  
}  

std::string matchId = match->matchId;
std::string lobbyArrangementString = match->lobbyArrangementString;

PFMultiplayerDestroyMatchmakingTicket(g_pfmHandle, ticket);
```

## 取消工单

如果你希望在 `PFMatchmakingServerBackfillTicketConfiguration` 中设置的超时之前取消匹配过程,请使用工单句柄调用 [PFMatchmakingTicketCancel](/services/playfab/multiplayer/lobby/playfabmultiplayerreference-cpp/pfmatchmaking/functions/pfmatchmakingticketcancel)。

调用此 API 并不保证工单被取消。工单可能在取消操作被处理之前已经完成,或者取消请求可能因网络或服务错误而失败。如果希望在继续之前确认工单取消已完成,你仍可以处理匹配状态变更以获取工单的结果。否则,你可以立即调用 [PFMultiplayerDestroyMatchmakingTicket](/services/playfab/multiplayer/lobby/playfabmultiplayerreference-cpp/pfmatchmaking/functions/pfmultiplayerdestroymatchmakingticket)。

```cpp theme={null}
HRESULT hr = PFMatchmakingTicketCancel(ticket);

PFMultiplayerDestroyMatchmakingTicket(g_pfmHandle, ticket);
```
