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.
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:
Updating the manifest
For an application referencing the lib, you will have to check the proximity capability:
Testing with the sample application
The sample demo application must be started on 2 Windows 8.1 devices supporting Wifi-direct.
Sender | Receiver |
The application has 2 buttons. The sender should click the enabled button first:
Sender | Receiver |
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
Click open : the receiver will then get the ConnectionRequested event.
Receiver
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 .
Now the file will be transfered from the sender to the receiver.
Sender | Receiver |
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
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.