Transferências de âncora local no DirectXLocal anchor transfers in DirectX

Em situações em que você não pode usar âncoras espaciais do Azure, as transferências de âncora local permitem que um dispositivo de hololens exporte uma âncora a ser importada por um segundo dispositivo hololens.In situations where you cannot use Azure Spatial Anchors, local anchor transfers enable one HoloLens device to export an anchor to be imported by a second HoloLens device.

Observação

As transferências de âncora local fornecem uma recall de ancoragem menos robusta do que as âncoras espaciais do Azure, e os dispositivos IOS e Android não são compatíveis com essa abordagem.Local anchor transfers provide less robust anchor recall than Azure Spatial Anchors, and iOS and Android devices are not supported by this approach.

Observação

Os trechos de código neste artigo demonstram atualmente o C++uso de/CX em vez de/WinRT em C++conformidade com C + +17, conforme usado no C++ modelo de projeto Holographic.The code snippets in this article currently demonstrate use of C++/CX rather than C++17-compliant C++/WinRT as used in the C++ holographic project template. Os conceitos são equivalentes a C++um projeto/WinRT, embora você precise converter o código.The concepts are equivalent for a C++/WinRT project, though you will need to translate the code.

Transferindo âncoras espaciaisTransferring spatial anchors

Você pode transferir âncoras espaciais entre dispositivos de realidade mista do Windows usando o SpatialAnchorTransferManager.You can transfer spatial anchors between Windows Mixed Reality devices by using the SpatialAnchorTransferManager. Essa API permite que você agrupe uma âncora com todos os dados de sensor de suporte necessários para encontrar esse local exato no mundo e, em seguida, importe esse pacote em outro dispositivo.This API lets you bundle up an anchor with all the supporting sensor data needed to find that exact place in the world, and then import that bundle on another device. Depois que o aplicativo no segundo dispositivo tiver importado essa âncora, cada aplicativo poderá renderizar hologramas usando o sistema de coordenadas da âncora espacial compartilhada, que será exibido no mesmo local do mundo real.Once the app on the second device has imported that anchor, each app can render holograms using that shared spatial anchor's coordinate system, which will then appear in the same place in the real world.

Observe que as âncoras espaciais não podem ser transferidas entre diferentes tipos de dispositivo, por exemplo, uma âncora espacial de HoloLens não pode ser localizável usando um headset de imersão.Note that spatial anchors are not able to transfer between different device types, for example a HoloLens spatial anchor may not be locatable using an immersive headset. As âncoras transferidas também não são compatíveis com dispositivos iOS ou Android.Transferred anchors are also not compatible with iOS or Android devices.

Configurar seu aplicativo para usar o recurso spatialPerceptionSet up your app to use the spatialPerception capability

Seu aplicativo deve receber permissão para usar o recurso spatialPerception antes de poder usar o SpatialAnchorTransferManager.Your app must be granted permission to use the spatialPerception capability before it can use the SpatialAnchorTransferManager. Isso é necessário porque a transferência de uma âncora espacial envolve o compartilhamento de imagens do sensor reunidas ao longo do tempo na proximidade da âncora, que pode incluir informações confidenciais.This is necessary because transferring a spatial anchor involves sharing sensor images gathered over time in vicinity of that anchor, which might include sensitive information.

Declare esse recurso no arquivo Package. appxmanifest para seu aplicativo.Declare this capability in the package.appxmanifest file for your app. Veja um exemplo:Here's an example:

<Capabilities>
  <uap2:Capability Name="spatialPerception" />
</Capabilities>

A funcionalidade vem do namespace uap2 .The capability comes from the uap2 namespace. Para obter acesso a esse namespace em seu manifesto, inclua-o como um atributo xlmns no elemento > do pacote de <.To get access to this namespace in your manifest, include it as an xlmns attribute in the <Package> element. Veja um exemplo:Here's an example:

<Package
    xmlns="https://schemas.microsoft.com/appx/manifest/foundation/windows10"
    xmlns:mp="https://schemas.microsoft.com/appx/2014/phone/manifest"
    xmlns:uap="https://schemas.microsoft.com/appx/manifest/uap/windows10"
    xmlns:uap2="https://schemas.microsoft.com/appx/manifest/uap/windows10/2"
    IgnorableNamespaces="uap mp"
    >

Observação: Seu aplicativo precisará solicitar o recurso em tempo de execução antes de poder acessar as APIs de exportação/importação do SpatialAnchor.NOTE: Your app will need to request the capability at runtime before it can access SpatialAnchor export/import APIs. Consulte RequestAccessAsync nos exemplos abaixo.See RequestAccessAsync in the examples below.

Serialize os dados de ancoragem exportando-os com o SpatialAnchorTransferManagerSerialize anchor data by exporting it with the SpatialAnchorTransferManager

Uma função auxiliar está incluída no exemplo de código para exportar (serializar) dados SpatialAnchor .A helper function is included in the code sample to export (serialize) SpatialAnchor data. Essa API de exportação serializa todas as âncoras em uma coleção de pares chave-valor associando cadeias de caracteres com âncoras.This export API serializes all anchors in a collection of key-value pairs associating strings with anchors.

// ExportAnchorDataAsync: Exports a byte buffer containing all of the anchors in the given collection.
//
// This function will place data in a buffer using a std::vector<byte>. The ata buffer contains one or more
// Anchors if one or more Anchors were successfully imported; otherwise, it is ot modified.
//
task<bool> SpatialAnchorImportExportHelper::ExportAnchorDataAsync(
    vector<byte>* anchorByteDataOut,
    IMap<String^, SpatialAnchor^>^ anchorsToExport
    )
{

Primeiro, precisamos configurar o fluxo de dados.First, we need to set up the data stream. Isso nos permitirá 1.) Use TryExportAnchorsAsync para colocar os dados em um buffer de Propriedade do aplicativo e 2.) ler dados do fluxo de buffer de bytes exportado, que é um fluxo de dados do WinRT, em nosso próprio buffer de memória, que é um > de bytes std:: vector<.This will allow us to 1.) use TryExportAnchorsAsync to put the data in a buffer owned by the app, and 2.) read data from the exported byte buffer stream - which is a WinRT data stream - into our own memory buffer, which is a std::vector<byte>.

// Create a random access stream to process the anchor byte data.
InMemoryRandomAccessStream^ stream = ref new InMemoryRandomAccessStream();
// Get an output stream for the anchor byte stream.
IOutputStream^ outputStream = stream->GetOutputStreamAt(0);

Precisamos pedir permissão para acessar dados espaciais, incluindo âncoras exportadas pelo sistema.We need to ask permission to access spatial data, including anchors that are exported by the system.

// Request access to spatial data.
auto accessRequestedTask = create_taskSpatialAnchorTransferManager::RequestAccessAsync()).then([anchorsToExport, utputStream](SpatialPerceptionAccessStatus status)
{
    if (status == SpatialPerceptionAccessStatus::Allowed)
    {
        // Access is allowed.
        // Export the indicated set of anchors.
        return create_task(SpatialAnchorTransferManager::TryExportAnchorsAsync(
            anchorsToExport,
            outputStream
            ));
    }
    else
    {
        // Access is denied.
        return task_from_result<bool>(false);
    }
});

Se obtivermos a permissão e as âncoras forem exportadas, podemos ler o fluxo de dados.If we do get permission and anchors are exported, we can read the data stream. Aqui, também mostramos como criar o DataReader e o InputStream que usaremos para ler os dados.Here, we also show how to create the DataReader and InputStream we will use to read the data.

// Get the input stream for the anchor byte stream.
IInputStream^ inputStream = stream->GetInputStreamAt(0);
// Create a DataReader, to get bytes from the anchor byte stream.
DataReader^ reader = ref new DataReader(inputStream);
return accessRequestedTask.then([anchorByteDataOut, stream, reader](bool nchorsExported)
{
    if (anchorsExported)
    {
        // Get the size of the exported anchor byte stream.
        size_t bufferSize = static_cast<size_t>(stream->Size);
        // Resize the output buffer to accept the data from the stream.
        anchorByteDataOut->reserve(bufferSize);
        anchorByteDataOut->resize(bufferSize);
        // Read the exported anchor store into the stream.
        return create_task(reader->LoadAsync(bufferSize));
    }
    else
    {
        return task_from_result<size_t>(0);
    }

Depois de ler os bytes do fluxo, podemos salvá-los em nosso próprio buffer de dados como esse.After we read bytes from the stream, we can save them to our own data buffer like so.

}).then([anchorByteDataOut, reader](size_t bytesRead)
{
    if (bytesRead > 0)
    {
        // Read the bytes from the stream, into our data output buffer.
        reader->ReadBytes(Platform::ArrayReference<byte>(&(*anchorByteDataOut)[0], bytesRead));
        return true;
    }
    else
    {
        return false;
    }
});
};

Desserializar dados de âncora importando-os para o sistema usando o SpatialAnchorTransferManagerDeserialize anchor data by importing it into the system using the SpatialAnchorTransferManager

Uma função auxiliar está incluída no exemplo de código para carregar dados exportados anteriormente.A helper function is included in the code sample to load previously exported data. Essa função de desserialização fornece uma coleção de pares chave-valor, semelhante ao que o SpatialAnchorStore fornece-exceto que obtemos esses dados de outra fonte, como um soquete de rede.This deserialization function provides a collection of key-value pairs, similar to what the SpatialAnchorStore provides - except that we got this data from another source, such as a network socket. Você pode processar e ponderar esses dados antes de armazená-los offline, usando a memória no aplicativo ou (se aplicável) o SpatialAnchorStore do seu aplicativo.You can process and reason about this data before storing it offline, using in-app memory, or (if applicable) your app's SpatialAnchorStore.

// ImportAnchorDataAsync: Imports anchors from a byte buffer that was previously exported.
//
// This function will import all anchors from a data buffer into an in-memory ollection of key, value
// pairs that maps String objects to SpatialAnchor objects. The Spatial nchorStore is not affected by
// this function unless you provide it as the target collection for import.
//
task<bool> SpatialAnchorImportExportHelper::ImportAnchorDataAsync(
    std::vector<byte>& anchorByteDataIn,
    IMap<String^, SpatialAnchor^>^ anchorMapOut
    )
{

Primeiro, precisamos criar objetos de fluxo para acessar os dados de âncora.First, we need to create stream objects to access the anchor data. Iremos gravar os dados do nosso buffer em um buffer do sistema, portanto, criaremos um DataWriter que grava em um fluxo de dados na memória para atingir nossa meta de obter âncoras de um buffer de bytes no sistema como SpatialAnchors.We will be writing the data from our buffer to a system buffer, so we will create a DataWriter that writes to an in-memory data stream in order to accomplish our goal of getting anchors from a byte buffer into the system as SpatialAnchors.

// Create a random access stream for the anchor data.
InMemoryRandomAccessStream^ stream = ref new InMemoryRandomAccessStream();
// Get an output stream for the anchor data.
IOutputStream^ outputStream = stream->GetOutputStreamAt(0);
// Create a writer, to put the bytes in the stream.
DataWriter^ writer = ref new DataWriter(outputStream);

Mais uma vez, precisamos garantir que o aplicativo tenha permissão para exportar dados de âncora espaciais, o que pode incluir informações particulares sobre o ambiente do usuário.Once again, we need to ensure the app has permission to export spatial anchor data, which could include private information about the user's environment.

// Request access to transfer spatial anchors.
return create_task(SpatialAnchorTransferManager::RequestAccessAsync()).then(
    [&anchorByteDataIn, writer](SpatialPerceptionAccessStatus status)
{
    if (status == SpatialPerceptionAccessStatus::Allowed)
    {
        // Access is allowed.

Se o acesso for permitido, podemos gravar bytes do buffer em um fluxo de dados do sistema.If access is allowed, we can write bytes from the buffer to a system data stream.

// Write the bytes to the stream.
        byte* anchorDataFirst = &anchorByteDataIn[0];
        size_t anchorDataSize = anchorByteDataIn.size();
        writer->WriteBytes(Platform::ArrayReference<byte>(anchorDataFirst, anchorDataSize));
        // Store the stream.
        return create_task(writer->StoreAsync());
    }
    else
    {
        // Access is denied.
        return task_from_result<size_t>(0);
    }

Se obtivermos êxito no armazenamento de bytes no fluxo de dados, podemos tentar importar esses dados usando o SpatialAnchorTransferManager.If we were successful in storing bytes in the data stream, we can try to import that data using the SpatialAnchorTransferManager.

}).then([writer, stream](unsigned int bytesWritten)
{
    if (bytesWritten > 0)
    {
        // Try to import anchors from the byte stream.
        return create_task(writer->FlushAsync())
            .then([stream](bool dataWasFlushed)
        {
            if (dataWasFlushed)
            {
                // Get the input stream for the anchor data.
                IInputStream^ inputStream = stream->GetInputStreamAt(0);
                return create_task(SpatialAnchorTransferManager::TryImportAnchorsAsync(inputStream));
            }
            else
            {
                return task_from_result<IMapView<String^, SpatialAnchor^>^>(nullptr);
            }
        });
    }
    else
    {
        return task_from_result<IMapView<String^, SpatialAnchor^>^>(nullptr);
    }

Se os dados puderem ser importados, obteremos uma exibição de mapa de pares chave-valor associando cadeias de caracteres com âncoras.If the data is able to be imported, we get a map view of key-value pairs associating strings with anchors. Podemos carregá-lo em nossa própria coleta de dados na memória e usar essa coleção para procurar âncoras que estamos interessados em usar.We can load this into our own in-memory data collection, and use that collection to look for anchors that we are interested in using.

}).then([anchorMapOut](task<Windows::Foundation::Collections::IMapView<String^, SpatialAnchor^>^>  previousTask)
{
    try
    {
        auto importedAnchorsMap = previousTask.get();
        // If the operation was successful, we get a set of imported anchors.
        if (importedAnchorsMap != nullptr)
        {
            for each (auto& pair in importedAnchorsMap)
            {
                // Note that you could look for specific anchors here, if you know their key values.
                auto const& id = pair->Key;
                auto const& anchor = pair->Value;
                // Append "Remote" to the end of the anchor name for disambiguation.
                std::wstring idRemote(id->Data());
                idRemote += L"Remote";
                String^ idRemoteConst = ref new String (idRemote.c_str());
                // Store the anchor in the current in-memory anchor map.
                anchorMapOut->Insert(idRemoteConst, anchor);
            }
            return true;
        }
    }
    catch (Exception^ exception)
    {
        OutputDebugString(L"Error: Unable to import the anchor data buffer bytes into the in-memory anchor collection.\n");
    }
    return false;
});
}

Observação: Só porque você pode importar uma âncora, não significa necessariamente que você pode usá-la imediatamente.NOTE: Just because you can import an anchor, doesn't necessarily mean that you can use it right away. A âncora pode estar em uma sala diferente ou em outra localização física; a âncora não será localizável até que o dispositivo que o recebeu tenha informações visuais suficientes sobre o ambiente em que a âncora foi criada, para restaurar a posição da âncora relativa ao ambiente atual conhecido.The anchor might be in a different room, or another physical location entirely; the anchor won't be locatable until the device that received it has enough visual information about the environment the anchor was created in, to restore the anchor's position relative to the known current environment. A implementação do cliente deve tentar localizar a âncora relativa ao seu sistema de coordenadas local ou quadro de referência antes de prosseguir para tentar usá-la para conteúdo ao vivo.The client implementation should try locating the anchor relative to your local coordinate system or reference frame before continuing on to try to use it for live content. Por exemplo, tente localizar a âncora relativa a um sistema de coordenadas atual periodicamente até que a âncora comece a ser localizável.For example, try locating the anchor relative to a current coordinate system periodically until the anchor begins to be locatable.

Considerações especiaisSpecial Considerations

A API TryExportAnchorsAsync permite que vários SpatialAnchors sejam exportados para o mesmo blob binário opaco.The TryExportAnchorsAsync API allows multiple SpatialAnchors to be exported into the same opaque binary blob. No entanto, há uma diferença sutil em quais dados o blob incluirá, dependendo se um único SpatialAnchor ou vários SpatialAnchors são exportados em uma única chamada.However, there is a subtle difference in what data the blob will include, depending on whether a single SpatialAnchor or multiple SpatialAnchors are exported in a single call.

Exportação de um único SpatialAnchorExport of a single SpatialAnchor

O BLOB contém uma representação do ambiente na vizinhança do SpatialAnchor para que o ambiente possa ser reconhecido no dispositivo que importa o SpatialAnchor.The blob contains a representation of the environment in the vicinity of the SpatialAnchor so that the environment can be recognized on the device that imports the SpatialAnchor. Após a conclusão da importação, o novo SpatialAnchor estará disponível para o dispositivo.After the import completes, the new SpatialAnchor will be available to the device. Supondo que o usuário esteve recentemente na proximidade da âncora, ele será localizável e os hologramas anexados ao SpatialAnchor poderão ser renderizados.Assuming the user has recently been in vicinity of the anchor, it will be locatable and holograms attached to the SpatialAnchor can be rendered. Esses hologramas serão mostrados no mesmo local físico que fizeram no dispositivo original que exportou o SpatialAnchor.These holograms will show up in the same physical location that they did on the original device which exported the SpatialAnchor.

Exportação de um único SpatialAnchor

Exportação de vários SpatialAnchorsExport of multiple SpatialAnchors

Como a exportação de um único SpatialAnchor, o BLOB contém uma representação do ambiente na vizinhança de todos os SpatialAnchors especificados.Like the export of a single SpatialAnchor, the blob contains a representation of the environment in the vicinity of all the specified SpatialAnchors. Além disso, o BLOB contém informações sobre as conexões entre o SpatialAnchors incluído, se eles estiverem localizados no mesmo espaço físico.In addition, the blob contains information about the connections between the included SpatialAnchors, if they are located in the same physical space. Isso significa que, se dois SpatialAnchors próximos forem importados, um holograma anexado ao segundo SpatialAnchor seria localizável, mesmo que o dispositivo reconheça apenas o ambiente em relação à primeira SpatialAnchor, porque dados suficientes para computar a transformação entre os dois SpatialAnchors foram incluídos no BLOB.This means that if two nearby SpatialAnchors are imported, then a hologram attached to the second SpatialAnchor would be locatable even if the device only recognizes the environment around the first SpatialAnchor, because enough data to compute transform between the two SpatialAnchors was included in the blob. Se as duas SpatialAnchors foram exportadas individualmente (duas chamadas separadas para TryExportSpatialAnchors), talvez não haja dados suficientes incluídos no blob para hologramas anexados ao segundo SpatialAnchor a serem localizáveis quando o primeiro estiver localizado.If the two SpatialAnchors were exported individually (two separate calls to TryExportSpatialAnchors) then there may not be enough data included in the blob for holograms attached to the second SpatialAnchor to be locatable when the first one is located.

Várias âncoras exportadas usando uma única chamada TryExportAnchorsAsync Várias âncoras exportadas usando uma chamada TryExportAnchorsAsync separada para cada âncora

Exemplo: enviar dados de âncora usando um Windows:: Networking:: StreamSocketExample: Send anchor data using a Windows::Networking::StreamSocket

Aqui, fornecemos um exemplo de como usar dados de âncora exportados enviando-os por uma rede TCP.Here, we provide an example of how to use exported anchor data by sending it across a TCP network. Isso é de HolographicSpatialAnchorTransferSample.This is from the HolographicSpatialAnchorTransferSample.

A classe StreamSocket do WinRT usa a biblioteca de tarefas PPL.The WinRT StreamSocket class uses the PPL task library. No caso de erros de rede, o erro é retornado para a próxima tarefa na cadeia usando uma exceção que é relançada.In the case of network errors, the error is returned to the next task in the chain using an exception that is re-thrown. A exceção contém um HRESULT que indica o status do erro.The exception contains an HRESULT indicating the error status.

Use um Windows:: Networking:: StreamSocketListener com TCP para enviar dados de âncora exportadosUse a Windows::Networking::StreamSocketListener with TCP to send exported anchor data

Crie uma instância de servidor que escuta uma conexão.Create a server instance that listens for a connection.

void SampleAnchorTcpServer::ListenForConnection()
{
    // Make a local copy to avoid races with Closed events.
    StreamSocketListener^ streamSocketListener = m_socketServer;
    if (streamSocketListener == nullptr)
    {
        OutputDebugString(L"Server listening for client.\n");
        // Create the web socket connection.
        streamSocketListener = ref new StreamSocketListener();
        streamSocketListener->Control->KeepAlive = true;
        streamSocketListener->BindEndpointAsync(
            SampleAnchorTcpCommon::m_serverHost,
            SampleAnchorTcpCommon::m_tcpPort
            );
        streamSocketListener->ConnectionReceived +=
            ref new Windows::Foundation::TypedEventHandler<StreamSocketListener^, StreamSocketListenerConnectionReceivedEventArgs^>(
                std::bind(&SampleAnchorTcpServer::OnConnectionReceived, this, _1, _2)
                );
        m_socketServer = streamSocketListener;
    }
    else
    {
        OutputDebugString(L"Error: Stream socket listener not created.\n");
    }
}

Quando uma conexão é recebida, use a conexão de soquete de cliente para enviar dados de âncora.When a connection is received, use the client socket connection to send anchor data.

void SampleAnchorTcpServer::OnConnectionReceived(StreamSocketListener^ listener, StreamSocketListenerConnectionReceivedEventArgs^ args)
{
    m_socketForClient = args->Socket;
    if (m_socketForClient != nullptr)
    {
        // In this example, when the client first connects, we catch it up to the current state of our anchor set.
        OutputToClientSocket(m_spatialAnchorHelper->GetAnchorMap());
    }
}

Agora, podemos começar a enviar um fluxo de dados que contém os dados de âncora exportados.Now, we can begin to send a data stream that contains the exported anchor data.

void SampleAnchorTcpServer::OutputToClientSocket(IMap<String^, SpatialAnchor^>^ anchorsToSend)
{
    m_anchorTcpSocketStreamWriter = ref new DataWriter(m_socketForClient->OutputStream);
    OutputDebugString(L"Sending stream to client.\n");
    SendAnchorDataStream(anchorsToSend).then([this](task<bool> previousTask)
    {
        try
        {
            bool success = previousTask.get();
            if (success)
            {
                OutputDebugString(L"Anchor data sent!\n");
            }
            else
            {
                OutputDebugString(L"Error: Anchor data not sent.\n");
            }
        }
        catch (Exception^ exception)
        {
            HandleException(exception);
            OutputDebugString(L"Error: Anchor data was not sent.\n");
        }
    });
}

Antes que possamos enviar o fluxo em si, devemos primeiro enviar um pacote de cabeçalho.Before we can send the stream itself, we must first send a header packet. Esse pacote de cabeçalho deve ter comprimento fixo e também deve indicar o comprimento da matriz variável de bytes que é o fluxo de dados de âncora; no caso deste exemplo, não temos nenhum outro dado de cabeçalho para enviar, portanto, nosso cabeçalho tem 4 bytes de comprimento e contém um inteiro sem sinal de 32 bits.This header packet must be of fixed length, and it must also indicate the length of the variable array of bytes that is the anchor data stream; in the case of this example we have no other header data to send, so our header is 4 bytes long and contains a 32-bit unsigned integer.

Concurrency::task<bool> SampleAnchorTcpServer::SendAnchorDataLengthMessage(size_t dataStreamLength)
{
    unsigned int arrayLength = dataStreamLength;
    byte* data = reinterpret_cast<byte*>(&arrayLength);
    m_anchorTcpSocketStreamWriter->WriteBytes(Platform::ArrayReference<byte>(data, SampleAnchorTcpCommon::c_streamHeaderByteArrayLength));
    return create_task(m_anchorTcpSocketStreamWriter->StoreAsync()).then([this](unsigned int bytesStored)
    {
        if (bytesStored > 0)
        {
            OutputDebugString(L"Anchor data length stored in stream; Flushing stream.\n");
            return create_task(m_anchorTcpSocketStreamWriter->FlushAsync());
        }
        else
        {
            OutputDebugString(L"Error: Anchor data length not stored in stream.\n");
            return task_from_result<bool>(false);
        }
    });
}
Concurrency::task<bool> SampleAnchorTcpServer::SendAnchorDataStreamIMap<String^, SpatialAnchor^>^ anchorsToSend)
{
    return SpatialAnchorImportExportHelper::ExportAnchorDataAsync(
        &m_exportedAnchorStoreBytes,
        anchorsToSend
        ).then([this](bool anchorDataExported)
    {
        if (anchorDataExported)
        {
            const size_t arrayLength = m_exportedAnchorStoreBytes.size();
            if (arrayLength > 0)
            {
                OutputDebugString(L"Anchor data was exported; sending data stream length message.\n");
                return SendAnchorDataLengthMessage(arrayLength);
            }
        }
        OutputDebugString(L"Error: Anchor data was not exported.\n");
        // No data to send.
        return task_from_result<bool>(false);

Depois que o tamanho do fluxo, em bytes, tiver sido enviado ao cliente, podemos continuar a gravar o fluxo de dados em si para o fluxo de soquete.Once the stream length, in bytes, has been sent to the client, we can proceed to write the data stream itself to the socket stream. Isso fará com que os bytes do repositório de âncora sejam enviados ao cliente.This will cause the anchor store bytes to get sent to the client.

}).then([this](bool dataLengthSent)
    {
        if (dataLengthSent)
        {
            OutputDebugString(L"Data stream length message sent; writing exported anchor store bytes to stream.\n");
            m_anchorTcpSocketStreamWriter->WriteBytes(Platform::ArrayReference<byte>(&m_exportedAnchorStoreBytes[0], m_exportedAnchorStoreBytes.size()));
            return create_task(m_anchorTcpSocketStreamWriter->StoreAsync());
        }
        else
        {
            OutputDebugString(L"Error: Data stream length message not sent.\n");
            return task_from_result<size_t>(0);
        }
    }).then([this](unsigned int bytesStored)
    {
        if (bytesStored > 0)
        {
            PrintWstringToDebugConsole(
                std::to_wstring(bytesStored) +
                L" bytes of anchor data written and stored to stream; flushing stream.\n"
                );
        }
        else
        {
            OutputDebugString(L"Error: No anchor data bytes were written to the stream.\n");
        }
        return task_from_result<bool>(false);
    });
}

Conforme observado anteriormente neste tópico, devemos estar preparados para manipular exceções que contenham mensagens de status de erro de rede.As noted earlier in this topic, we must be prepared to handle exceptions containing network error status messages. Para erros que não são esperados, podemos gravar as informações de exceção no console de depuração como esse.For errors that are not expected, we can write the exception info to the debug console like so. Isso nos dará uma pista sobre o que aconteceu se nosso exemplo de código não puder concluir a conexão ou se não for possível concluir o envio dos dados de âncora.This will give us a clue as to what happened if our code sample is unable to complete the connection, or if it is unable to finish sending the anchor data.

void SampleAnchorTcpServer::HandleException(Exception^ exception)
{
    PrintWstringToDebugConsole(
        std::wstring(L"Connection error: ") +
        exception->ToString()->Data() +
        L"\n"
        );
}

Use um Windows:: Networking:: StreamSocket com TCP para receber dados de âncora exportadosUse a Windows::Networking::StreamSocket with TCP to receive exported anchor data

Primeiro, precisamos se conectar ao servidor.First, we have to connect to the server. Este exemplo de código mostra como criar e configurar um StreamSocket e criar um DataReader que você pode usar para adquirir dados de rede usando a conexão de soquete.This code sample shows how to create and configure a StreamSocket, and create a DataReader that you can use to acquire network data using the socket connection.

Observação: Se você executar este código de exemplo, certifique-se de configurar e iniciar o servidor antes de iniciar o cliente.NOTE: If you run this sample code, ensure that you configure and launch the server before starting the client.

task<bool> SampleAnchorTcpClient::ConnectToServer()
{
    // Make a local copy to avoid races with Closed events.
    StreamSocket^ streamSocket = m_socketClient;
    // Have we connected yet?
    if (m_socketClient == nullptr)
    {
        OutputDebugString(L"Client is attempting to connect to server.\n");
        EndpointPair^ endpointPair = ref new EndpointPair(
            SampleAnchorTcpCommon::m_clientHost,
            SampleAnchorTcpCommon::m_tcpPort,
            SampleAnchorTcpCommon::m_serverHost,
            SampleAnchorTcpCommon::m_tcpPort
            );
        // Create the web socket connection.
        m_socketClient = ref new StreamSocket();
        // The client connects to the server.
        return create_task(m_socketClient->ConnectAsync(endpointPair, SocketProtectionLevel::PlainSocket)).then([this](task<void> previousTask)
        {
            try
            {
                // Try getting all exceptions from the continuation chain above this point.
                previousTask.get();
                m_anchorTcpSocketStreamReader = ref new DataReader(m_socketClient->InputStream);
                OutputDebugString(L"Client connected!\n");
                m_anchorTcpSocketStreamReader->InputStreamOptions = InputStreamOptions::ReadAhead;
                WaitForAnchorDataStream();
                return true;
            }
            catch (Exception^ exception)
            {
                if (exception->HResult == 0x80072741)
                {
                    // This code sample includes a very simple implementation of client/server
                    // endpoint detection: if the current instance tries to connect to itself,
                    // it is determined to be the server.
                    OutputDebugString(L"Starting up the server instance.\n");
                    // When we return false, we'll start up the server instead.
                    return false;
                }
                else if ((exception->HResult == 0x8007274c) || // connection timed out
                    (exception->HResult == 0x80072740)) // connection maxed at server end
                {
                    // If the connection timed out, try again.
                    ConnectToServer();
                }
                else if (exception->HResult == 0x80072741)
                {
                    // No connection is possible.
                }
                HandleException(exception);
                return true;
            }
        });
    }
    else
    {
        OutputDebugString(L"A StreamSocket connection to a server already exists.\n");
        return task_from_result<bool>(true);
    }
}

Assim que tivermos uma conexão, podemos aguardar até que o servidor envie dados.Once we have a connection, we can wait for the server to send data. Fazemos isso chamando LoadAsync no leitor de dados de fluxo.We do this by calling LoadAsync on the stream data reader.

O primeiro conjunto de bytes que recebemos deve sempre ser o pacote de cabeçalho, que indica o comprimento de bytes do fluxo de dados de âncora, conforme descrito na seção anterior.The first set of bytes we receive should always be the header packet, which indicates the anchor data stream byte length as described in the previous section.

void SampleAnchorTcpClient::WaitForAnchorDataStream()
{
    if (m_anchorTcpSocketStreamReader == nullptr)
    {
        // We have not connected yet.
        return;
    }
    OutputDebugString(L"Waiting for server message.\n");
    // Wait for the first message, which specifies the byte length of the string data.
    create_task(m_anchorTcpSocketStreamReader->LoadAsync(SampleAnchorTcpCommon::c_streamHeaderByteArrayLength)).then([this](unsigned int numberOfBytes)
    {
        if (numberOfBytes > 0)
        {
            OutputDebugString(L"Server message incoming.\n");
            return ReceiveAnchorDataLengthMessage();
        }
        else
        {
            OutputDebugString(L"0-byte async task received, awaiting server message again.\n");
            WaitForAnchorDataStream();
            return task_from_result<size_t>(0);
        }

......

task<size_t> SampleAnchorTcpClient::ReceiveAnchorDataLengthMessage()
{
    byte data[4];
    m_anchorTcpSocketStreamReader->ReadBytes(Platform::ArrayReference<byte>(data, SampleAnchorTcpCommon::c_streamHeaderByteArrayLength));
    unsigned int lengthMessageSize = *reinterpret_cast<unsigned int*>(data);
    if (lengthMessageSize > 0)
    {
        OutputDebugString(L"One or more anchors to be received.\n");
        return task_from_result<size_t>(lengthMessageSize);
    }
    else
    {
        OutputDebugString(L"No anchors to be received.\n");
        ConnectToServer();
    }
    return task_from_result<size_t>(0);
}

Depois de recebermos o pacote de cabeçalho, sabemos quantos bytes de dados de âncora devemos esperar.After we have received the header packet, we know how many bytes of anchor data we should expect. Podemos continuar lendo esses bytes a partir do fluxo.We can proceed to read those bytes from the stream.

}).then([this](size_t dataStreamLength)
    {
        if (dataStreamLength > 0)
        {
            std::wstring debugMessage = std::to_wstring(dataStreamLength);
            debugMessage += L" bytes of anchor data incoming.\n";
            OutputDebugString(debugMessage.c_str());
            // Prepare to receive the data stream in one or more pieces.
            m_anchorStreamLength = dataStreamLength;
            m_exportedAnchorStoreBytes.clear();
            m_exportedAnchorStoreBytes.resize(m_anchorStreamLength);
            OutputDebugString(L"Loading byte stream.\n");
            return ReceiveAnchorDataStream();
        }
        else
        {
            OutputDebugString(L"Error: Anchor data size not received.\n");
            ConnectToServer();
            return task_from_result<bool>(false);
        }
    });
}

Aqui está nosso código para receber o fluxo de dados de âncora.Here's our code for receiving the anchor data stream. Novamente, primeiro carregaremos os bytes do fluxo; Essa operação pode levar algum tempo para ser concluída, pois o StreamSocket aguarda para receber essa quantidade de bytes da rede.Again, we will first load the bytes from the stream; this operation may take some time to complete as the StreamSocket waits to receive that amount of bytes from the network.

Quando a operação de carregamento for concluída, podemos ler esse número de bytes.When the loading operation is complete, we can read that number of bytes. Se recebermos o número de bytes que esperamos para o fluxo de dados de âncora, podemos continuar e importar os dados de âncora; caso contrário, deve haver algum tipo de erro.If we received the number of bytes that we expect for the anchor data stream, we can go ahead and import the anchor data; if not, there must have been some sort of error. Por exemplo, isso pode acontecer quando a instância do servidor é encerrada antes de concluir o envio do fluxo de dados, ou a rede fica inoperante antes que todo o fluxo de dados possa ser recebido pelo cliente.For example, this can happen when the server instance terminates before it can finish sending the data stream, or the network goes down before the entire data stream can be received by the client.

task<bool> SampleAnchorTcpClient::ReceiveAnchorDataStream()
{
    if (m_anchorStreamLength > 0)
    {
        // First, we load the bytes from the network socket.
        return create_task(m_anchorTcpSocketStreamReader->LoadAsync(m_anchorStreamLength)).then([this](size_t bytesLoadedByStreamReader)
        {
            if (bytesLoadedByStreamReader > 0)
            {
                // Once the bytes are loaded, we can read them from the stream.
                m_anchorTcpSocketStreamReader->ReadBytes(Platform::ArrayReference<byte>(&m_exportedAnchorStoreBytes[0],
                    bytesLoadedByStreamReader));
                // Check status.
                if (bytesLoadedByStreamReader == m_anchorStreamLength)
                {
                    // The whole stream has arrived. We can process the data.
                    // Informational message of progress complete.
                    std::wstring infoMessage = std::to_wstring(bytesLoadedByStreamReader);
                    infoMessage += L" bytes read out of ";
                    infoMessage += std::to_wstring(m_anchorStreamLength);
                    infoMessage += L" total bytes; importing the data.\n";
                    OutputDebugStringW(infoMessage.c_str());
                    // Kick off a thread to wait for a new message indicating another incoming anchor data stream.
                    WaitForAnchorDataStream();
                    // Process the data for the stream we just received.
                    return SpatialAnchorImportExportHelper::ImportAnchorDataAsync(m_exportedAnchorStoreBytes, m_spatialAnchorHelper->GetAnchorMap());
                }
                else
                {
                    OutputDebugString(L"Error: Fewer than expected anchor data bytes were received.\n");
                }
            }
            else
            {
                OutputDebugString(L"Error: No anchor bytes were received.\n");
            }
            return task_from_result<bool>(false);
        });
    }
    else
    {
        OutputDebugString(L"Warning: A zero-length data buffer was sent.\n");
        return task_from_result<bool>(false);
    }
}

Novamente, devemos estar preparados para lidar com erros de rede desconhecidos.Again, we must be prepared to handle unknown network errors.

void SampleAnchorTcpClient::HandleException(Exception^ exception)
{
    std::wstring error = L"Connection error: ";
    error += exception->ToString()->Data();
    error += L"\n";
    OutputDebugString(error.c_str());
}

É só isso!That's it! Agora, você deve ter informações suficientes para tentar localizar as âncoras recebidas pela rede.Now, you should have enough information to try locating the anchors received over the network. Novamente, observe que o cliente deve ter dados de controle visual suficientes para o espaço localizar a âncora com êxito; Se não funcionar imediatamente, tente percorrer um tempo.Again, note that the client must have enough visual tracking data for the space to successfully locate the anchor; if it doesn't work right away, try walking around for a while. Se ainda não funcionar, faça com que o servidor envie mais âncoras e use as comunicações de rede para concordar em uma que funcione para o cliente.If it still doesn't work, have the server send more anchors, and use network communications to agree on one that works for the client. Você pode experimentar isso baixando o HolographicSpatialAnchorTransferSample, configurando seus IPs de cliente e de servidor e implantando-os em dispositivos HoloLens de cliente e servidor.You can try this out by downloading the HolographicSpatialAnchorTransferSample, configuring your client and server IPs, and deploying it to client and server HoloLens devices.

Consulte tambémSee also