通过远程会话连接设备

远程会话功能允许应用通过会话连接到其他设备,无论是用于明确的应用消息传递还是由系统管理的数据的中介交换(如 SpatialEntityStore,用于 Windows Holographic 设备之间的全息共享)。

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

初始设置

添加远程系统功能

为了使应用在远程设备上启动应用,必须将 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);   
}