Chris Voce Ответов: 1

Получение неправильной дейтаграммы с помощью udpclient.endreceive()


Я написал программу, которая принимает многоадресный udp. Это в основном работает, но когда есть несколько пакетов, прибывающих в быстрой последовательности, я получаю некоторые "дубликаты" и пропускаю некоторые полностью.

Я запустил Wireshark, и машина получает каждый пакет только один раз, и получает их все. Так например если машина получает пакеты 1 2 3 4 5 6 7 8 9 10 в быстрой последовательности, а затем по данным, которые я извлекаю в своем коде, я получаю пакеты 3 4 5 5 5 5 9 10 10 10. Таким образом, я получаю правильное количество вызовов моей функции обратного вызова, но либо я делаю что-то глупое, либо буфер дает мне неправильную дейтаграмму.

Это C#, VS 2017, DotNet 4.6.1.

Я написал упрощенное тестовое приложение, которое имеет те же результаты:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TestLWMulticastGPIO
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        delegate void WriteToListboxCallback(string text);
        private void WriteToListbox(string text)
        {
            if (this.listBox1.InvokeRequired)
            {
                WriteToListboxCallback d = new WriteToListboxCallback(WriteToListbox);
                this.Invoke(d, new object[] { text });
            }
            else
                this.listBox1.Items.Add(text);
        }


        public static readonly System.Net.IPAddress DefaultMulticastGroup = System.Net.IPAddress.Parse(***MULTICASTADDRESS***);
        private System.Net.Sockets.UdpClient _MulticastClient;

        private void Form1_Load(object sender, EventArgs e)
        {
            initGPIOClient(ref _MulticastClient, ***MULTICASTPORT***);
        }

        private void initGPIOClient(ref System.Net.Sockets.UdpClient clnt, int port)
        {
            IPEndPoint mCastLocalIP = new IPEndPoint(IPAddress.Any, port);
            clnt = new System.Net.Sockets.UdpClient();

            clnt.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            clnt.Client.Bind(mCastLocalIP);

            // join multicast group on all available network interfaces
            NetworkInterface[] networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();

            foreach (NetworkInterface networkInterface in networkInterfaces)
            {
                if ((!networkInterface.Supports(NetworkInterfaceComponent.IPv4)) || (networkInterface.OperationalStatus != OperationalStatus.Up))
                    continue;

                IPInterfaceProperties adapterProperties = networkInterface.GetIPProperties();
                UnicastIPAddressInformationCollection unicastIPAddresses = adapterProperties.UnicastAddresses;
                IPAddress ipAddress = null;

                foreach (UnicastIPAddressInformation unicastIPAddress in unicastIPAddresses)
                {
                    if (unicastIPAddress.Address.AddressFamily != AddressFamily.InterNetwork)
                        continue;

                    ipAddress = unicastIPAddress.Address;
                    break;
                }

                if (ipAddress == null)
                {
                    continue;
                }

                clnt.JoinMulticastGroup(DefaultMulticastGroup, ipAddress);
                clnt.BeginReceive(ReceiveMulticastCallback, clnt);
            }
        }

        bool ReceiveBusy = false;
        private void ReceiveMulticastCallback(IAsyncResult ar)
        {
            WriteToListbox("Data Received");

            while (ReceiveBusy)
                Thread.Sleep(1);
            ReceiveBusy = true;

            WriteToListbox("  Data Being Processed...");

            UdpClient udp = (UdpClient)ar.AsyncState;
            if (udp.Client == null)
                return;

            IPEndPoint remoteEp = null;

            byte[] data = udp.EndReceive(ar, ref remoteEp);

            WriteToListbox("    Received Message " + BitConverter.ToString(data));

            udp.BeginReceive(new AsyncCallback(ReceiveMulticastCallback), udp);

            ReceiveBusy = false;
        }
    }
}


Может ли кто-нибудь увидеть, что я делаю неправильно? Это похоже на то, как я перезаписываю переменную со 2-м потоком, прежде чем я обработал первый. Это было то, что ReceiveBusy должен был остановить, но (а) не уверен, что это необходимо, так как переменные являются локальными, а не статическими, и (б) я не уверен, что он достигнет того, чего я хочу в любом случае?

Простите меня, я относительно новичок во всем этом.

Вся помощь оценена по достоинству

Крис

Что я уже пробовал:

Я гуглил в течение нескольких дней, и я переписал базовый код выше, чтобы убрать сложность моего основного кода. Ничто из того, что я делаю, кажется, не имеет никакого значения (или смысла).

Richard Deeming

Я был бы склонен переместить BeginReceive вызов вне цикла, просто на случай, если у вас есть несколько совпадающих интерфейсов.

Поскольку вы используете последнюю версию .NET, я также был бы склонен использовать ReceiveAsync[^] вместо BeginReceive, и использовать async способ обработки полученных данных. Напр.:

private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private Task _receiveTask;

private void Form1_Load(object sender, EventArgs e)
{
    initGPIOClient(***MULTICASTPORT***);
}

private void Form1_Closed(object sender, FormClosedEventArgs e)
{
    _cancellationTokenSource.Cancel();
    if (_receiveTask != null)
    {
        _receiveTask.Wait();
    }
}

private void initGPIOClient(int port)
{
    IPEndPoint mCastLocalIP = new IPEndPoint(IPAddress.Any, port);
    UdpClient clnt = UdpClient();

    ...

    _receiveTask = ReceiveMulticastData(clnt, _cancellationTokenSource.Token);
}

private async Task ReceiveMulticastData(UdpClient client, CancellationToken cancellationToken)
{
    while (!cancellationToken.IsCancellationRequested)
    {
        UdpReceiveResult result = await client.ReceiveAsync();
        this.listBox1.Items.Add("    Received Message " + BitConverter.ToString(result.Buffer));
    }
}

Chris Voce

Спасибо. Он находится в цикле, потому что там вполне может быть более 1 интерфейса, и я хочу слушать их все. Я попробую асинхронный метод, хотя, конечно, стоит попробовать! Спасибо.

Chris Voce

Привет. Сегодня я попробовал асинхронный метод и получил тот же результат. Для меня это просто не имеет смысла. Хотя Спасибо за предложение

1 Ответов

Рейтинг:
2

Gerry Schmitz

По моему опыту, когда данные начинают поступать, вы должны определить, когда у вас есть полное "сообщение".

Вы просто читаете дальше и предполагаете, что каждое прочитанное-это новое сообщение, хотя на самом деле это может быть только часть сообщения.

Где-то на линии находится "общая длина сообщения", которую вам нужно захватить, и "части", когда вы их читаете. Сложите их все вместе, и вы получите полное сообщение (прежде чем вы сможете "пометить" следующее сообщение).

(Или он может сказать, что осталось прочитать 0-N байтов).


Chris Voce

Спасибо за ответ. Однако это очень маленькие полезные нагрузки, и Wireshark показывает каждую полезную нагрузку, поступающую в одном пакете. Кроме того, мой обратный вызов вызывается только 10 раз (в приведенном выше примере). Так что я не думаю, что это так. Я получаю точно такой же массив, передаваемый мне каждый раз.