0

I have list of ipaddress. I am using udp protocol to send request to these addressses asynchronously to an snmp agent. The available snmp agent on these address reply. When I call BeginSendTo with these addresses, devices are replying in random order. When ReceiveBeginReceiveFrom is called and stateobject object is constructed. The RemoteIPEndPoint doesn't belong to the machine that replied instead it belongs to other machine. Is my approach of calling BeginSendTo is correct? If Yes, y stateobject instance doesn't give me correct remoteendpoint that replied? I am posting my code. Please correct if case something is missing.

public class Test
{
    Socket udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
    class StateObject
    {
        public Socket Socket { get; set; }
        public byte[] DataBuff { get; set; }
        public IPEndPoint RemoteEndPoint { get; set; }
        //validation are omited
        public static StateObject Create(Socket s,byte[] dataBuf,IPEndPoint endpoint)
        {
            return new StateObject() { Socket = s, DataBuff = dataBuf, RemoteEndPoint = endpoint };
        }

        public static StateObject Transform(object objectstate)
        {
            return objectstate as StateObject;
        }
    }
    public void Fx()
    {
        //list of ip address from 190.188.191.1 - 190.188.191.100
        var address = new[] {
            IPAddress.Parse("190.188.191.1"),
            IPAddress.Parse("190.188.191.100")
        };

        byte[] dataGram = new byte[1024];
        foreach (IPAddress item in address)
        {
            udpSocket.BeginSendTo(dataGram,
                        0,
                        dataGram.Length,
                        SocketFlags.None,
                        new IPEndPoint(item, 161),
                        SendComplete,
                        StateObject.Create(udpSocket, null, new IPEndPoint(item, 161))
                        );
        }
    }

    private void SendComplete(IAsyncResult ar)
    {
        StateObject obj = StateObject.Transform(ar.AsyncState);
        obj.Socket.EndSendTo(ar);//ignore the no of bytes send for now
        byte[] receivedBytes = new byte[1024 * 4];//assume 4kb is enough for response
        var ep = obj.RemoteEndPoint as EndPoint;
        obj.Socket.BeginReceiveFrom(receivedBytes,
            0,
            receivedBytes.Length,
            SocketFlags.None,
            ref ep,
            ReceivedData,
            StateObject.Create(obj.Socket, receivedBytes, obj.RemoteEndPoint)
            );
    }

    private void ReceivedData(IAsyncResult ar)
    {
        StateObject obj = StateObject.Transform(ar.AsyncState);
        var ep = obj.RemoteEndPoint as EndPoint;

        //response received from ip 190.188.191.2 but in stateobject.remoteendpoint will give 190.188.191.1

        var bytesReceived  = obj.Socket.EndReceiveFrom(ar,ref ep);
        byte[] data = new byte[bytesReceived];
        Array.Copy(obj.DataBuff,data, bytesReceived);
    }
}
neelesh bodgal
  • 632
  • 5
  • 14
  • You tagged [UdpClient](https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.udpclient?view=netframework-4.8), yet you are not using it. Instead you are using the underlying Socket ... You should listen on a separate thread and figure out the sender from the received packet (also order and completeness of message). That's how UDP works. It's connectionless, unreliable and does not keep order. You cannot even assume a message will be complete in one received packet... or that you receive an answer at all. – Fildor Apr 03 '20 at 11:02
  • I also strongly suggest you have a look into the Task async API of UdpClient : [ReceiveAsync](https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.udpclient.receiveasync?view=netframework-4.8#System_Net_Sockets_UdpClient_ReceiveAsync) and [SendAsync](https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.udpclient.sendasync?view=netframework-4.8#System_Net_Sockets_UdpClient_SendAsync_System_Byte___System_Int32_System_Net_IPEndPoint_) – Fildor Apr 03 '20 at 11:06
  • For a range of ip addresses how to send and receive. How to map send and receive? – neelesh bodgal Apr 03 '20 at 11:18
  • You cannot "map" (the way you think). That's up to you to implement. Udp will only tell you the sender ip/port. It is not designed to tell you which (and for that matter if at all) a datagram is an answer to a corresponding request. You need to implement an application protocol on top of UDP to have for example Request/Response capabilities. _But_: don't panic. You do not need to reinvent the wheel. You could use some MessageQueue framework, that supports Request/Response over UDP. I know that ZeroMQ is capable of this, but a wide variety of others should, too. – Fildor Apr 03 '20 at 11:27
  • Due to mapping issue, i used socket, as i did. Class Stateobject will allow us to map remote end point that responded. It is helping to map, but sometimes stateobject instacne remote endpoint property is not same as device ip that replied. – neelesh bodgal Apr 03 '20 at 11:32
  • As I said, it cannot. It could if you only ever sent one Request and waited for the Response, then send next Request. But not if you fan out requests. Think of a mailbox. You sent out 10 letters to different recipients. Now postman comes and drops 7 letters in your mailbox. How would you "map" them? You would need to inspect the envelope and read who the sender was. – Fildor Apr 03 '20 at 11:33
  • _"but sometimes stateobject instacne remote endpoint property is not same as device ip that replied"_ if it _is_ the same, then it is _by chance_. – Fildor Apr 03 '20 at 11:37
  • I understand incase of fan out, mapping is not possible. Can we use seperate socket instance for each ip address. Will this have performance issue. Also does a machine a limit of no of socket instance to be created? – neelesh bodgal Apr 03 '20 at 11:38
  • 1
    Well, first of all, you have a limit on ports, to begin with. And it would be overkill. Also, you need to take care of _lost_ packets. That is: In production, you will sooner or later find that some request do not get responded to at all. That can have several reasons: Remote did not receive request, remote did not send response or remote sent response but it got lost on the way. You need to keep track of that, so you can re-issue requests (if necessary) or stop waiting for that response. Otherwise you will find your software appears to have a "memory leak". – Fildor Apr 03 '20 at 11:42
  • 1
    Thanks for inputs. Let me explore other possibilities and try to solve this problem. – neelesh bodgal Apr 03 '20 at 11:46

1 Answers1

0

Finally i figured it out to solve this issue of fanning out multiple udp requests. New field introduced in State Type called "ID". When generating ip address a unique identifier is generated. This identifier is assigned to State Type instance and cached. Also this ID is sent in request inorder to identify the response from a specific IP address and retrieve its state value from its cache.

    Socket udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 
      ProtocolType.Udp);

    Dictionary<int, StateObject> _Cache = new Dictionary<int, StateObject>();

    class StateObject
    {
        public int ID { get; set; }//uniquely identify the response

        public Socket Socket { get; set; }
        public byte[] DataBuff { get; set; }
        public IPEndPoint RemoteEndPoint { get; set; }
        //validation are omited
        public static StateObject Create(Socket s, byte[] dataBuf, IPEndPoint endpoint)
        {
            return new StateObject() { Socket = s, DataBuff = dataBuf, RemoteEndPoint = endpoint };
        }

        public static StateObject Transform(object objectstate)
        {
            return objectstate as StateObject;
        }
    }

    //This function is used to generate range of address and corresponding identifiers
    IEnumerable<KeyValuePair<IPAddress, int>> GenerateMaps(IPAddress start, IPAddress end)
    {
        int count = -1;
        yield return new KeyValuePair<IPAddress, int>(start, ++count);

        //other address in the range

        yield return new KeyValuePair<IPAddress, int>(end, ++count);
    }

Also instead of receiving from particular host BeginReceiveFrom, instead i replaced with BeginReceive function that is capable of receiving and host. Mapping of identifier helped solve the issue in a cache.

neelesh bodgal
  • 632
  • 5
  • 14