使用async / await将数据从许多UDP套接字传播到TPL Dataflow

时间:2016-05-16 15:19:31

标签: c# multithreading sockets async-await tpl-dataflow

我必须听几个udp套接字并接收多播数据报。从某个套接字接收后,必须将数据发送到TransformBlock。我不想为每个接收到的数据包复制接收数据,因此数据缓冲区通过引用发送到TransformBlock。这就是为什么每个特定的套接字必须等待TransformBlock完成处理才能再次开始读取它的缓冲区。 我使用this article中的代码与套接字进行异步操作。 此外,我需要能够添加或删除在程序执行期间要监听的套接字。 我的多播侦听器类:

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);
        }
    }
}

套接字数据:

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;
    }
}

以防万一,来自socket的文章代码等待:

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;
    }
}

扩展名:

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;
    }
}

如您所见,此代码实现了单个套接字的目标。在循环中,它等待数据,然后等待transformblock接受数据包。只有在那之后它才能再次从套接字读取数据到缓冲区。

所以问题是:如何使用async / await模式为许多套接字实现此行为,而无需为每个数据包创建线程和复制数据?

0 个答案:

没有答案