通过远程会话连接设备Connect devices through remote sessions

“远程会话”功能允许应用通过会话连接到其他设备,以进行显式应用消息传送或系统管理数据的中转交换,如用于在 Windows Holographic 设备之间进行全息共享的 SpatialEntityStoreThe Remote Sessions feature allows an app to connect to other devices through a session, either for explicit app messaging or for brokered exchange of system-managed data, such as the SpatialEntityStore for holographic sharing between Windows Holographic devices.

远程会话可以由任何 Windows 设备进行创建,并且任何 Windows 设备(包括其他用户登录的设备)都可以请求加入远程会话(尽管会话可能具有“仅邀请”可见性)。Remote sessions can be created by any Windows device, and any Windows device can request to join (although sessions can have invite-only visibility), including devices signed in by other users. 本指南为使用远程会话的所有主要方案提供了基本示例代码。This guide provides basic sample code for all of the major scenarios that make use of remote sessions. 此代码可以合并到现有应用项目中,并且可以根据需要进行修改。This code can be incorporated into an existing app project and modified as necessary. 有关端到端实现,请参阅问答游戏示例应用For an end-to-end implementation, see the Quiz Game sample app).

初步设置Preliminary setup

添加 RemoteSystem 功能Add the remoteSystem capability

为了让你的应用启动远程设备上的应用,必须将 remoteSystem 功能添加到应用包清单。In order for your app to launch an app on a remote device, you must add the remoteSystem capability to your app package manifest. 你可以使用包清单设计器通过选择 "功能" 选项卡上的 "远程系统" 来添加它,也可以手动将以下行添加到项目的_appxmanifest.xml_文件。You can use the package manifest designer to add it by selecting Remote System on the Capabilities tab, or you can manually add the following line to your project's Package.appxmanifest file.

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

在设备上启用跨用户发现Enable cross-user discovering on the device

远程会话旨在连接多个不同的用户,因此涉及的设备将需要启用跨用户共享。Remote Sessions is geared toward connecting multiple different users, so the devices involved will need to have Cross-User Sharing enabled. 这是一种系统设置,可用 RemoteSystem 类中的静态方法进行查询:This is a system setting that can be queried with a static method in the RemoteSystem class:

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

若要更改此设置,用户必须打开设置应用。To change this setting, the user must open the Settings app. 在 "系统 > 共享体验 > 跨设备" 菜单中,有一个下拉框,用户可以在其中指定与系统共享的设备。In the System > Shared experiences > Share across devices menu, there is a drop-down box where the user can specify which devices their system can share with.

共享体验设置页面

包含必需的命名空间Include the necessary namespaces

为了使用本指南中的所有代码段,你将需要在类文件中使用以下 using 语句。In order to use all of the code snippets in this guide, you will need the following using statements in your class file(s).

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

创建远程会话Create a remote session

若要创建远程会话实例,你必须从 RemoteSystemSessionController 对象开始进行操作。To create a remote session instance, you must start with a RemoteSystemSessionController object. 使用以下框架创建新的会话并处理其他设备的加入请求。Use the following framework to create a new session and handle join requests from other devices.

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

将远程会话设为仅邀请会话Make a remote session invite-only

如果希望远程会话无法被公众发现,可以将其设为“仅邀请”会话。If you wish to keep your remote session from being publicly discoverable, you can make it invite-only. 只有收到邀请的设备才能发送加入请求。Only the devices that receive an invitation will be able to send join requests.

此过程基本上与上面相同,但在构建 RemoteSystemSessionController 实例时,你将传入配置的 RemoteSystemSessionOptions 对象。The procedure is mostly the same as above, but when constructing the RemoteSystemSessionController instance, you will pass in a configured RemoteSystemSessionOptions object.

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

//...

若要发送邀请,你必须拥有对接收远程系统的引用(通过正常的远程系统发现来获取)。To send an invitation, you must have a reference to the receiving remote system (acquired through normal remote system discovery). 只需将此引用传入会话对象的 SendInvitationAsync 方法即可。Simply pass this reference into the session object's SendInvitationAsync method. 会话中的所有参与者都拥有对远程会话的引用(请参阅下一部分),所以任何参与者都可以发送邀请。All of the participants in a session have a reference to the remote session (see next section), so any participant can send an invitation.

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

发现并加入远程会话Discover and join a remote session

发现远程会话的过程由 RemoteSystemSessionWatcher 类进行处理,类似于发现单个远程系统。The process of discovering remote sessions is handled by the RemoteSystemSessionWatcher class and is similar to discovering individual remote systems.

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 实例后,它可以用于向控制相应会话的设备发出加入请求。When a RemoteSystemSessionInfo instance is obtained, it can be used to issue a join request to the device that controls the corresponding session. 接受的加入请求将异步返回 RemoteSystemSessionJoinResult 对象,该对象包含对已加入会话的引用。An accepted join request will asynchronously return a RemoteSystemSessionJoinResult object that contains a reference to the joined session.

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

设备可以同时加入到多个会话中。A device can be joined to multiple sessions at the same time. 因此,可能需要将加入功能与每个会话的实际交互分开。For this reason, it may be desirable to separate the joining functionality from the actual interaction with each session. 只要在应用中保留了对 RemoteSystemSession 实例的引用,就可以通过该会话尝试通信。As long as a reference to the RemoteSystemSession instance is maintained in the app, communication can be attempted over that session.

通过远程会话共享消息和数据Share messages and data through a remote session

接收消息Receive messages

你可以使用一个表示单一会话范围的信道的 RemoteSystemSessionMessageChannel 实例与参与会话的其他设备交换消息和数据。You can exchange messages and data with other participant devices in the session by using a RemoteSystemSessionMessageChannel instance, which represents a single session-wide communication channel. 对其进行初始化后,它会立即开始侦听传入的消息。As soon as it's initialized, it begins listening for incoming messages.

备注

在发送和接收时,必须从字节数组对消息进行序列化和反序列化。Messages must be serialized and deserialized from byte arrays upon sending and receiving. 此功能包含在以下示例中,但可以单独实现以获得更好的代码模块性。This functionality is included in the following examples, but it can be implemented separately for better code modularity. 有关此功能的示例,请参阅示例应用See the sample app) for an example of this.

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

发送消息Send messages

建立通道后,向所有会话参与者发送消息很简单。When the channel is established, sending a message to all of the session participants is straightforward.

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

为了仅向特定参与者发送消息,你必须首先启动发现过程以获取对参与会话的远程系统的引用。In order to send a message to only certain participant(s), you must first initiate a discovery process to acquire references to the remote systems participating in the session. 这类似于在会话之外发现远程系统的过程。This is similar to the process of discovering remote systems outside of a session. 使用 RemoteSystemSessionParticipantWatcher 实例查找会话的参与设备。Use a RemoteSystemSessionParticipantWatcher instance to find a session's participant devices.

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

当获得对会话参与者的引用列表后,你可以向其中的任何一组发送消息。When a list of references to session participants is obtained you can send a message to any set of them.

若要将消息发送给单个参与者(理想情况下由用户在屏幕上选择),只需将引用传入如下所示的方法中即可。To send a message to a single participant (ideally selected on-screen by the user), simply pass the reference into a method like the following.

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

若要将消息发送给多个参与者(理想情况下由用户在屏幕上选择),请将其添加到列表对象,并将列表传入如下所示的方法中即可。To send a message to multiple participants (ideally selected on-screen by the user), add them to a list object and pass the list into a method like the following.

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