Connettere i dispositivi tramite sessioni remote

La funzionalità Sessioni remote consente a un'app di connettersi ad altri dispositivi tramite una sessione, per la messaggistica esplicita dell'app o per lo scambio negoziato di dati gestiti dal sistema, ad esempio SpatialEntityStore per la condivisione olografica tra dispositivi Windows Holographic.

Le sessioni remote possono essere create da qualsiasi dispositivo Windows e qualsiasi dispositivo Windows può richiedere di partecipare (anche se le sessioni possono avere visibilità di sola invito), inclusi i dispositivi connessi da altri utenti. Questa guida fornisce codice di esempio di base per tutti gli scenari principali che usano sessioni remote. Questo codice può essere integrato in un progetto di app esistente e modificato in base alle esigenze. Per un'implementazione end-to-end, vedere l'app Quiz Game di esempio.

Impostazioni preliminari

Aggiungere la funzionalità remoteSystem

Per consentire all'app di avviare un'app in un dispositivo remoto, è necessario aggiungere la funzionalità remoteSystem al manifesto del pacchetto dell'app. È possibile usare la finestra di progettazione del manifesto del pacchetto per aggiungerla selezionando Sistema remoto nella scheda Capacità, oppure aggiungere manualmente la riga seguente al file Package.appxmanifest del progetto.

<Capabilities>
   <uap3:Capability Name="remoteSystem"/>
</Capabilities>

Abilitare l'individuazione tra utenti nel dispositivo

Le sessioni remote sono orientate alla connessione di più utenti diversi, quindi i dispositivi coinvolti dovranno avere la condivisione tra utenti abilitata. Si tratta di un'impostazione di sistema su cui è possibile eseguire query con un metodo statico nella classe RemoteSystem:

if (!RemoteSystem.IsAuthorizationKindEnabled(RemoteSystemAuthorizationKind.Anonymous)) {
	// The system is not authorized to connect to cross-user devices. 
	// Inform the user that they can discover more devices if they
	// update the setting to "Everyone nearby".
}

Per modificare questa impostazione, l'utente deve aprire l'app Impostazioni. Nel menu Sistema>Esperienze condivise>Condividi tra dispositivi è disponibile una casella a discesa in cui l'utente può specificare i dispositivi con cui il sistema può condividere.

shared experiences settings page

Includere gli spazi dei nomi necessari

Per usare tutti i frammenti di codice in questa guida, sono necessarie le istruzioni using seguenti nei file di classe.

using System.Runtime.Serialization.Json;
using Windows.Foundation.Collections;
using Windows.System.RemoteSystems;

Creare una sessione remota

Per creare un'istanza di sessione remota, è necessario iniziare con un oggetto RemoteSystemSessionController. Usare il framework seguente per creare una nuova sessione e gestire le richieste di collegamento da altri dispositivi.

public async void CreateSession() {
    
    // create a session controller
    RemoteSystemSessionController manager = new RemoteSystemSessionController("Bob’s Minecraft game");
    
    // register the following code to handle the JoinRequested event
    manager.JoinRequested += async (sender, args) => {
        // Get the deferral
        var deferral = args.GetDeferral();
        
        // display the participant (args.JoinRequest.Participant) on UI, giving the 
        // user an opportunity to respond
        // ...
        
        // If the user chooses "accept", accept this remote system as a participant
        args.JoinRequest.Accept();
    };
    
    // create and start the session
    RemoteSystemSessionCreationResult createResult = await manager.CreateSessionAsync();
    
    // handle the creation result
    if (createResult.Status == RemoteSystemSessionCreationStatus.Success) {
        // creation was successful, get a reference to the session
        RemoteSystemSession currentSession = createResult.Session;
        
        // optionally subscribe to the disconnection event
        currentSession.Disconnected += async (sender, args) => {
            // update the UI, using args.Reason
            //...
        };
    
        // Use session (see later section)
        //...
    
    } else if (createResult.Status == RemoteSystemSessionCreationStatus.SessionLimitsExceeded) {
        // creation failed. Optionally update UI to indicate that there are too many sessions in progress
    } else {
        // creation failed for an unknown reason. Optionally update UI
    }
}

Creare un invito solo alla sessione remota

Se si desidera impedire che la sessione remota sia individuabile pubblicamente, è possibile renderla di solo invito. Solo i dispositivi che ricevono un invito potranno inviare richieste di collegamento.

La procedura è per lo più uguale a quella precedente, ma quando si costruisce l'istanza RemoteSystemSessionController, si passerà a un oggetto RemoteSystemSessionOptions configurato.

// define the session options with the invite-only designation
RemoteSystemSessionOptions sessionOptions = new RemoteSystemSessionOptions();
sessionOptions.IsInviteOnly = true;

// create the session controller
RemoteSystemSessionController manager = new RemoteSystemSessionController("Bob's Minecraft game", sessionOptions);

//...

Per inviare un invito, è necessario avere un riferimento al sistema remoto ricevente (acquisito tramite l'individuazione normale del sistema remoto). È sufficiente passare questo riferimento al metodo SendInvitationAsync dell'oggetto di sessione. Tutti i partecipanti a una sessione hanno un riferimento alla sessione remota (vedere la sezione successiva), in modo che qualsiasi partecipante possa inviare un invito.

// "currentSession" is a reference to a RemoteSystemSession.
// "guestSystem" is a previously discovered RemoteSystem instance
currentSession.SendInvitationAsync(guestSystem); 

Individuare e collegarsi a una sessione remota

Il processo di individuazione delle sessioni remote viene gestito dalla classe RemoteSystemSessionWatcher ed è simile all'individuazione di singoli sistemi remoti.

public void DiscoverSessions() {
    
    // create a watcher for remote system sessions
    RemoteSystemSessionWatcher sessionWatcher = RemoteSystemSession.CreateWatcher();
    
    // register a handler for the "added" event
    sessionWatcher.Added += async (sender, args) => {
        
        // get a reference to the info about the discovered session
        RemoteSystemSessionInfo sessionInfo = args.SessionInfo;
        
        // Optionally update the UI with the sessionInfo.DisplayName and 
        // sessionInfo.ControllerDisplayName strings. 
        // Save a reference to this RemoteSystemSessionInfo to use when the
        // user selects this session from the UI
        //...
    };
    
    // Begin watching
    sessionWatcher.Start();
}

Quando si ottiene un'istanza RemoteSystemSessionInfo, può essere usata per inviare una richiesta di collegamento al dispositivo che controlla la sessione corrispondente. Una richiesta di collegamento accettata restituirà in modo asincrono un oggetto RemoteSystemSessionJoinResult che contiene un riferimento alla sessione aggiunta.

public async void JoinSession(RemoteSystemSessionInfo sessionInfo) {

    // issue a join request and wait for result.
    RemoteSystemSessionJoinResult joinResult = await sessionInfo.JoinAsync();
    if (joinResult.Status == RemoteSystemSessionJoinStatus.Success) {
        // Join request was approved

        // RemoteSystemSession instance "currentSession" was declared at class level.
        // Assign the value obtained from the join result.
        currentSession = joinResult.Session;
        
        // note connection and register to handle disconnection event
        bool isConnected = true;
        currentSession.Disconnected += async (sender, args) => {
            isConnected = false;

            // update the UI with args.Reason value
        };
        
        if (isConnected) {
            // optionally use the session here (see next section)
            //...
        }
    } else {
        // Join was unsuccessful.
        // Update the UI, using joinResult.Status value to show cause of failure.
    }
}

Un dispositivo può essere aggiunto a più sessioni contemporaneamente. Per questo motivo, potrebbe essere consigliabile separare la funzionalità di collegamento dall'interazione effettiva con ogni sessione. Se nell'app viene mantenuto un riferimento all'istanza RemoteSystemSession, è possibile tentare la comunicazione su tale sessione.

Condividere messaggi e dati tramite una sessione remota

Ricevere messaggi

È possibile scambiare messaggi e dati con altri dispositivi partecipanti nella sessione usando un'istanza remoteSystemSessionMessageChannel, che rappresenta un singolo canale di comunicazione a livello di sessione. Non appena viene inizializzato, inizia ad ascoltare i messaggi in arrivo.

Nota

I messaggi devono essere serializzati e deserializzati da matrici di byte al momento dell'invio e della ricezione. Questa funzionalità è inclusa negli esempi seguenti, ma può essere implementata separatamente per una migliore modularità del codice. Vedere la app di esempio) per un esempio di ciò.

public async void StartReceivingMessages() {
    
    // Initialize. The channel name must be known by all participant devices 
    // that will communicate over it.
    RemoteSystemSessionMessageChannel messageChannel = new RemoteSystemSessionMessageChannel(currentSession, 
        "Everyone in Bob's Minecraft game", 
        RemoteSystemSessionMessageChannelReliability.Reliable);
    
    // write the handler for incoming messages on this channel
    messageChannel.ValueSetReceived += async (sender, args) => {
        
        // Update UI: a message was received from the participant args.Sender
        
        // Deserialize the message 
        // (this app must know what key to use and what object type the value is expected to be)
        ValueSet receivedMessage = args.Message;
        object rawData = receivedMessage["appKey"]);
        object value = new ExpectedType(); // this must be whatever type is expected

        using (var stream = new MemoryStream((byte[])rawData)) {
            value = new DataContractJsonSerializer(value.GetType()).ReadObject(stream);
        }
        
        // do something with the "value" object
        //...
    };
}

Inviare messaggi

Quando viene stabilito il canale, l'invio di un messaggio a tutti i partecipanti alla sessione è semplice.

public async void SendMessageToAllParticipantsAsync(RemoteSystemSessionMessageChannel messageChannel, object value){

    // define a ValueSet message to send
    ValueSet message = new ValueSet();
    
    // serialize the "value" object to send
    using (var stream = new MemoryStream()){
        new DataContractJsonSerializer(value.GetType()).WriteObject(stream, value);
        byte[] rawData = stream.ToArray();
            message["appKey"] = rawData;
    }
    
    // Send message to all participants. Ordering is not guaranteed.
    await messageChannel.BroadcastValueSetAsync(message);
}

Per inviare un messaggio solo a determinati partecipanti, è necessario innanzitutto avviare un processo di individuazione per acquisire riferimenti ai sistemi remoti che partecipano alla sessione. Ciò è simile al processo di individuazione di sistemi remoti all'esterno di una sessione. Usare un'istanza RemoteSystemSessionParticipantWatcher per trovare i dispositivi partecipanti a una sessione.

public void WatchForParticipants() {
    // "currentSession" is a reference to a RemoteSystemSession.
    RemoteSystemSessionParticipantWatcher watcher = currentSession.CreateParticipantWatcher();

    watcher.Added += (sender, participant) => {
        // save a reference to "participant"
        // optionally update UI
    };   

    watcher.Removed += (sender, participant) => {
        // remove reference to "participant"
        // optionally update UI
    };

    watcher.EnumerationCompleted += (sender, args) => {
        // Apps can delay data model render up until this point if they wish.
    };

    // Begin watching for session participants
    watcher.Start();
}

Quando si ottiene un elenco di riferimenti ai partecipanti alla sessione, è possibile inviare un messaggio a qualsiasi set di essi.

Per inviare un messaggio a un singolo partecipante (idealmente selezionato sullo schermo dall'utente), è sufficiente passare il riferimento a un metodo simile al seguente.

public async void SendMessageToParticipantAsync(RemoteSystemSessionMessageChannel messageChannel, RemoteSystemSessionParticipant participant, object value) {
    
    // define a ValueSet message to send
    ValueSet message = new ValueSet();
    
    // serialize the "value" object to send
    using (var stream = new MemoryStream()){
        new DataContractJsonSerializer(value.GetType()).WriteObject(stream, value);
        byte[] rawData = stream.ToArray();
            message["appKey"] = rawData;
    }

    // Send message to the participant
    await messageChannel.SendValueSetAsync(message,participant);
}

Per inviare un messaggio a più partecipanti (idealmente selezionati sullo schermo dall'utente), aggiungerli a un oggetto elenco e passare l'elenco in un metodo simile al seguente.

public async void SendMessageToListAsync(RemoteSystemSessionMessageChannel messageChannel, IReadOnlyList<RemoteSystemSessionParticipant> myTeam, object value){

    // define a ValueSet message to send
    ValueSet message = new ValueSet();
    
    // serialize the "value" object to send
    using (var stream = new MemoryStream()){
        new DataContractJsonSerializer(value.GetType()).WriteObject(stream, value);
        byte[] rawData = stream.ToArray();
            message["appKey"] = rawData;
    }

    // Send message to specific participants. Ordering is not guaranteed.
    await messageChannel.SendValueSetToParticipantsAsync(message, myTeam);   
}