UDP multicast group on Windows Phone 8

C#SocketsUdpWindows Phone-8Datagram

C# Problem Overview


OK this is one I've been trying to figure out for a few days now. We have an application on Windows Phone 7 where phones join a multicast group and then send and receive messages to the group to talk to each other. Note - this is phone to phone communication.

Now I'm trying to port this application to Windows Phone 8 - using the 'Convert to Phone 8' feature in Visual Studio 2012 - so far so good. Until I try to test the phone to phone communication. The handsets seem to join the group fine, and they send the datagrams OK. They even receive the messages that they send to the group - however, no handset ever receives a message from another handset.

Here is the sample code behind to my page:

// Constructor
public MainPage()
{
    InitializeComponent();
}

// The address of the multicast group to join.
// Must be in the range from 224.0.0.0 to 239.255.255.255
private const string GROUP_ADDRESS = "224.0.1.1";

// The port over which to communicate to the multicast group
private const int GROUP_PORT = 55562;

// A client receiver for multicast traffic from any source
UdpAnySourceMulticastClient _client = null;

// Buffer for incoming data
private byte[] _receiveBuffer;

// Maximum size of a message in this communication
private const int MAX_MESSAGE_SIZE = 512;

private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
    _client = new UdpAnySourceMulticastClient(IPAddress.Parse(GROUP_ADDRESS), GROUP_PORT);
    _receiveBuffer = new byte[MAX_MESSAGE_SIZE];

    _client.BeginJoinGroup(
        result =>
        {
            _client.EndJoinGroup(result);
            _client.MulticastLoopback = true;
            Receive();
        }, null);
}

private void SendRequest(string s)
{
    if (string.IsNullOrWhiteSpace(s)) return;

    byte[] requestData = Encoding.UTF8.GetBytes(s);

    _client.BeginSendToGroup(requestData, 0, requestData.Length,
        result =>
        {
            _client.EndSendToGroup(result);
            Receive();
        }, null);
}

private void Receive()
{
    Array.Clear(_receiveBuffer, 0, _receiveBuffer.Length);
    _client.BeginReceiveFromGroup(_receiveBuffer, 0, _receiveBuffer.Length,
        result =>
        {
            IPEndPoint source;

            _client.EndReceiveFromGroup(result, out source);

            string dataReceived = Encoding.UTF8.GetString(_receiveBuffer, 0, _receiveBuffer.Length);

            string message = String.Format("[{0}]: {1}", source.Address.ToString(), dataReceived);
            Log(message, false);

            Receive();
        }, null);
}

private void Log(string message, bool isOutgoing)
{
    if (string.IsNullOrWhiteSpace(message.Trim('\0')))
    {
        return;
    }

    // Always make sure to do this on the UI thread.
    Deployment.Current.Dispatcher.BeginInvoke(
    () =>
    {
        string direction = (isOutgoing) ? ">> " : "<< ";
        string timestamp = DateTime.Now.ToString("HH:mm:ss");
        message = timestamp + direction + message;
        lbLog.Items.Add(message);

        // Make sure that the item we added is visible to the user.
        lbLog.ScrollIntoView(message);
    });

}

private void btnSend_Click(object sender, RoutedEventArgs e)
{
    // Don't send empty messages.
    if (!String.IsNullOrWhiteSpace(txtInput.Text))
    {
        //Send(txtInput.Text);
        SendRequest(txtInput.Text);
    }
}

private void btnStart_Click(object sender, RoutedEventArgs e)
{
    SendRequest("start now");
}

In order to simply test out the UDP stack, I downloaded the sample from MSDN found here and I tested this on a pair of Windows Phone 7 devices and it works as expected. Then I converted to Windows Phone 8 and deployed to my handsets, again the devices seem to initiate their connection, and the user can enter their name. However, again the devices can't see or communicate with other devices.

Finally I implemented a simple communication test using the new DatagramSocket implementation, and again I see successful initiation, but no inter-device communication.

This is the same code behind page using the datagram socket implementation:

// Constructor
public MainPage()
{
    InitializeComponent();
}

// The address of the multicast group to join.
// Must be in the range from 224.0.0.0 to 239.255.255.255
private const string GROUP_ADDRESS = "224.0.1.1";

// The port over which to communicate to the multicast group
private const int GROUP_PORT = 55562;

private DatagramSocket socket = null;

private void Log(string message, bool isOutgoing)
{
    if (string.IsNullOrWhiteSpace(message.Trim('\0')))
        return;

    // Always make sure to do this on the UI thread.
    Deployment.Current.Dispatcher.BeginInvoke(
    () =>
    {
        string direction = (isOutgoing) ? ">> " : "<< ";
        string timestamp = DateTime.Now.ToString("HH:mm:ss");
        message = timestamp + direction + message;
        lbLog.Items.Add(message);

        // Make sure that the item we added is visible to the user.
        lbLog.ScrollIntoView(message);
    });
}

private void btnSend_Click(object sender, RoutedEventArgs e)
{
    // Don't send empty messages.
    if (!String.IsNullOrWhiteSpace(txtInput.Text))
    {
        //Send(txtInput.Text);
        SendSocketRequest(txtInput.Text);
    }
}

private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
    socket = new DatagramSocket();
    socket.MessageReceived += socket_MessageReceived;

    try
    {
        // Connect to the server (in our case the listener we created in previous step).
        await socket.BindServiceNameAsync(GROUP_PORT.ToString());
        socket.JoinMulticastGroup(new Windows.Networking.HostName(GROUP_ADDRESS));
        System.Diagnostics.Debug.WriteLine(socket.ToString());
    }
    catch (Exception exception)
    {
        throw;
    }
}

private async void SendSocketRequest(string message)
{
    // Create a DataWriter if we did not create one yet. Otherwise use one that is already cached.
    //DataWriter writer;
    var stream = await socket.GetOutputStreamAsync(new Windows.Networking.HostName(GROUP_ADDRESS), GROUP_PORT.ToString());
    //writer = new DataWriter(socket.OutputStream);
    DataWriter writer = new DataWriter(stream);

    // Write first the length of the string as UINT32 value followed up by the string. Writing data to the writer will just store data in memory.
   // stream.WriteAsync(
    writer.WriteString(message);

    // Write the locally buffered data to the network.
    try
    {
        await writer.StoreAsync();
        Log(message, true);
        System.Diagnostics.Debug.WriteLine(socket.ToString());
    }
    catch (Exception exception)
    {
        throw;
    }
    finally
    {
        writer.Dispose();
    }
}

void socket_MessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
{
    try
    {
        uint stringLength = args.GetDataReader().UnconsumedBufferLength;
        string msg = args.GetDataReader().ReadString(stringLength);

        Log(msg, false);
    }
    catch (Exception exception)
    {
        throw;
    }
}

Last night I took the handsets home to test them on my home wireless network, low and behold I get successful device communication.

So to recap - my legacy Windows Phone 7 code runs fine on my work network. The port to Windows Phone 8 (no actual code change) does not send inter-device communication. This code does work on my home network. The code runs with the debugger attached and there are no signs of errors or exceptions anywhere during execution.

The handsets I'm using are:

Windows Phone 7 - Nokia Lumia 900 (* 2), Nokia Lumia 800 (* 3) Windows Phone 8 - Nokia Lumia 920 (* 1), Nokia Limia 820 (* 2)

These are all running the latest OS, and are in developer mode. Development environment is Windows 8 Enterprise running Visual Studio 2012 Professional

I cant tell you much about the work wireless network - other than the Phone 7 devices have no trouble.

As for the home wireless network i used, that's just a basic BT Broadband router with none of the 'out the box' settings altered.

Clearly there is an issue with the way that the two network are configured, but there is also very clearly an issue with the way Windows Phone 8 implements UDP messages.

Any input would be appreciated as this is driving me mad right now.

C# Solutions


Solution 1 - C#

I notice you use the loopback. I think it means that when you send a message from your client you will receive the message you sent also. This means your receive handler will fire. It has the effect of clearing the receive buffer in a seemingly unthreadsafe fashion. Try putting some try catch in your receive method and see if anything untoward is happening but you might instead just not use the shared receive buffer in any case.

Solution 2 - C#

Have you ever tried joining another multicast group? Because 224.0.1.1 seems to be in use as of IANA assignments. You find all of the here.

Maybe on Windows Phone 8 some service is tighter bound to listen to messages coming in (e.g. a network service that is running in kernel mode) and they are never forwarded to you.

Solution 3 - C#

UDP multicast works pretty strangely under windows phone 7 from my experience so you should checkout the same for windows phone 8.

Here is my experience:

  1. check what is officially supported, for instance under Windows Phone OS 7.1 (last OS I tried before switching), TCP unicast, UDP unicast, and UDP multicast clients are supported.
  2. some versions of Windows phone permit to receive a UDP session only if the client first opened it and the session is received within no more than 10 seconds, this seems like some sort of security thing on Windows Phone.
  3. try with different addresses: multicast addresses in the range from 224.0.0.0 to 224.0.0.255 inclusive are “well-known” reserved multicast addresses.
  4. Check both in the virtual machine and in a real phone device, the behaviour may differ.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAdam StewartView Question on Stackoverflow
Solution 1 - C#cineam mispeltView Answer on Stackoverflow
Solution 2 - C#HaMsterView Answer on Stackoverflow
Solution 3 - C#dendiniView Answer on Stackoverflow