WinRT : Transfering a file between 2 peers using Wifi-Direct and Proximity API

On Windows 8.1 and WinRT, Wifi-direct enables wireless direct communication scenarios between devices executing the same application. That means you can exchange data between peers in areas without any internet network connectivity. All you need is a device that supports Wifi-Direct technology (Surface RT does, as well as most Windows 8 devices). This is complementary to the Share charm functionnality which helps sharing data between 2 apps on the same machine. Here you will share stuff on different machines that are executing the same app.

Wifi-direct can be a good alternative to NFC for some scenarios : you don’t need any NFC hardware but still can communicate between several close peers without network connectivity. If ever you want to test NFC but you don’t have the appropriate hardware, I wrote an article explaining how to install an NFC simulator driver on Windows 8.1 : Windows 8.1 : How to use Near-Field Proximity API without NFC hardware.

Wifi-direct communication is made possible thanks to the Proximity API (just like NFC does, but with different scenarios).

Here is a small lib providing the Wifi-direct file transfer fonctionnality and its sample demo application so that you can test it.

Please be aware that this was not tested for production : if you use it as is, it is at your own risks.

In this article, you will find:

  • how to use the lib and the sample app
  • behind the scene : how the Proximity API handles Wifi-direct
  • more possibilities for Wifi-direct scenarios

How does the lib work ?

The WifiDirectTransfer library is based on the Proximity API. It is VERY easy to use : you just need 3 lines of code.

The lib contains a WifiDirectFileTransfer class.

image

The same class is used by both the sender and receiver.

 public WifiDirectFileTransfer(Action<string> verboseCb = null)
 public void Start()
 public async Task<IEnumerable<PeerInformation>> FindPeersAsync()
 public async Task ConnectAndSendFileAsync(PeerInformation selectedPeer, StorageFile selectedFile)
 public async Task<string> ReceiveFileAsync(PeerInformation requestingPeer, 
 StorageFolder folder, string outputFilename = null)

The ctor parameter is an optional verbose callback, so that you can follow the communication process easily.

Once instanciated, you can:

Sender

- Start browsing for other peers (Start)

- Send a file to a peer (ConnectAndSendFileAsync)

Receiver

- Register to the ConnectionRequested event

- Start listening to other peers (Start)

- Receive and save a file once a peer connected to you (ReceiveFileAsync)

Updating the manifest

For an application referencing the lib, you will have to check the proximity capability:

image

Testing with the sample application

The sample demo application must be started on 2 Windows 8.1 devices supporting Wifi-direct.

Sender           image Receiver           image

The application has 2 buttons. The sender should click the enabled button first:

Sender           image Receiver           image

The sender application just discovered another device running the same application, the device name is SurfaceRT.

Now, double-click the device you want to connect to and browse the file you want to send.

Sender

image

Click open : the receiver will then get the ConnectionRequested event.

Receiver

image

The SurfaceRT device has just received the connection request from the sender who’s machine name is MININT-A34GQT7.

Click the second button that was just enabled by the sender connection. The connection phase will take some time (on my device, it can be more than 10 secs): please be patient Sourire.

Now the file will be transfered from the sender to the receiver.

Sender           image Receiver           image

You will then see the download progress shown on the status bar.

Then, the image will be saved on disk and shown on the receiver screen.

Receiver

image

Note: Restart the app to initiate a new transfer.

Behind the scene : how the Proximity API handles Wifi-direct

The Proximity API provides an easy way to get a Stream socket with Wifi-direct.

Each peer should start by calling

 PeerFinder.Start();

Then you can browse for other peers using the same application with

 PeerFinder.FindAllPeersAsync();

You may have assigned roles (host, client, peers) to peers to be able to find only the appropriate ones.

You can then connect to a specific peer to get the stream socket:

 var socket = await PeerFinder.ConnectAsync(selectedPeer);

The peer gets then a specific event:

 PeerFinder.ConnectionRequested += PeerFinder_ConnectionRequested;
 

and can then connect in response :

 StreamSocket socket = await PeerFinder.ConnectAsync(requestingPeer);
 

Then you can start exchanging data on the socket.

And what if I want to transfer other kind of data ?

I chose a file, but of course, any kind of data could be transfered : the communication is made on a classic Stream socket. Just make sure your receiver will be able to understand what you send him.

In my example, I made a little protocol to be able to transfer a file. I do it in 4 steps:

  • send the filename length
  • send the filename
  • send the file length
  • send the file itself

Here is an extract of the code:

     private async Task SendFileToPeerAsync(PeerInformation selectedPeer, 
        StreamSocket socket, StorageFile selectedFile)      
    {
        byte[] buff = new byte[BLOCK_SIZE];
        var prop = await selectedFile.GetBasicPropertiesAsync();
        using (var dw = new DataWriter(socket.OutputStream))
        {
            // 1. Send the filename length
            dw.WriteInt32(selectedFile.Name.Length); 
            // 2. Send the filename
            dw.WriteString(selectedFile.Name);
            // 3. Send the file length
            dw.WriteUInt64(prop.Size);
            // 4. Send the file
            var fileStream = await selectedFile.OpenStreamForReadAsync();
            while (fileStream.Position < (long)prop.Size)
            {
                var rlen = await fileStream.ReadAsync(buff, 0, buff.Length);
                dw.WriteBytes(buff);
            }

            await dw.FlushAsync();
            await dw.StoreAsync();
            await socket.OutputStream.FlushAsync();
        }
    }

The receiver will read each information step by step too:

 private async Task<string> ReceiveFileFomPeer(StreamSocket socket, 
    StorageFolder folder, string outputFilename = null)
{
    StorageFile file; 
    using (var rw = new DataReader(socket.InputStream))
    {
        // 1. Read the filename length
        await rw.LoadAsync(sizeof(Int32));
        var filenameLength = (uint)rw.ReadInt32();
        // 2. Read the filename
        await rw.LoadAsync(filenameLength);
        var originalFilename = rw.ReadString(filenameLength);
        if (outputFilename == null)
        {
            outputFilename = originalFilename;
        }
        //3. Read the file length
        await rw.LoadAsync(sizeof(UInt64));
        var fileLength = rw.ReadUInt64();

        // 4. Reading file
        using (var memStream = await DownloadFile(rw, fileLength))
        {
            file = await folder.CreateFileAsync(outputFilename, 
                CreationCollisionOption.ReplaceExisting);
            using (var fileStream1 = await file.OpenAsync(FileAccessMode.ReadWrite))
            {
                await RandomAccessStream.CopyAndCloseAsync(
                    memStream.GetInputStreamAt(0), fileStream1.GetOutputStreamAt(0));
            }
            Verbose("Et voila :)");
            rw.DetachStream();
        }
    }
    return file.Path;
}

More possibilities with the Proximity API

Other scenarios are available, like listening to any proximity event occuring (with PeerFinder.CreateWatcher) or providing an early access to peer’s data during the accept phase. It can be useful to help decide how to handle the peer that is trying to connect or if it should accept the connection or not.