通过远程会话连接设备

“远程会话”功能允许应用通过会话连接到其他设备,以进行显式应用消息传送或系统管理数据的中转交换,如用于在 Windows Holographic 设备之间进行全息共享的 SpatialEntityStore

远程会话可以由任何 Windows 设备进行创建,并且任何 Windows 设备(包括其他用户登录的设备)都可以请求加入远程会话(尽管会话可能具有“仅邀请”可见性)。 本指南为使用远程会话的所有主要方案提供了基本示例代码。 此代码可以合并到现有应用项目中,并且可以根据需要进行修改。 有关端到端实现,请参阅问答游戏示例应用

初步设置

添加 RemoteSystem 功能

为了让你的应用启动远程设备上的应用,必须将 remoteSystem 功能添加到应用包清单。 可以通过选择“功能”选项卡上的“远程系统”来使用程序包清单设计器添加它,也可以手动将以下行添加到项目的 Package.appxmanifest 文件。

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

在设备上启用跨用户发现

远程会话旨在连接多个不同的用户,因此涉及的设备将需要启用跨用户共享。 这是一种系统设置,可用 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".
}

若要更改此设置,用户必须打开设置应用。 在系统>共享体验>跨设备共享菜单中有一个下拉框,用户可在此指定系统可与哪些设备共享体验。

共享体验设置页面

包含必需的命名空间

为了使用本指南中的所有代码段,你将需要在类文件中使用以下 using 语句。

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

创建远程会话

若要创建远程会话实例,你必须从 RemoteSystemSessionController 对象开始进行操作。 使用以下框架创建新的会话并处理其他设备的加入请求。

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

将远程会话设为仅邀请会话

如果希望远程会话无法被公众发现,可以将其设为“仅邀请”会话。 只有收到邀请的设备才能发送加入请求。

此过程基本上与上面相同,但在构建 RemoteSystemSessionController 实例时,你将传入配置的 RemoteSystemSessionOptions 对象。

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

//...

若要发送邀请,你必须拥有对接收远程系统的引用(通过正常的远程系统发现来获取)。 只需将此引用传入会话对象的 SendInvitationAsync 方法即可。 会话中的所有参与者都拥有对远程会话的引用(请参阅下一部分),所以任何参与者都可以发送邀请。

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

发现并加入远程会话

发现远程会话的过程由 RemoteSystemSessionWatcher 类进行处理,类似于发现单个远程系统。

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

获取 RemoteSystemSessionInfo 实例后,它可以用于向控制相应会话的设备发出加入请求。 接受的加入请求将异步返回 RemoteSystemSessionJoinResult 对象,该对象包含对已加入会话的引用。

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

设备可以同时加入到多个会话中。 因此,可能需要将加入功能与每个会话的实际交互分开。 只要在应用中保留了对 RemoteSystemSession 实例的引用,就可以通过该会话尝试通信。

通过远程会话共享消息和数据

接收消息

你可以使用一个表示单一会话范围的信道的 RemoteSystemSessionMessageChannel 实例与参与会话的其他设备交换消息和数据。 对其进行初始化后,它会立即开始侦听传入的消息。

注意

在发送和接收时,必须从字节数组对消息进行序列化和反序列化。 此功能包含在以下示例中,但可以单独实现以获得更好的代码模块性。 有关此功能的示例,请参阅示例应用

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

发送消息

建立通道后,向所有会话参与者发送消息很简单。

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

为了仅向特定参与者发送消息,你必须首先启动发现过程以获取对参与会话的远程系统的引用。 这类似于在会话之外发现远程系统的过程。 使用 RemoteSystemSessionParticipantWatcher 实例查找会话的参与设备。

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

当获得对会话参与者的引用列表后,你可以向其中的任何一组发送消息。

若要将消息发送给单个参与者(理想情况下由用户在屏幕上选择),只需将引用传入如下所示的方法中即可。

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

若要将消息发送给多个参与者(理想情况下由用户在屏幕上选择),请将其添加到列表对象,并将列表传入如下所示的方法中即可。

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