Using PeerFinder from Console: Wi-Fi Direct data transfer in C#

Tuesday, September 13, 2016 0 Comments

There are situations where you don't have a wifi or LAN available but you would like to exchange some files. Then you find yourself copying to a usb stick and giving it to your colleague. You are probably sitting together on the lobby of a hotel, a train, a plane, it doesn't matter, but connecting to the internet or a local network is not an option.

Wi-Fi Direct is a standard to connect with each other without a wireless access point. So, if your two laptops are close enough, you should be able to take advantage of your wifi antennas to transfer data faster than using the usb.

.NET has a bunch of classes to help implementing this. Unfortunately, (IMO) they are all designed to be used from UWP or Windows Store apps, and it is sort of a nightmare to use them from a simple Console application (or a Windows service, which could greatly benefit from this functionality). I'm going to explain how to use the old PeerFinder (available since Windows 8.1) to transfer data using Wi-Fi Direct from a simple C# Console program. Full source code available on GitHub.

Using WinRT from Console

As I pointed in a previous blogpost you need some special initialization in order to use the WinRT APIs from Console. In my case I'm using Visual Studio 2013, so you need to edit the .csproj manually to enter something like 8.1. Then it is just a matter of adding the right windows.winmd (C:\Program Files (x86)\Windows Kits\8.1\References\CommonConfiguration\Neutral\Windows.winmd in my case) and Windows Runtime (C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5.1\System.Runtime.WindowsRuntime.dll). It is a pain to add these assemblies, and expect all sort of weird build errors if you don't add them correctly.

PeerFinder roles: host and client

You will be starting two instances of the same application, one on each laptop. One will be playing the "host" role and announcing it to be reachable, the other will scan to find peers, playing the "client" role. The logic is as simple as this. The devil is not as much in the details but in the initialization of the code to be used from Console.

Host role

Running the host role is as simple as:

    PeerFinder.Role = PeerRole.Host;

    PeerFinder.DisplayName = "PeerFinderConsoleApp";
    PeerFinder.AllowWiFiDirect = true;

    PeerFinder.ConnectionRequested += ConnectionRequested;

    PeerFinder.Start();

Where "ConnectionRequested" is something as:

        void ConnectionRequested(
            object sender,
            Windows.Networking.Proximity.ConnectionRequestedEventArgs e)
        {
            Console.WriteLine(
                "Connection requested by " + e.PeerInformation.DisplayName + ". " +
                "Will be accepted automatically.");
            Receiver(e.PeerInformation);
        }

And "Receiver" is the one actually receiving data (but I will cover that later).

So, in short, just 5 lines of code to launch a Host role and wait for peers to connect.

The "DisplayName" is the name how the application will be listed on the network.

Client role

Finding peers on the network is also very simple:

        PeerFinder.Role = PeerRole.Client;
        PeerFinder.AllowWiFiDirect = true;

        PeerFinder.Start();


        var peerInfoCollection = PeerFinder.FindAllPeersAsync().AsTask().Result;

        if (peerInfoCollection.Count == 0)
        {
            Console.WriteLine("No peers found");
            return;
        }

        foreach (var info in peerInfoCollection)
        {
            Console.WriteLine("Peer found: {0}", info.DisplayName);

            Windows.Networking.Sockets.StreamSocket socket =
                PeerFinder.ConnectAsync(info).AsTask().Result;

            Sender(socket);
        }

Please note I'm doing weird things like FindAllPeersAync().AsTask().Result to avoid using await, since in my app I'm not initializing the code in such a way that asyncs are allowed, but that's all.

Again, super simple.

The missing magic

All this is sort of explained reading the MSDN docu for PeerFinder. Sort of, because they do not show a full example, but just fragments. And also, because the code fragments are NOT meant to be used from Console.

If you want this thing to run instead of getting weird runtime exceptions on startup, you need to invoke this first:

  [DllImport("shell32.dll")]
  static extern int SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID);

As explained here.

Simple, but it took me a few hours to put everything together (references, the asyncs from a plain console app, plus this missing magic thing).

Sending and receiving bytes

In the previous code fragments I introduced 2 methods: Sender which receives a Windows.Networking.Sockets.StreamSocket, and Receiver, with a PeerInformation as argument.

Basically once the connection is established, what I do is to send data from the client role to the host role to test the actual data transfer.

Here goes a Gist with the Sender code:

To highlight: I'm converting the Windows Runtime Streams into regular System.IO streams (yes, I'm oldfashioned) with socket.OutputStream.AsStreamForWrite(). Yes, it saved me a lot of time because first I tried to use the "new" streams and they are a pain (IMHO).

All I do is to send 64MB of byte[] over the socket, in a single write, and I use BinaryWriter for that. After that, I wait on the receiver for an answer (just a bool). Then a print the data transfer speed in MB/s. The receiver is not much harder:

Please not that I do a PeerFinder.ConnectAsync(peerInfo).AsTask().Result to get a socket from the PeerInformation. This ConnectAsync actually happens both on "client" and "host". In the client we do it after listing the available peers, and in the host I do it after receiving a connection request. (And I realize I should make the code more symmetric now :P).

My results – 10MB/s on Wi-Fi Direct mode

I tried with 2 laptops located like 5cm from each other. I got the following results:

PeerFinder.exe client
Peer found: PeerFinderConsoleApp
Going to send 67108864 bytes
Sent 64 MB in 6 secs = 10.67 MB/s
10.67 MB/s which is about 86mbps.

Note: we got 16MB/s running on other laptop pair, so quite fast indeed.

I ran the tests after disconnecting the 2 laptops from the wifi, to simulate a real Wi-Fi Direct scenario.

Interestingly, I'm using a very poor wifi wireless access point at home, so a simple program sending bytes over regular TCP gets between 1.5 – 2.5 MB/s, maximum 3MB/s sometimes (yes, my wifi is crap, at the office we have a nice one reaching 300mbps). But it helps me probing that Wi-Fi Direct is connecting the two laptops directly, avoiding the slow wireless router, and getting much better data transfer results, which is very interesting!

Why I find Wi-Fi Direct so interesting?

Well, when I started writing this I was obviously thinking on Plastic SCM. Suppose you just want to push some changes to another colleague, why should you upload them to a server half a world away? I mean, if you're both at the office, and you have fast internet access, then no problem, but what about conferences, going to customer sites (with high security levels), hackathons and the like? Just doing something like "announce on Wi-Fi Direct", then share a PIN and have your peer connected would be awesome, I think (especially considering you can benefit from higher bandwidth than the actual wifi, if available).

Obviously I'm not describing an everyday scenario, it is more something you would use occasionally.

On the other hand, I've been writing p2pcopy (https://github.com/psantosl/p2pcopy), a small command line app to transfer file doing NAT hole punching. Adding Wi-Fi Direct support sounded like a perfect idea :P

Finally, I'm sharing this because it took me a while to figure out how to do it from Console, and the same applies to traditional desktop apps. I think there are many classic desktop applications where the "proximity" APIs could greatly help, but IMO Microsoft is making it very difficult for developers to use this (and others) except if you are targeting UWP.

Next steps

About 1 year ago we started playing with the new Windows 10 Wi-Fi Direct APIs after watching a video on Channel 9. I'm talking about the Windows.Devices.WiFiDirect API, not the older Windows.Networking.Proximity that I use for PeerFinder and that has been available since W8.1.

Well, the thing is that we were unable to make the Devices.WiFiDirect thing work from Console, thinking on a server scenario with both GUI and command line clients (Plastic). It seems they have just released a newer version a few months ago and now the "pairing" of devices can be customized so you don't have to depend on some UWP UI showing up. I would like to give this API a second try and figure out if it is now usable from good-ol Console, although I'm not sure if the data transfer is going to be any faster, since I understand the underlying components will be the same.

Source code

The entire source code of my example is on GitHub.

0 comentarios: