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?