Статьи

Windows Phone 8: одноранговое соединение (Bluetooth и Wi-Fi Direct)

Одной из особенностей, которые меня взволновали в Windows Phone 8 SDK, являются API-интерфейсы Proximity. Эти API предоставляют вашему приложению доступ к Bluetooth-соединению и Wi-Fi Direct / NFC для совместного использования всего, что вы хотите, с 2-х совместимых телефонов, на которых запущено приложение, ожидающее соединения. Мой пример довольно прост и не подходит для реальной жизни, но это хорошо для начинающих.

В этом примере мы будем интенсивно использовать класс PeerFinder, который предоставляет функции для принятия и ожидания подключений и асинхронного поиска одноранговых узлов для подключения. Как только соединение установлено, мы можем читать и писать, используя двоичные устройства чтения через сокеты. Довольно легко!

Проект можно скачать здесь .

MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Windows.Foundation;
using Windows.Networking.Proximity;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;

namespace PhoneApp1
{
    public partial class MainPage : PhoneApplicationPage
    {
        private IReadOnlyList<PeerInformation> _peerInformationList;
        private PeerInformation _requestingPeer;
        private StreamSocket _socket = null;
        private bool _socketClosed = true;
        private DataWriter _dataWriter;
        private DataReader _dataReader;
        private bool _triggeredConnectSupported = false;

        bool _isLaunchedByTap = false;
        private bool _browseConnectSupported = false;
        // Constructor
        public MainPage()
        {
            InitializeComponent();
            Loaded += MainPage_Loaded;
        }

        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            _triggeredConnectSupported = (PeerFinder.SupportedDiscoveryTypes & PeerDiscoveryTypes.Triggered) ==
                             PeerDiscoveryTypes.Triggered;
            _browseConnectSupported = (PeerFinder.SupportedDiscoveryTypes & PeerDiscoveryTypes.Browse) ==
                                      PeerDiscoveryTypes.Browse;
            if (_triggeredConnectSupported || _browseConnectSupported)
            {
                // This scenario demonstrates "PeerFinder" to browse for peers to connect to using a StreamSocket
                PeerFinder_StartFindingPeersButton.Click += new RoutedEventHandler(PeerFinder_StartFindingPeers);
                PeerFinder_BrowsePeersButton.Click += new RoutedEventHandler(PeerFinder_BrowsePeers);
                PeerFinder_ConnectButton.Click += new RoutedEventHandler(PeerFinder_Connect);
                PeerFinder_AcceptButton.Click += new RoutedEventHandler(PeerFinder_Accept);
                PeerFinder_SendButton.Click += new RoutedEventHandler(PeerFinder_Send);
                PeerFinder_StartFindingPeersButton.Visibility = Visibility.Visible;
            }
        }
        string[] rgConnectState = {"PeerFound", 
                                   "Listening",
                                   "Connecting",
                                   "Completed",
                                   "Canceled",
                                   "Failed"};
        public void NotifyUser(string strMessage, NotifyType type)
        {
            Dispatcher.BeginInvoke( () =>
            {
                switch (type)
                {
                    // Use the status message style.
                    case NotifyType.StatusMessage:
                        MessageBox.Show(strMessage,"Status",MessageBoxButton.OK);
                        break;
                    // Use the error message style.
                    case NotifyType.ErrorMessage:
                        MessageBox.Show(strMessage,"Error",MessageBoxButton.OK);
                        break;
                }
            });
        }
         private void TriggeredConnectionStateChangedEventHandler(object sender, TriggeredConnectionStateChangedEventArgs eventArgs)
        {
            
            if (eventArgs.State == TriggeredConnectState.PeerFound)
            {
                // Use this state to indicate to users that the tap is complete and
                // they can pull there devices away.
                NotifyUser("Tap complete, socket connection starting!", NotifyType.StatusMessage);
            }

            if (eventArgs.State == TriggeredConnectState.Completed)
            {
                NotifyUser("Socket connect success!", NotifyType.StatusMessage);
                // Grab the socket that just connected.
                _socket = eventArgs.Socket;
                Dispatcher.BeginInvoke(() =>
                {
                    this.PeerFinder_StartSendReceive();
                });

            }

            if (eventArgs.State == TriggeredConnectState.Failed)
            {
                NotifyUser("Socket connect failed!", NotifyType.ErrorMessage);
            }
        }

        private bool _peerFinderStarted = false;

        private void SocketError(String errMessage)
        {
            NotifyUser(errMessage, NotifyType.ErrorMessage);
            PeerFinder_StartFindingPeersButton.Visibility = Visibility.Visible;
            if (_browseConnectSupported)
            {
                PeerFinder_BrowsePeersButton.Visibility = Visibility.Visible;
            }
            PeerFinder_SendButton.Visibility = Visibility.Collapsed;
            PeerFinder_MessageBox.Visibility = Visibility.Collapsed;
            if (!_socketClosed)
            {
                _socketClosed = true;
                _socket.Dispose();

                _socket = null;
            }
        }

        async private void PeerFinder_Send(object sender, RoutedEventArgs e)
        {
            NotifyUser("", NotifyType.ErrorMessage);
            String message = PeerFinder_MessageBox.Text;
            PeerFinder_MessageBox.Text = ""; // clear the input now that the message is being sent.
            if (!_socketClosed)
            {
                if (message.Length > 0)
                {
                    try
                    {
                        uint strLength = _dataWriter.MeasureString(message);
                        _dataWriter.WriteUInt32(strLength);
                        _dataWriter.WriteString(message);
                        uint numBytesWritten = await _dataWriter.StoreAsync();
                        if (numBytesWritten > 0)
                        {
                            NotifyUser("Sent message: " + message + ", number of bytes written: " + numBytesWritten, NotifyType.StatusMessage);

                        }
                        else
                        {
                            SocketError("The remote side closed the socket");
                        }
                    }
                    catch (Exception err)
                    {
                        if (!_socketClosed)
                        {
                            SocketError("Failed to send message with error: " + err.Message);
                        }
                    }
                }
                else
                {
                    NotifyUser("Please type a message", NotifyType.ErrorMessage);
                }
            }
            else
            {
                SocketError("The remote side closed the socket");
            }
        }

        async private void PeerFinder_Accept(object sender, RoutedEventArgs e)
        {
            NotifyUser("Connecting to " + _requestingPeer.DisplayName + "....", NotifyType.StatusMessage);
            PeerFinder_AcceptButton.Visibility = Visibility.Collapsed;
            try
            {
                _socket = await PeerFinder.ConnectAsync(_requestingPeer);
                NotifyUser("Connection succeeded", NotifyType.StatusMessage);
                PeerFinder_StartSendReceive();
            }
            catch (Exception err)
            {
                NotifyUser("Connection to " + _requestingPeer.DisplayName + " failed: " + err.Message, NotifyType.ErrorMessage);
            }
        }

        private void PeerConnectionRequested(object sender, ConnectionRequestedEventArgs args)
        {
            _requestingPeer = args.PeerInformation;
             Dispatcher.BeginInvoke( () =>
            {
                NotifyUser("Connection requested from peer " + args.PeerInformation.DisplayName, NotifyType.StatusMessage);

                this.PeerFinder_AcceptButton.Visibility = Visibility.Visible;
                this.PeerFinder_SendButton.Visibility = Visibility.Collapsed;
                this.PeerFinder_MessageBox.Visibility = Visibility.Collapsed;
            });
        }

        async void PeerFinder_StartReader()
        {
            try
            {
                uint bytesRead = await _dataReader.LoadAsync(sizeof(uint));
                if (bytesRead > 0)
                {
                    uint strLength = (uint)_dataReader.ReadUInt32();
                    bytesRead = await _dataReader.LoadAsync(strLength);
                    if (bytesRead > 0)
                    {
                        String message = _dataReader.ReadString(strLength);
                        NotifyUser("Got message: " + message, NotifyType.StatusMessage);
                        PeerFinder_StartReader(); // Start another reader
                    }
                    else
                    {
                        SocketError("The remote side closed the socket");
                    }
                }
                else
                {
                    SocketError("The remote side closed the socket");
                }
            }
            catch (Exception e)
            {
                if (!_socketClosed)
                {
                    SocketError("Reading from socket failed: " + e.Message);
                }
            }
        }

        // Start the send receive operations
        void PeerFinder_StartSendReceive()
        {
            PeerFinder_SendButton.Visibility = Visibility.Visible;
            PeerFinder_MessageBox.Visibility = Visibility.Visible;

            // Hide the controls related to setting up a connection
            PeerFinder_ConnectButton.Visibility = Visibility.Collapsed;
            PeerFinder_AcceptButton.Visibility = Visibility.Collapsed;
            PeerFinder_FoundPeersList.Visibility = Visibility.Collapsed;
            PeerFinder_BrowsePeersButton.Visibility = Visibility.Collapsed;
            PeerFinder_StartFindingPeersButton.Visibility = Visibility.Collapsed;
            _dataReader = new DataReader(_socket.InputStream);
            _dataWriter = new DataWriter(_socket.OutputStream);
            _socketClosed = false;
            PeerFinder_StartReader();
        }

        async void PeerFinder_Connect(object sender, RoutedEventArgs e)
        {
            NotifyUser("", NotifyType.ErrorMessage);
            PeerInformation peerToConnect = null;
            try
            {
                // If nothing is selected, select the first peer
                if (PeerFinder_FoundPeersList.SelectedIndex == -1)
                {
                    peerToConnect = _peerInformationList[0];
                }
                else
                {
                    peerToConnect = _peerInformationList[PeerFinder_FoundPeersList.SelectedIndex];
                }

               NotifyUser("Connecting to " + peerToConnect.DisplayName + "....", NotifyType.StatusMessage);
                _socket = await PeerFinder.ConnectAsync(peerToConnect);
                NotifyUser("Connection succeeded", NotifyType.StatusMessage);
                PeerFinder_StartSendReceive();
            }
            catch (Exception err)
            {
                NotifyUser("Connection to " + peerToConnect.DisplayName + " failed: " + err.Message, NotifyType.ErrorMessage);
            }
        }

        async void PeerFinder_BrowsePeers(object sender, RoutedEventArgs e)
        {
            NotifyUser("Finding Peers...", NotifyType.StatusMessage);
            try
            {
                _peerInformationList = await PeerFinder.FindAllPeersAsync();
            }
            catch (Exception ex)
            {
                Debug.WriteLine("FindAll throws exception" + ex.Message);
            }
            Debug.WriteLine("Async operation completed");
            NotifyUser("No peers found", NotifyType.StatusMessage);
            try{
            if (_peerInformationList.Count > 0)
            {
                PeerFinder_FoundPeersList.Items.Clear();
                for (int i = 0; i < _peerInformationList.Count; i++)
                {
                    ListBoxItem item = new ListBoxItem();
                    item.Content = _peerInformationList[i].DisplayName;
                    PeerFinder_FoundPeersList.Items.Add(item);
                }
                PeerFinder_ConnectButton.Visibility = Visibility.Visible;
                PeerFinder_FoundPeersList.Visibility = Visibility.Visible;
                NotifyUser("Finding Peers Done", NotifyType.StatusMessage);
            }}
            catch
            {
                NotifyUser("No peers found", NotifyType.StatusMessage);
                PeerFinder_ConnectButton.Visibility = Visibility.Collapsed;
                PeerFinder_FoundPeersList.Visibility = Visibility.Collapsed;
            }
        }

        void PeerFinder_StartFindingPeers(object sender, RoutedEventArgs e)
        {
            NotifyUser("", NotifyType.ErrorMessage);
            if (!_peerFinderStarted)
            {
                // attach the callback handler (there can only be one PeerConnectProgress handler).
                PeerFinder.TriggeredConnectionStateChanged += new TypedEventHandler<object, TriggeredConnectionStateChangedEventArgs>(TriggeredConnectionStateChangedEventHandler);
                // attach the incoming connection request event handler
                PeerFinder.ConnectionRequested += new TypedEventHandler<object, ConnectionRequestedEventArgs>(PeerConnectionRequested);
                // start listening for proximate peers
                PeerFinder.Start();
                _peerFinderStarted = true;
                if (_browseConnectSupported && _triggeredConnectSupported)
                {
                    NotifyUser("Tap another device to connect to a peer or click Browse for Peers button.", NotifyType.StatusMessage);
                    PeerFinder_BrowsePeersButton.Visibility = Visibility.Visible;
                }
                else if (_triggeredConnectSupported)
                {
                    NotifyUser("Tap another device to connect to a peer.", NotifyType.StatusMessage);
                }
                else if (_browseConnectSupported)
                {
                    NotifyUser("Click Browse for Peers button.", NotifyType.StatusMessage);
                }
            }
        }

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            if (_triggeredConnectSupported || _browseConnectSupported)
            {
                // Initially only the advertise button should be visible.
                PeerFinder_StartFindingPeersButton.Visibility = Visibility.Visible;
                PeerFinder_BrowsePeersButton.Visibility = Visibility.Collapsed;
                PeerFinder_ConnectButton.Visibility = Visibility.Collapsed;
                PeerFinder_FoundPeersList.Visibility = Visibility.Collapsed;
                PeerFinder_SendButton.Visibility = Visibility.Collapsed;
                PeerFinder_AcceptButton.Visibility = Visibility.Collapsed;
                PeerFinder_MessageBox.Visibility = Visibility.Collapsed;
                PeerFinder_MessageBox.Text = "Hello World";
                if (IsLaunchedByTap())
                {
                    NotifyUser("Launched by tap", NotifyType.StatusMessage);
                    PeerFinder_StartFindingPeers(null, null);
                }
                else
                {
                    if (!_triggeredConnectSupported)
                    {
                        NotifyUser("Tap based discovery of peers not supported", NotifyType.ErrorMessage);
                    }
                    else if (!_browseConnectSupported)
                    {
                        NotifyUser("Browsing for peers not supported", NotifyType.ErrorMessage);
                    }
                }
            }
            else
            {
                NotifyUser("Tap based discovery of peers not supported \nBrowsing for peers not supported", NotifyType.ErrorMessage);
            }
        }

        // Invoked when the main page navigates to a different scenario
        protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
        {
            if (_peerFinderStarted)
            {
                // detach the callback handler (there can only be one PeerConnectProgress handler).
PeerFinder.TriggeredConnectionStateChanged -= new TypedEventHandler<object, TriggeredConnectionStateChangedEventArgs>(TriggeredConnectionStateChangedEventHandler);
                // detach the incoming connection request event handler
                PeerFinder.ConnectionRequested -= new TypedEventHandler<object, ConnectionRequestedEventArgs>(PeerConnectionRequested);
                PeerFinder.Stop();
                if (_socket != null)
                {
                    _socketClosed = true;
                    _socket.Dispose();

                    _socket = null;
                }
                _peerFinderStarted = false;
            }
        }
        public bool IsLaunchedByTap()
        {
            bool isLaunchedByTap = _isLaunchedByTap;
            _isLaunchedByTap = false;
            return isLaunchedByTap;
        }
        public enum NotifyType
        {
            StatusMessage,
            ErrorMessage
        };
    }
}

MainPage.xaml
    <phone:PhoneApplicationPage
    x:Class="PhoneApp1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock Text="{Binding Path=LocalizedResources.PageTitle, Source={StaticResource LocalizedStrings}}" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <StackPanel Orientation="Vertical" Margin="0,10,0,0" Grid.Row="0">
                <Button x:Name="PeerFinder_StartFindingPeersButton" Content="Advertise" Visibility="Collapsed" Margin="0,0,10,0"/>
                <Button x:Name="PeerFinder_BrowsePeersButton" Content="Browse for Peers" Visibility="Collapsed" Margin="0,0,10,0"/>
                <Button x:Name="PeerFinder_ConnectButton" Content="Connect To a Peer" Visibility="Collapsed" Margin="0,0,10,0"/>
                <ListBox x:Name="PeerFinder_FoundPeersList" Visibility="Collapsed">
                </ListBox>
            </StackPanel>
            <StackPanel Orientation="Vertical" Margin="0,10,0,0" Grid.Row="1">
                <Button x:Name="PeerFinder_AcceptButton" Content="Accept Connection Request" Visibility="Collapsed" Margin="0,0,10,0"/>
                <Button x:Name="PeerFinder_SendButton" Content="Send Message" Visibility="Collapsed" Margin="0,0,10,0"/>
                <TextBox x:Name="PeerFinder_MessageBox" Visibility="Collapsed" Width="400" Margin="0,0,10,0"/>
            </StackPanel>
        </Grid>

        <Grid x:Name="Output" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="1">
            <TextBlock x:Name="PeerFinderOutputText" TextWrapping="Wrap" />

        </Grid>


        <!--Uncomment to see an alignment grid to help ensure your controls are
            aligned on common boundaries.  Remove or comment out before shipping
            your application.-->
        <!--<Image Margin="0" Source="/Assets/AlignmentGrid.png" Stretch="None" IsHitTestVisible="False" />-->
    </Grid>

</phone:PhoneApplicationPage>

Конечно, вам нужно немного подправить файл Proprieties (я только что проверил все авторизации). И вот у вас есть простой способ отправки сообщений между смартфоном WP8. Я не мог попробовать это, потому что виртуальная машина не может получить доступ к Bluetooth, но я уверен, что когда я получу в свои руки реальные машины, это будет.

Также я должен упомянуть, что этот код является простым портом Proximity Lab для Windows RT, и можно только надеяться, что это позволит передавать данные с Windows Phone на Windows Tablet и наоборот.