0

I have to listen to several udp sockets and receive multicast datagrams. After receiving from some socket, data has to be sent to the TransformBlock. I don't want to copy received data for each received packet, so data buffer is sent to the TransformBlock by reference. And that's why each particular socket has to wait for the TransformBlock to finish processing before it can begin reading to it's buffer again. I used code from this article for async operations with sockets. Also I need an ability to add or delete sockets which to listen during the program execution. My multicast listener class:

using ChannelWithDecoder = Tuple<FastChannel, TransformBlock<SocketAsyncEventArgs, List<Message>>>;

class MulticastReceiveManager
{
    const int MaxPacketSize = 1400;

    readonly ConcurrentDictionary<int, SocketReadData> socketDataByPort = new ConcurrentDictionary<int, SocketReadData>();

    public MulticastReceiveManager(IEnumerable<ChannelWithDecoder> channelsWithDecoders)
    {
        foreach (ChannelWithDecoder tuple in channelsWithDecoders) AddChannel(tuple.Item1, tuple.Item2);
    }

    static Socket CreateSocket(FastChannel channel)
    {
        var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

        var end = new IPEndPoint(IPAddress.Any, channel.Port);
        socket.Bind(end);

        //подписываемся на source specific multicast
        var membershipAddresses = new byte[12]; // 3 IPs * 4 bytes (IPv4)
        IPAddress mcastGroup = IPAddress.Parse(channel.IP);
        IPAddress mcastSource = IPAddress.Parse(channel.SourceIP);

        Buffer.BlockCopy(mcastGroup.GetAddressBytes(), 0, membershipAddresses, 0, 4);
        Buffer.BlockCopy(mcastSource.GetAddressBytes(), 0, membershipAddresses, 4, 4);
        Buffer.BlockCopy(IPAddress.Any.GetAddressBytes(), 0, membershipAddresses, 8, 4);

        socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddSourceMembership, membershipAddresses);

        return socket;
    }

    public void AddChannel(FastChannel channel, TransformBlock<SocketAsyncEventArgs, List<Message>> decoderTransformBlock)
    {
        var args = new SocketAsyncEventArgs();
        args.SetBuffer(new byte[MaxPacketSize], 0, MaxPacketSize);
        var socketAwaitable = new ReceiveSocketAwaitable(args);

        Socket socket = CreateSocket(channel);
        var socketReadData = new SocketReadData(socket, socketAwaitable, decoderTransformBlock);

        if (!socketDataByPort.TryAdd(channel.Port, socketReadData))
            throw new ArgumentException("Channel with port number = " + channel.Port + " already exists in dictionary. IP = " + channel.IP);
    }

    public void DeleteChannel(FastChannel channel)
    {
        SocketReadData socketReadData;

        if (!socketDataByPort.TryRemove(channel.Port, out socketReadData))
            throw new ArgumentException("Channel with port number = " + channel.Port + " could not be removed from dictionary. IP = " + channel.IP);
    }

    public async Task StartListen(CancellationToken token)
    {
        SocketReadData socketReadData = socketDataByPort.Values.First();

        while (!token.IsCancellationRequested)
        {
            await socketReadData.Socket.ReceiveAsync(socketReadData.Awaitable);

            SocketAsyncEventArgs args = socketReadData.Awaitable.EventArgs;

            if (args.BytesTransferred > 0) await socketReadData.DecoderTransformBlock.SendAsync(args, token);
        }
    }
}

Socket data:

class SocketReadData
{
    public Socket Socket { get; }
    public ReceiveSocketAwaitable Awaitable { get; }
    public TransformBlock<SocketAsyncEventArgs, List<Message>> DecoderTransformBlock { get; }

    public SocketReadData(Socket socket, ReceiveSocketAwaitable awaitable, TransformBlock<SocketAsyncEventArgs, List<Message>> decoderTransformBlock)
    {
        Socket = socket;
        Awaitable = awaitable;
        DecoderTransformBlock = decoderTransformBlock;
    }
}

And just in case, the code from the article for socket awaitable:

class ReceiveSocketAwaitable : INotifyCompletion
{
    static readonly Action Sentinel = () => { };

    Action mContinuation;

    public bool WasCompleted { get; set; }
    public SocketAsyncEventArgs EventArgs { get; }

    public bool IsCompleted => WasCompleted;

    public ReceiveSocketAwaitable(SocketAsyncEventArgs eventArgs)
    {
        Contract.Requires<ArgumentNullException>(eventArgs != null, "eventArgs can't be null");

        EventArgs = eventArgs;

        eventArgs.Completed += delegate
                               {
                                   Action prev = mContinuation ?? Interlocked.CompareExchange(ref mContinuation, Sentinel, null);
                                   prev?.Invoke();
                               };
    }

    public void OnCompleted(Action continuation)
    {
        if (mContinuation == Sentinel || Interlocked.CompareExchange(ref mContinuation, continuation, null) == Sentinel)
        {
            Task.Run(continuation);
        }
    }

    public void Reset()
    {
        WasCompleted = false;
        mContinuation = null;
    }

    public ReceiveSocketAwaitable GetAwaiter()
    {
        return this;
    }

    public void GetResult()
    {
        if (EventArgs.SocketError != SocketError.Success) throw new SocketException((int)EventArgs.SocketError);

        //return EventArgs.BytesTransferred;
    }
}

And extensions:

static class SocketExtensions
{
    public static ReceiveSocketAwaitable ReceiveAsync(this Socket socket, ReceiveSocketAwaitable awaitable)
    {
        awaitable.Reset();
        if (!socket.ReceiveAsync(awaitable.EventArgs)) awaitable.WasCompleted = true;
        return awaitable;
    }

    public static ReceiveSocketAwaitable SendAsync(this Socket socket, ReceiveSocketAwaitable awaitable)
    {
        awaitable.Reset();
        if (!socket.SendAsync(awaitable.EventArgs)) awaitable.WasCompleted = true;
        return awaitable;
    }
}

As you can see, this code achieves my goal for a single socket. In a loop it awaits for data and then await for transformblock to accept the packet. And only after that it can read data from a socket to the buffer again.

So the question is: how can i achieve this behavior for many sockets using async/await pattern, without creating threads and copying data for each packet?

shda
  • 729
  • 7
  • 19
  • Can you just keep the sockets in a list and just traverse the list, giving each socket a turn before going back to the beginning of the list? – Warren Dew May 16 '16 at 17:22
  • I want each particular socket to wait for it's transformblock to accept data before reading again. All other sockets don't have to wait for other socket's transformblocks. If I use the list, I'll have to wait for each socket's turn. Right? – shda May 16 '16 at 17:28
  • I think what you would do in this case would be that, when a socket's transformblock is not ready to accept data, skip ahead to the next socket. That might be the best you can do with a single thread. – Warren Dew May 16 '16 at 17:32
  • But I have to poll blocks until they ready? May you show some snippet please? – shda May 16 '16 at 17:37
  • You would have to have a polling loop. You could pause occasionally to avoid the thread eating all available processor time. – Warren Dew May 16 '16 at 17:47
  • I can't make pauses. I have to react as fast as possible for each packet. – shda May 16 '16 at 18:22

0 Answers0