Lokalne transfery kotwic w technologii DirectX

W sytuacjach, w których nie można użyć usługi Azure Spatial Anchors,lokalne transfery kotwic umożliwiają jednem urządzeniu HoloLens wyeksportowanie kotwicy do zaimportowania przez drugie HoloLens urządzenie.

Uwaga

Lokalne transfery kotwic zapewniają mniej niezawodne odwoływanie kotwic niż usługa Azure Spatial Anchors,a urządzenia z systemami iOS i Android nie są obsługiwane przez to podejście.

Uwaga

Fragmenty kodu w tym artykule pokazują obecnie użycie języka C++/CX, a nie języka C++17 zgodnego z C++/WinRT, używanego w szablonie projektu holograficznego języka C++. Pojęcia są równoważne projektowi języka C++/WinRT, ale trzeba będzie przetłumaczyć kod.

Transferowanie kotwic przestrzennych

Kotwice przestrzenne można przesyłać między Windows Mixed Reality urządzeniami przy użyciu klasy SpatialAnchorTransferManager. Ten interfejs API umożliwia tworzenie pakietu kotwicy ze wszystkimi danymi czujników potrzebnymi do znalezienia tego dokładnego miejsca na świecie, a następnie importowanie tego pakietu na innym urządzeniu. Gdy aplikacja na drugim urządzeniu zaimportuje tę kotwicę, każda aplikacja może renderować hologramy przy użyciu współużytkowanego systemu współrzędnych kotwicy przestrzennej, który będzie następnie wyświetlany w tym samym miejscu w świecie rzeczywistym.

Należy pamiętać, że kotwice przestrzenne nie mogą być transferowane między różnymi typami urządzeń, na przykład kotwica przestrzenna HoloLens może nie być przechwytywalna przy użyciu immersyjnego zestawu nagłownego. Przesłane kotwice nie są również zgodne z urządzeniami z systemem iOS lub Android.

Konfigurowanie aplikacji do korzystania z funkcji spatialPerception

Aby aplikacja była w stanie korzystać z klasy SpatialAnchorTransferManager,musi mieć uprawnienia do korzystania z funkcji SpatialPerception. Jest to konieczne, ponieważ transfer kotwicy przestrzennej obejmuje udostępnianie obrazów czujników zebranych w czasie w sąsiedztwie tej kotwicy, co może zawierać informacje poufne.

Zadeklaruj tę możliwość w pliku package.appxmanifest dla aplikacji. Oto przykład:

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

Ta możliwość pochodzi z przestrzeni nazw uap2. Aby uzyskać dostęp do tej przestrzeni nazw w manifeście, uwzględnij ją jako atrybut xlmns w < elemencie Package>. Oto przykład:

<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"
    >

UWAGA: Aplikacja musi zażądać możliwości w czasie wykonywania, aby uzyskać dostęp do interfejsów API eksportu/importu spatialanchor. Zobacz RequestAccessAsync w poniższych przykładach.

Serializowanie danych kotwicy przez wyeksportowanie ich za pomocą klasy SpatialAnchorTransferManager

Funkcja pomocnika jest zawarta w przykładowym kodzie do eksportowania (serializacji) danych SpatialAnchor. Ten interfejs API eksportu serializuje wszystkie kotwice w kolekcji par klucz-wartość, kojarząc ciągi z kotwicami.

// 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
    )
{

Najpierw musimy skonfigurować strumień danych. Pozwoli nam to na 1). Użyj funkcji TryExportAnchorsAsync, aby umieścić dane w buforze należącym do aplikacji i 2). odczytywanie danych z wyeksportowanego strumienia bufora bajtów — czyli strumienia danych WinRT — do własnego buforu pamięci, który jest bajtem std::vector <>.

// 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);

Musimy poprosić o uprawnienie dostępu do danych przestrzennych, w tym kotwic wyeksportowanych przez 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);
    }
});

Jeśli pobierzemy uprawnienia i kotwice zostaną wyeksportowane, możemy odczytać strumień danych. W tym miejscu pokażemy również, jak utworzyć dane DataReader i InputStream, których będziemy używać do odczytywania danych.

// 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);
    }

Po odczytaniu bajtów ze strumienia możemy zapisać je w naszym buforze danych w taki sposób.

}).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;
    }
});
};

Deserializuj dane kotwicy, importując je do systemu przy użyciu klasy SpatialAnchorTransferManager

Funkcja pomocnika jest dołączona do przykładowego kodu w celu załadowania wcześniej wyeksportowanych danych. Ta funkcja deserializacji udostępnia kolekcję par klucz-wartość, podobnie jak funkcja SpatialAnchorStore — z tą różnicą, że dane te pochodzą z innego źródła, takiego jak gniazdo sieciowe. Te dane można przetwarzać i rozumiać przed zapisaniem ich w trybie offline, użyciem pamięci w aplikacji lub (jeśli ma to zastosowanie) magazynem SpatialAnchorStore aplikacji.

// 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
    )
{

Najpierw musimy utworzyć obiekty strumienia, aby uzyskać dostęp do danych zakotwiczenia. Zapiszemy dane z buforu w buforze systemowym, dlatego utworzymy element DataWriter, który zapisuje dane w strumieniu danych w pamięci, aby osiągnąć nasz cel, czyli pobieranie kotwic z buforu bajtowego do systemu jako 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);

Ponownie musimy upewnić się, że aplikacja ma uprawnienia do eksportowania danych zakotwiczenia przestrzennego, które mogą zawierać prywatne informacje o środowisku użytkownika.

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

Jeśli dostęp jest dozwolony, możemy zapisywać bajty z buforu do systemowego strumienia danych.

// 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);
    }

Jeśli udało nam się przechowywać bajty w strumieniu danych, możemy spróbować zaimportować te dane przy użyciu klasy 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);
    }

Jeśli można zaimportować dane, otrzymamy widok mapy par klucz-wartość kojarzący ciągi z kotwicami. Możemy załadować ją do własnej kolekcji danych w pamięci i użyć tej kolekcji do wyszukiwania kotwic, których jesteśmy zainteresowani.

}).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;
});
}

UWAGA: To, że można zaimportować kotwicę, nie musi oznaczać, że można jej użyć od razu. Kotwica może być w innym pomieszczeniu lub w innej lokalizacji fizycznej; Kotwica nie będzie przechwycona, dopóki urządzenie, które je odebrało, nie ma wystarczającej ilości informacji wizualnych dotyczących środowiska, w którym utworzono kotwicę, aby przywrócić położenie kotwicy względem znanego bieżącego środowiska. Implementacja klienta powinna spróbować znaleźć kotwicę względem lokalnego układu współrzędnych lub ramki referencyjnej przed kontynuowaniem próby użycia jej dla zawartości na żywo. Na przykład okresowo spróbuj znaleźć kotwicę względem bieżącego układu współrzędnych, aż kotwica zacznie być przechwytywalna.

Uwagi specjalne

Interfejs API TryExportAnchorsAsync umożliwia wyeksportowanie wielu elementów SpatialAnchors do tego samego nieprzezroczystego binarnego obiektu blob. Istnieje jednak subtelna różnica w tym, jakie dane będą dołączane do obiektu blob, w zależności od tego, czy pojedynczy obiekt SpatialAnchor lub wiele obiektów SpatialAnchor jest eksportowanych w jednym wywołaniu.

Eksportowanie pojedynczego planu SpatialAnchor

Obiekt blob zawiera reprezentację środowiska w sąsiedztwie obiektu SpatialAnchor, dzięki czemu środowisko można rozpoznać na urządzeniu, które importuje obiekt SpatialAnchor. Po zakończeniu importowania nowy model SpatialAnchor będzie dostępny dla urządzenia. Zakładając, że użytkownik niedawno znajduje się w sąsiedztwie kotwicy, będzie on locatable i można renderować hologramy dołączone do obiektu SpatialAnchor. Te hologramy będą wyświetlane w tej samej lokalizacji fizycznej, co na oryginalnym urządzeniu, które wyeksportowało usługę SpatialAnchor.

Eksportowanie pojedynczego planu SpatialAnchor

Eksportowanie wielu elementów SpatialAnchors

Podobnie jak w przypadku eksportowania pojedynczego obiektu SpatialAnchor obiekt blob zawiera reprezentację środowiska w sąsiedztwie wszystkich określonych obiektów SpatialAnchor. Ponadto obiekt blob zawiera informacje o połączeniach między dołączonymi obiektami SpatialAnchors, jeśli znajdują się w tej samej przestrzeni fizycznej. Oznacza to, że jeśli zostaną zaimportowane dwa pobliskie urządzenia SpatialAnchor, hologram dołączony do drugiego obiektu SpatialAnchor będzie można przechwycić, nawet jeśli urządzenie rozpozna tylko środowisko wokół pierwszego obiektu SpatialAnchor, ponieważ wystarczająca ilość danych do obliczenia przekształceń między dwoma obiektami SpatialAnchor została uwzględniona w obiekcie blob. Jeśli dwa elementy SpatialAnchor zostały wyeksportowane indywidualnie (dwa oddzielne wywołania funkcji TryExportSpatialAnchors), może być za mało danych zawartych w obiekcie blob, aby hologramy dołączone do drugiego obiektu SpatialAnchor można było przechwycić, gdy znajduje się pierwszy.

Wiele kotwic wyeksportowanych przy użyciu jednego wywołania TryExportAnchorsAsync Wiele kotwic wyeksportowanych przy użyciu oddzielnego wywołania TryExportAnchorsAsync dla każdego zakotwiczenia

Przykład: wysyłanie danych zakotwiczenia przy użyciu Windows::Networking::StreamSocket

W tym miejscu przedstawiamy przykład sposobu używania wyeksportowanych danych kotwicy przez wysyłanie ich przez sieć TCP. Pochodzi to z urządzenia HolographicSpatialAnchorTransferSample.

Klasa WinRT StreamSocket używa biblioteki zadań PPL. W przypadku błędów sieci błąd jest zwracany do następnego zadania w łańcuchu przy użyciu wyjątku, który jest zgłaszany ponownie. Wyjątek zawiera hresult wskazujący stan błędu.

Wysyłanie wyeksportowanych danych zakotwiczenia przy użyciu Windows::Networking::StreamSocketListener z protokołem TCP

Utwórz wystąpienie serwera, które nasłuchuje połączenia.

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");
    }
}

Po otrzymaniu połączenia użyj połączenia gniazda klienta, aby wysłać dane zakotwiczenia.

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());
    }
}

Teraz możemy rozpocząć wysyłanie strumienia danych zawierającego wyeksportowane dane zakotwiczenia.

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");
        }
    });
}

Zanim wyślemy sam strumień, musimy najpierw wysłać pakiet nagłówkowy. Ten pakiet nagłówkowy musi mieć stałą długość i musi również wskazywać długość tablicy zmiennych bajtów, która jest strumieniem danych zakotwiczenia; W tym przykładzie nie mamy żadnych innych danych nagłówka do wysłania, więc nasz nagłówek ma 4 bajty i zawiera 32-bitową liczbę całkowitą bez znaku.

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);

Po wysłaniu długości strumienia w bajtach do klienta możemy przejść do zapisu samego strumienia danych w strumieniu gniazda. Spowoduje to, że bajty magazynu kotwic zostaną wysłane do klienta.

}).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);
    });
}

Jak wspomniano wcześniej w tym temacie, musimy być przygotowani do obsługi wyjątków zawierających komunikaty o stanie błędów sieci. W przypadku błędów, które nie są oczekiwane, możemy zapisać informacje o wyjątku w konsoli debugowania w taki sposób. Daje to wskazówki dotyczące tego, co się stało, jeśli nasz przykładowy kod nie może nawiązaniu połączenia lub jeśli nie może zakończyć wysyłania danych zakotwiczenia.

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

Użyj portu Windows::Networking::StreamSocket z protokołem TCP do odbierania wyeksportowanych danych zakotwiczenia

Najpierw musimy nawiązać połączenie z serwerem. Ten przykładowy kod pokazuje, jak utworzyć i skonfigurować streamSocket, a następnie utworzyć datareader, którego można użyć do uzyskania danych sieciowych przy użyciu połączenia gniazda.

UWAGA: Jeśli uruchomisz ten przykładowy kod, przed uruchomieniem klienta upewnij się, że serwer został skonfigurowany i uruchomiony.

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);
    }
}

Po nawiązaniu połączenia możemy poczekać na wysłanie danych przez serwer. W tym celu wywołamy loadasync na czytniku danych strumienia.

Pierwszy zestaw bajtów, które otrzymujemy, powinien być zawsze pakietem nagłówka, który wskazuje długość bajtów strumienia danych kotwicy zgodnie z opisem w poprzedniej sekcji.

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);
}

Po otrzymaniu pakietu nagłówkowego wiemy, ilu bajtów danych kotwicy powinniśmy oczekiwać. Możemy kontynuować odczytywanie tych bajtów ze strumienia.

}).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);
        }
    });
}

Oto nasz kod do odbierania strumienia danych kotwicy. Ponownie najpierw załadujmy bajty ze strumienia; Ukończenie tej operacji może zająć trochę czasu, ponieważ streamSocket czeka na otrzymanie tej ilości bajtów z sieci.

Po zakończeniu operacji ładowania możemy odczytać liczbę bajtów. Jeśli otrzymamy oczekiwaną liczbę bajtów dla strumienia danych zakotwiczenia, możemy zaimportować dane zakotwiczenia. Jeśli nie, wystąpił jakiś błąd. Na przykład może się to zdarzyć, gdy wystąpienie serwera zakończy działanie, zanim zakończy wysyłanie strumienia danych, lub gdy sieć zostanie przerwana przed otrzymaniem całego strumienia danych przez klienta.

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);
    }
}

Ponownie musimy być przygotowani do obsługi nieznanych błędów sieci.

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

Gotowe. Teraz musisz mieć wystarczającą ilość informacji, aby spróbować zlokalizować kotwice odebrane przez sieć. Ponownie należy pamiętać, że klient musi mieć wystarczającą ilość danych śledzenia wizualnego dla miejsca, aby pomyślnie zlokalizować kotwicę; Jeśli nie zadziała od razu, spróbuj przez jakiś czas. Jeśli to nadal nie działa, niech serwer wyśle więcej kotwic i użyj komunikacji sieciowej, aby uzgodnić taki, który działa dla klienta. Możesz to wypróbować, pobierając urządzenie HolographicSpatialAnchorTransferSample, konfigurując adres IP klienta i serwera oraz wdrażając je na urządzeniach klienckich i HoloLens serwerowych.

Zobacz też