Skip to content

Commit b317d06

Browse files
UdpClient with span support (#53429)
Add API from #864
1 parent 1773f16 commit b317d06

File tree

6 files changed

+339
-70
lines changed

6 files changed

+339
-70
lines changed

src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,12 +760,19 @@ public void JoinMulticastGroup(System.Net.IPAddress multicastAddr, int timeToLiv
760760
public void JoinMulticastGroup(System.Net.IPAddress multicastAddr, System.Net.IPAddress localAddress) { }
761761
public byte[] Receive([System.Diagnostics.CodeAnalysis.NotNullAttribute] ref System.Net.IPEndPoint? remoteEP) { throw null; }
762762
public System.Threading.Tasks.Task<System.Net.Sockets.UdpReceiveResult> ReceiveAsync() { throw null; }
763+
public System.Threading.Tasks.ValueTask<System.Net.Sockets.UdpReceiveResult> ReceiveAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
763764
public int Send(byte[] dgram, int bytes) { throw null; }
765+
public int Send(System.ReadOnlySpan<byte> datagram) {throw null; }
764766
public int Send(byte[] dgram, int bytes, System.Net.IPEndPoint? endPoint) { throw null; }
767+
public int Send(System.ReadOnlySpan<byte> datagram, System.Net.IPEndPoint? endPoint) { throw null; }
765768
public int Send(byte[] dgram, int bytes, string? hostname, int port) { throw null; }
769+
public int Send(System.ReadOnlySpan<byte> datagram, string? hostname, int port) { throw null; }
766770
public System.Threading.Tasks.Task<int> SendAsync(byte[] datagram, int bytes) { throw null; }
771+
public System.Threading.Tasks.ValueTask<int> SendAsync(System.ReadOnlyMemory<byte> datagram, System.Threading.CancellationToken cancellationToken = default) { throw null; }
767772
public System.Threading.Tasks.Task<int> SendAsync(byte[] datagram, int bytes, System.Net.IPEndPoint? endPoint) { throw null; }
773+
public System.Threading.Tasks.ValueTask<int> SendAsync(System.ReadOnlyMemory<byte> datagram, System.Net.IPEndPoint? endPoint, System.Threading.CancellationToken cancellationToken = default) { throw null; }
768774
public System.Threading.Tasks.Task<int> SendAsync(byte[] datagram, int bytes, string? hostname, int port) { throw null; }
775+
public System.Threading.Tasks.ValueTask<int> SendAsync(System.ReadOnlyMemory<byte> datagram, string? hostname, int port, System.Threading.CancellationToken cancellationToken = default) { throw null; }
769776
}
770777
public partial struct UdpReceiveResult : System.IEquatable<System.Net.Sockets.UdpReceiveResult>
771778
{

src/libraries/System.Net.Sockets/src/System/Net/Sockets/UDPClient.cs

Lines changed: 163 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Diagnostics.CodeAnalysis;
55
using System.Threading.Tasks;
66
using System.Runtime.Versioning;
7+
using System.Threading;
78

89
namespace System.Net.Sockets
910
{
@@ -600,9 +601,46 @@ public void DropMulticastGroup(IPAddress multicastAddr, int ifindex)
600601
public Task<int> SendAsync(byte[] datagram, int bytes) =>
601602
SendAsync(datagram, bytes, null);
602603

604+
/// <summary>
605+
/// Sends a UDP datagram asynchronously to a remote host.
606+
/// </summary>
607+
/// <param name="datagram">
608+
/// An <see cref="ReadOnlyMemory{T}"/> of Type <see cref="byte"/> that specifies the UDP datagram that you intend to send.
609+
/// </param>
610+
/// <param name="cancellationToken">
611+
/// The token to monitor for cancellation requests. The default value is None.
612+
/// </param>
613+
/// <returns>A <see cref="ValueTask{T}"/> that represents the asynchronous send operation. The value of its Result property contains the number of bytes sent.</returns>
614+
/// <exception cref="ObjectDisposedException">The <see cref="UdpClient"/> is closed.</exception>
615+
/// <exception cref="SocketException">An error occurred when accessing the socket.</exception>
616+
public ValueTask<int> SendAsync(ReadOnlyMemory<byte> datagram, CancellationToken cancellationToken = default) =>
617+
SendAsync(datagram, null, cancellationToken);
618+
603619
public Task<int> SendAsync(byte[] datagram, int bytes, string? hostname, int port) =>
604620
SendAsync(datagram, bytes, GetEndpoint(hostname, port));
605621

622+
/// <summary>
623+
/// Sends a UDP datagram asynchronously to a remote host.
624+
/// </summary>
625+
/// <param name="datagram">
626+
/// An <see cref="ReadOnlyMemory{T}"/> of Type <see cref="byte"/> that specifies the UDP datagram that you intend to send.
627+
/// </param>
628+
/// <param name="hostname">
629+
/// The name of the remote host to which you intend to send the datagram.
630+
/// </param>
631+
/// <param name="port">
632+
/// The remote port number with which you intend to communicate.
633+
/// </param>
634+
/// <param name="cancellationToken">
635+
/// The token to monitor for cancellation requests. The default value is None.
636+
/// </param>
637+
/// <returns>A <see cref="ValueTask{T}"/> that represents the asynchronous send operation. The value of its Result property contains the number of bytes sent.</returns>
638+
/// <exception cref="InvalidOperationException">The <see cref="UdpClient"/> has already established a default remote host.</exception>
639+
/// <exception cref="ObjectDisposedException">The <see cref="UdpClient"/> is closed.</exception>
640+
/// <exception cref="SocketException">An error occurred when accessing the socket.</exception>
641+
public ValueTask<int> SendAsync(ReadOnlyMemory<byte> datagram, string? hostname, int port, CancellationToken cancellationToken = default) =>
642+
SendAsync(datagram, GetEndpoint(hostname, port), cancellationToken);
643+
606644
public Task<int> SendAsync(byte[] datagram, int bytes, IPEndPoint? endPoint)
607645
{
608646
ValidateDatagram(datagram, bytes, endPoint);
@@ -618,6 +656,39 @@ public Task<int> SendAsync(byte[] datagram, int bytes, IPEndPoint? endPoint)
618656
}
619657
}
620658

659+
/// <summary>
660+
/// Sends a UDP datagram asynchronously to a remote host.
661+
/// </summary>
662+
/// <param name="datagram">
663+
/// An <see cref="ReadOnlyMemory{T}"/> of Type <see cref="byte"/> that specifies the UDP datagram that you intend to send.
664+
/// </param>
665+
/// <param name="endPoint">
666+
/// An <see cref="IPEndPoint"/> that represents the host and port to which to send the datagram.
667+
/// </param>
668+
/// <param name="cancellationToken">
669+
/// The token to monitor for cancellation requests. The default value is None.
670+
/// </param>
671+
/// <returns>A <see cref="ValueTask{T}"/> that represents the asynchronous send operation. The value of its Result property contains the number of bytes sent.</returns>
672+
/// <exception cref="InvalidOperationException"><see cref="UdpClient"/> has already established a default remote host and <paramref name="endPoint"/> is not <see langword="null"/>.</exception>
673+
/// <exception cref="ObjectDisposedException">The <see cref="UdpClient"/> is closed.</exception>
674+
/// <exception cref="SocketException">An error occurred when accessing the socket.</exception>
675+
public ValueTask<int> SendAsync(ReadOnlyMemory<byte> datagram, IPEndPoint? endPoint, CancellationToken cancellationToken = default)
676+
{
677+
ThrowIfDisposed();
678+
679+
if (endPoint is null)
680+
{
681+
return _clientSocket.SendAsync(datagram, SocketFlags.None, cancellationToken);
682+
}
683+
if (_active)
684+
{
685+
// Do not allow sending packets to arbitrary host when connected.
686+
throw new InvalidOperationException(SR.net_udpconnected);
687+
}
688+
CheckForBroadcast(endPoint.Address);
689+
return _clientSocket.SendToAsync(datagram, SocketFlags.None, endPoint, cancellationToken);
690+
}
691+
621692
public Task<UdpReceiveResult> ReceiveAsync()
622693
{
623694
ThrowIfDisposed();
@@ -639,6 +710,36 @@ async Task<UdpReceiveResult> WaitAndWrap(Task<SocketReceiveFromResult> task)
639710
}
640711
}
641712

713+
/// <summary>
714+
/// Returns a UDP datagram asynchronously that was sent by a remote host.
715+
/// </summary>
716+
/// <param name="cancellationToken">
717+
/// The token to monitor for cancellation requests.
718+
/// </param>
719+
/// <returns>A <see cref="ValueTask{TResult}"/> representing the asynchronous operation.</returns>
720+
/// <exception cref="ObjectDisposedException">The underlying <see cref="Socket"/> has been closed.</exception>
721+
/// <exception cref="SocketException">An error occurred when accessing the socket.</exception>
722+
public ValueTask<UdpReceiveResult> ReceiveAsync(CancellationToken cancellationToken)
723+
{
724+
ThrowIfDisposed();
725+
726+
return WaitAndWrap(_clientSocket.ReceiveFromAsync(
727+
_buffer,
728+
SocketFlags.None,
729+
_family == AddressFamily.InterNetwork ? IPEndPointStatics.Any : IPEndPointStatics.IPv6Any, cancellationToken));
730+
731+
async ValueTask<UdpReceiveResult> WaitAndWrap(ValueTask<SocketReceiveFromResult> task)
732+
{
733+
SocketReceiveFromResult result = await task.ConfigureAwait(false);
734+
735+
byte[] buffer = result.ReceivedBytes < MaxUDPSize ?
736+
_buffer.AsSpan(0, result.ReceivedBytes).ToArray() :
737+
_buffer;
738+
739+
return new UdpReceiveResult(buffer, (IPEndPoint)result.RemoteEndPoint);
740+
}
741+
}
742+
642743
private void CreateClientSocket()
643744
{
644745
// Common initialization code.
@@ -892,45 +993,59 @@ public int Send(byte[] dgram, int bytes, IPEndPoint? endPoint)
892993
return Client.SendTo(dgram, 0, bytes, SocketFlags.None, endPoint);
893994
}
894995

895-
896-
// Sends a UDP datagram to the specified port on the specified remote host.
897-
public int Send(byte[] dgram, int bytes, string? hostname, int port)
996+
/// <summary>
997+
/// Sends a UDP datagram to the host at the specified remote endpoint.
998+
/// </summary>
999+
/// <param name="datagram">
1000+
/// An <see cref="ReadOnlySpan{T}"/> of Type <see cref="byte"/> that specifies the UDP datagram that you intend to send.
1001+
/// </param>
1002+
/// <param name="endPoint">
1003+
/// An <see cref="IPEndPoint"/> that represents the host and port to which to send the datagram.
1004+
/// </param>
1005+
/// <returns>The number of bytes sent.</returns>
1006+
/// <exception cref="InvalidOperationException"><see cref="UdpClient"/> has already established a default remote host and <paramref name="endPoint"/> is not <see langword="null"/>.</exception>
1007+
/// <exception cref="ObjectDisposedException"><see cref="UdpClient"/> is closed.</exception>
1008+
/// <exception cref="SocketException">An error occurred when accessing the socket.</exception>
1009+
public int Send(ReadOnlySpan<byte> datagram, IPEndPoint? endPoint)
8981010
{
8991011
ThrowIfDisposed();
9001012

901-
if (dgram == null)
902-
{
903-
throw new ArgumentNullException(nameof(dgram));
904-
}
905-
if (_active && ((hostname != null) || (port != 0)))
1013+
if (_active && endPoint != null)
9061014
{
9071015
// Do not allow sending packets to arbitrary host when connected
9081016
throw new InvalidOperationException(SR.net_udpconnected);
9091017
}
9101018

911-
if (hostname == null || port == 0)
912-
{
913-
return Client.Send(dgram, 0, bytes, SocketFlags.None);
914-
}
915-
916-
IPAddress[] addresses = Dns.GetHostAddresses(hostname);
917-
918-
int i = 0;
919-
for (; i < addresses.Length && !IsAddressFamilyCompatible(addresses[i].AddressFamily); i++)
1019+
if (endPoint == null)
9201020
{
921-
; // just count the addresses
1021+
return Client.Send(datagram, SocketFlags.None);
9221022
}
9231023

924-
if (addresses.Length == 0 || i == addresses.Length)
925-
{
926-
throw new ArgumentException(SR.net_invalidAddressList, nameof(hostname));
927-
}
1024+
CheckForBroadcast(endPoint.Address);
9281025

929-
CheckForBroadcast(addresses[i]);
930-
IPEndPoint ipEndPoint = new IPEndPoint(addresses[i], port);
931-
return Client.SendTo(dgram, 0, bytes, SocketFlags.None, ipEndPoint);
1026+
return Client.SendTo(datagram, SocketFlags.None, endPoint);
9321027
}
9331028

1029+
// Sends a UDP datagram to the specified port on the specified remote host.
1030+
public int Send(byte[] dgram, int bytes, string? hostname, int port) => Send(dgram, bytes, GetEndpoint(hostname, port));
1031+
1032+
/// <summary>
1033+
/// Sends a UDP datagram to a specified port on a specified remote host.
1034+
/// </summary>
1035+
/// <param name="datagram">
1036+
/// An <see cref="ReadOnlySpan{T}"/> of Type <see cref="byte"/> that specifies the UDP datagram that you intend to send.
1037+
/// </param>
1038+
/// <param name="hostname">
1039+
/// The name of the remote host to which you intend to send the datagram.
1040+
/// </param>
1041+
/// <param name="port">
1042+
/// The remote port number with which you intend to communicate.
1043+
/// </param>
1044+
/// <returns>The number of bytes sent.</returns>
1045+
/// <exception cref="InvalidOperationException">The <see cref="UdpClient"/> has already established a default remote host.</exception>
1046+
/// <exception cref="ObjectDisposedException">The <see cref="UdpClient"/> is closed.</exception>
1047+
/// <exception cref="SocketException">An error occurred when accessing the socket.</exception>
1048+
public int Send(ReadOnlySpan<byte> datagram, string? hostname, int port) => Send(datagram, GetEndpoint(hostname, port));
9341049

9351050
// Sends a UDP datagram to a remote host.
9361051
public int Send(byte[] dgram, int bytes)
@@ -950,6 +1065,29 @@ public int Send(byte[] dgram, int bytes)
9501065
return Client.Send(dgram, 0, bytes, SocketFlags.None);
9511066
}
9521067

1068+
/// <summary>
1069+
/// Sends a UDP datagram to a remote host.
1070+
/// </summary>
1071+
/// <param name="datagram">
1072+
/// An <see cref="ReadOnlySpan{T}"/> of Type <see cref="byte"/> that specifies the UDP datagram that you intend to send.
1073+
/// </param>
1074+
/// <returns>The number of bytes sent.</returns>
1075+
/// <exception cref="InvalidOperationException">The <see cref="UdpClient"/> has not established a default remote host.</exception>
1076+
/// <exception cref="ObjectDisposedException">The <see cref="UdpClient"/> is closed.</exception>
1077+
/// <exception cref="SocketException">An error occurred when accessing the socket.</exception>
1078+
public int Send(ReadOnlySpan<byte> datagram)
1079+
{
1080+
ThrowIfDisposed();
1081+
1082+
if (!_active)
1083+
{
1084+
// only allowed on connected socket
1085+
throw new InvalidOperationException(SR.net_notconnected);
1086+
}
1087+
1088+
return Client.Send(datagram, SocketFlags.None);
1089+
}
1090+
9531091
private void ThrowIfDisposed()
9541092
{
9551093
if (_disposed)

src/libraries/System.Net.Sockets/src/System/Net/Sockets/UdpReceiveResult.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
namespace System.Net.Sockets
77
{
88
/// <summary>
9-
/// Presents UDP receive result information from a call to the <see cref="UdpClient.ReceiveAsync"/> method
9+
/// Presents UDP receive result information from a call to the <see cref="UdpClient.ReceiveAsync()"/> and <see cref="UdpClient.ReceiveAsync(System.Threading.CancellationToken)"/> method
1010
/// </summary>
1111
public struct UdpReceiveResult : IEquatable<UdpReceiveResult>
1212
{

src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive/SendReceiveUdpClient.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ public sealed class SendReceiveUdpClient : MemberDatas
1111
{
1212
[OuterLoop]
1313
[Theory]
14-
[MemberData(nameof(Loopbacks))]
15-
public async Task SendToRecvFromAsync_Datagram_UDP_UdpClient(IPAddress loopbackAddress)
14+
[MemberData(nameof(LoopbacksAndUseMemory))]
15+
public async Task SendToRecvFromAsync_Datagram_UDP_UdpClient(IPAddress loopbackAddress, bool useMemoryOverload)
1616
{
1717
IPAddress leftAddress = loopbackAddress, rightAddress = loopbackAddress;
1818

@@ -66,7 +66,7 @@ public async Task SendToRecvFromAsync_Datagram_UDP_UdpClient(IPAddress loopbackA
6666
random.NextBytes(sendBuffer);
6767
sendBuffer[0] = (byte)sentDatagrams;
6868

69-
int sent = await right.SendAsync(sendBuffer, DatagramSize, leftEndpoint);
69+
int sent = useMemoryOverload ? await right.SendAsync(new ReadOnlyMemory<byte>(sendBuffer), leftEndpoint) : await right.SendAsync(sendBuffer, DatagramSize, leftEndpoint);
7070

7171
Assert.True(receiverAck.Wait(AckTimeout));
7272
receiverAck.Reset();
@@ -85,5 +85,13 @@ public async Task SendToRecvFromAsync_Datagram_UDP_UdpClient(IPAddress loopbackA
8585
}
8686
}
8787
}
88+
89+
public static readonly object[][] LoopbacksAndUseMemory = new object[][]
90+
{
91+
new object[] { IPAddress.IPv6Loopback, true },
92+
new object[] { IPAddress.IPv6Loopback, false },
93+
new object[] { IPAddress.Loopback, true },
94+
new object[] { IPAddress.Loopback, false },
95+
};
8896
}
8997
}

src/libraries/System.Net.Sockets/tests/FunctionalTests/TelemetryTest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,8 @@ await listener.RunWithCallbackAsync(e => events.Enqueue((e, e.ActivityId)), asyn
325325
await new SendReceive_Apm(null).SendRecv_Stream_TCP(IPAddress.Loopback, false).ConfigureAwait(false);
326326
await new SendReceive_Apm(null).SendRecv_Stream_TCP(IPAddress.Loopback, true).ConfigureAwait(false);
327327

328-
await new SendReceiveUdpClient().SendToRecvFromAsync_Datagram_UDP_UdpClient(IPAddress.Loopback).ConfigureAwait(false);
329-
await new SendReceiveUdpClient().SendToRecvFromAsync_Datagram_UDP_UdpClient(IPAddress.Loopback).ConfigureAwait(false);
328+
await new SendReceiveUdpClient().SendToRecvFromAsync_Datagram_UDP_UdpClient(IPAddress.Loopback, false).ConfigureAwait(false);
329+
await new SendReceiveUdpClient().SendToRecvFromAsync_Datagram_UDP_UdpClient(IPAddress.Loopback, false).ConfigureAwait(false);
330330

331331
await new NetworkStreamTest().CopyToAsync_AllDataCopied(4096, true).ConfigureAwait(false);
332332
await new NetworkStreamTest().Timeout_Roundtrips().ConfigureAwait(false);

0 commit comments

Comments
 (0)