Skip to content

Commit dcb42e8

Browse files
authored
Adds synchronous span APIs for datagram sockets. (#51956)
* Adds synchronous span APIs for datagram sockets. Part of #33418/#938 * Doc comments for new APIs. * Fix review nits. * Add 0-byte send tests.
1 parent 1197851 commit dcb42e8

File tree

6 files changed

+312
-34
lines changed

6 files changed

+312
-34
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,8 @@ public void Listen(int backlog) { }
385385
public int ReceiveFrom(byte[] buffer, int size, System.Net.Sockets.SocketFlags socketFlags, ref System.Net.EndPoint remoteEP) { throw null; }
386386
public int ReceiveFrom(byte[] buffer, ref System.Net.EndPoint remoteEP) { throw null; }
387387
public int ReceiveFrom(byte[] buffer, System.Net.Sockets.SocketFlags socketFlags, ref System.Net.EndPoint remoteEP) { throw null; }
388+
public int ReceiveFrom(System.Span<byte> buffer, ref System.Net.EndPoint remoteEP) { throw null; }
389+
public int ReceiveFrom(System.Span<byte> buffer, System.Net.Sockets.SocketFlags socketFlags, ref System.Net.EndPoint remoteEP) { throw null; }
388390
public System.Threading.Tasks.Task<System.Net.Sockets.SocketReceiveFromResult> ReceiveFromAsync(System.ArraySegment<byte> buffer, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEndPoint) { throw null; }
389391
public System.Threading.Tasks.ValueTask<System.Net.Sockets.SocketReceiveFromResult> ReceiveFromAsync(System.Memory<byte> buffer, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEndPoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
390392
public bool ReceiveFromAsync(System.Net.Sockets.SocketAsyncEventArgs e) { throw null; }
@@ -419,6 +421,8 @@ public void SendFile(string? fileName, System.ReadOnlySpan<byte> preBuffer, Syst
419421
public int SendTo(byte[] buffer, int size, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; }
420422
public int SendTo(byte[] buffer, System.Net.EndPoint remoteEP) { throw null; }
421423
public int SendTo(byte[] buffer, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; }
424+
public int SendTo(System.ReadOnlySpan<byte> buffer, System.Net.EndPoint remoteEP) { throw null; }
425+
public int SendTo(System.ReadOnlySpan<byte> buffer, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; }
422426
public System.Threading.Tasks.Task<int> SendToAsync(System.ArraySegment<byte> buffer, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; }
423427
public System.Threading.Tasks.ValueTask<int> SendToAsync(System.ReadOnlyMemory<byte> buffer, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
424428
public bool SendToAsync(System.Net.Sockets.SocketAsyncEventArgs e) { throw null; }

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

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,6 +1380,67 @@ public int SendTo(byte[] buffer, EndPoint remoteEP)
13801380
return SendTo(buffer, 0, buffer != null ? buffer.Length : 0, SocketFlags.None, remoteEP);
13811381
}
13821382

1383+
/// <summary>
1384+
/// Sends data to the specified endpoint.
1385+
/// </summary>
1386+
/// <param name="buffer">A span of bytes that contains the data to be sent.</param>
1387+
/// <param name="remoteEP">The <see cref="EndPoint"/> that represents the destination for the data.</param>
1388+
/// <returns>The number of bytes sent.</returns>
1389+
/// <exception cref="ArgumentNullException"><c>remoteEP</c> is <see langword="null" />.</exception>
1390+
/// <exception cref="SocketException">An error occurred when attempting to access the socket.</exception>
1391+
/// <exception cref="ObjectDisposedException">The <see cref="Socket"/> has been closed.</exception>
1392+
public int SendTo(ReadOnlySpan<byte> buffer, EndPoint remoteEP)
1393+
{
1394+
return SendTo(buffer, SocketFlags.None, remoteEP);
1395+
}
1396+
1397+
/// <summary>
1398+
/// Sends data to a specific endpoint using the specified <see cref="SocketFlags"/>.
1399+
/// </summary>
1400+
/// <param name="buffer">A span of bytes that contains the data to be sent.</param>
1401+
/// <param name="socketFlags">A bitwise combination of the <see cref="SocketFlags"/> values.</param>
1402+
/// <param name="remoteEP">The <see cref="EndPoint"/> that represents the destination for the data.</param>
1403+
/// <returns>The number of bytes sent.</returns>
1404+
/// <exception cref="ArgumentNullException"><c>remoteEP</c> is <see langword="null" />.</exception>
1405+
/// <exception cref="SocketException">An error occurred when attempting to access the socket.</exception>
1406+
/// <exception cref="ObjectDisposedException">The <see cref="Socket"/> has been closed.</exception>
1407+
public int SendTo(ReadOnlySpan<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP)
1408+
{
1409+
ThrowIfDisposed();
1410+
if (remoteEP == null)
1411+
{
1412+
throw new ArgumentNullException(nameof(remoteEP));
1413+
}
1414+
1415+
ValidateBlockingMode();
1416+
1417+
Internals.SocketAddress socketAddress = Serialize(ref remoteEP);
1418+
1419+
int bytesTransferred;
1420+
SocketError errorCode = SocketPal.SendTo(_handle, buffer, socketFlags, socketAddress.Buffer, socketAddress.Size, out bytesTransferred);
1421+
1422+
// Throw an appropriate SocketException if the native call fails.
1423+
if (errorCode != SocketError.Success)
1424+
{
1425+
UpdateSendSocketErrorForDisposed(ref errorCode);
1426+
1427+
UpdateStatusAfterSocketErrorAndThrowException(errorCode);
1428+
}
1429+
else if (SocketsTelemetry.Log.IsEnabled())
1430+
{
1431+
SocketsTelemetry.Log.BytesSent(bytesTransferred);
1432+
if (SocketType == SocketType.Dgram) SocketsTelemetry.Log.DatagramSent();
1433+
}
1434+
1435+
if (_rightEndPoint == null)
1436+
{
1437+
// Save a copy of the EndPoint so we can use it for Create().
1438+
_rightEndPoint = remoteEP;
1439+
}
1440+
1441+
return bytesTransferred;
1442+
}
1443+
13831444
// Receives data from a connected socket.
13841445
public int Receive(byte[] buffer, int size, SocketFlags socketFlags)
13851446
{
@@ -1764,6 +1825,93 @@ public int ReceiveFrom(byte[] buffer, ref EndPoint remoteEP)
17641825
return ReceiveFrom(buffer, 0, buffer != null ? buffer.Length : 0, SocketFlags.None, ref remoteEP);
17651826
}
17661827

1828+
/// <summary>
1829+
/// Receives a datagram into the data buffer and stores the endpoint.
1830+
/// </summary>
1831+
/// <param name="buffer">A span of bytes that is the storage location for received data.</param>
1832+
/// <param name="remoteEP">An <see cref="EndPoint"/>, passed by reference, that represents the remote server.</param>
1833+
/// <returns>The number of bytes received.</returns>
1834+
/// <exception cref="ArgumentNullException"><c>remoteEP</c> is <see langword="null" />.</exception>
1835+
/// <exception cref="SocketException">An error occurred when attempting to access the socket.</exception>
1836+
/// <exception cref="ObjectDisposedException">The <see cref="Socket"/> has been closed.</exception>
1837+
public int ReceiveFrom(Span<byte> buffer, ref EndPoint remoteEP)
1838+
{
1839+
return ReceiveFrom(buffer, SocketFlags.None, ref remoteEP);
1840+
}
1841+
1842+
/// <summary>
1843+
/// Receives a datagram into the data buffer, using the specified <see cref="SocketFlags"/>, and stores the endpoint.
1844+
/// </summary>
1845+
/// <param name="buffer">A span of bytes that is the storage location for received data.</param>
1846+
/// <param name="socketFlags">A bitwise combination of the <see cref="SocketFlags"/> values.</param>
1847+
/// <param name="remoteEP">An <see cref="EndPoint"/>, passed by reference, that represents the remote server.</param>
1848+
/// <returns>The number of bytes received.</returns>
1849+
/// <exception cref="ArgumentNullException"><c>remoteEP</c> is <see langword="null" />.</exception>
1850+
/// <exception cref="SocketException">An error occurred when attempting to access the socket.</exception>
1851+
/// <exception cref="ObjectDisposedException">The <see cref="Socket"/> has been closed.</exception>
1852+
public int ReceiveFrom(Span<byte> buffer, SocketFlags socketFlags, ref EndPoint remoteEP)
1853+
{
1854+
ThrowIfDisposed();
1855+
ValidateReceiveFromEndpointAndState(remoteEP, nameof(remoteEP));
1856+
1857+
SocketPal.CheckDualModeReceiveSupport(this);
1858+
1859+
ValidateBlockingMode();
1860+
1861+
// We don't do a CAS demand here because the contents of remoteEP aren't used by
1862+
// WSARecvFrom; all that matters is that we generate a unique-to-this-call SocketAddress
1863+
// with the right address family.
1864+
EndPoint endPointSnapshot = remoteEP;
1865+
Internals.SocketAddress socketAddress = Serialize(ref endPointSnapshot);
1866+
Internals.SocketAddress socketAddressOriginal = IPEndPointExtensions.Serialize(endPointSnapshot);
1867+
1868+
int bytesTransferred;
1869+
SocketError errorCode = SocketPal.ReceiveFrom(_handle, buffer, socketFlags, socketAddress.Buffer, ref socketAddress.InternalSize, out bytesTransferred);
1870+
1871+
UpdateReceiveSocketErrorForDisposed(ref errorCode, bytesTransferred);
1872+
// If the native call fails we'll throw a SocketException.
1873+
SocketException? socketException = null;
1874+
if (errorCode != SocketError.Success)
1875+
{
1876+
socketException = new SocketException((int)errorCode);
1877+
UpdateStatusAfterSocketError(socketException);
1878+
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, socketException);
1879+
1880+
if (socketException.SocketErrorCode != SocketError.MessageSize)
1881+
{
1882+
throw socketException;
1883+
}
1884+
}
1885+
else if (SocketsTelemetry.Log.IsEnabled())
1886+
{
1887+
SocketsTelemetry.Log.BytesReceived(bytesTransferred);
1888+
if (SocketType == SocketType.Dgram) SocketsTelemetry.Log.DatagramReceived();
1889+
}
1890+
1891+
if (!socketAddressOriginal.Equals(socketAddress))
1892+
{
1893+
try
1894+
{
1895+
remoteEP = endPointSnapshot.Create(socketAddress);
1896+
}
1897+
catch
1898+
{
1899+
}
1900+
if (_rightEndPoint == null)
1901+
{
1902+
// Save a copy of the EndPoint so we can use it for Create().
1903+
_rightEndPoint = endPointSnapshot;
1904+
}
1905+
}
1906+
1907+
if (socketException != null)
1908+
{
1909+
throw socketException;
1910+
}
1911+
1912+
return bytesTransferred;
1913+
}
1914+
17671915
public int IOControl(int ioControlCode, byte[]? optionInValue, byte[]? optionOutValue)
17681916
{
17691917
ThrowIfDisposed();

src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,19 @@ public static SocketError SendTo(SafeSocketHandle handle, byte[] buffer, int off
12031203
return errorCode;
12041204
}
12051205

1206+
public static SocketError SendTo(SafeSocketHandle handle, ReadOnlySpan<byte> buffer, SocketFlags socketFlags, byte[] socketAddress, int socketAddressLen, out int bytesTransferred)
1207+
{
1208+
if (!handle.IsNonBlocking)
1209+
{
1210+
return handle.AsyncContext.SendTo(buffer, socketFlags, socketAddress, socketAddressLen, handle.SendTimeout, out bytesTransferred);
1211+
}
1212+
1213+
bytesTransferred = 0;
1214+
SocketError errorCode;
1215+
TryCompleteSendTo(handle, buffer, socketFlags, socketAddress, socketAddressLen, ref bytesTransferred, out errorCode);
1216+
return errorCode;
1217+
}
1218+
12061219
public static SocketError Receive(SafeSocketHandle handle, IList<ArraySegment<byte>> buffers, SocketFlags socketFlags, out int bytesTransferred)
12071220
{
12081221
SocketError errorCode;
@@ -1311,6 +1324,18 @@ public static SocketError ReceiveFrom(SafeSocketHandle handle, byte[] buffer, in
13111324
return completed ? errorCode : SocketError.WouldBlock;
13121325
}
13131326

1327+
public static SocketError ReceiveFrom(SafeSocketHandle handle, Span<byte> buffer, SocketFlags socketFlags, byte[] socketAddress, ref int socketAddressLen, out int bytesTransferred)
1328+
{
1329+
if (!handle.IsNonBlocking)
1330+
{
1331+
return handle.AsyncContext.ReceiveFrom(buffer, ref socketFlags, socketAddress, ref socketAddressLen, handle.ReceiveTimeout, out bytesTransferred);
1332+
}
1333+
1334+
SocketError errorCode;
1335+
bool completed = TryCompleteReceiveFrom(handle, buffer, socketFlags, socketAddress, ref socketAddressLen, out bytesTransferred, out socketFlags, out errorCode);
1336+
return completed ? errorCode : SocketError.WouldBlock;
1337+
}
1338+
13141339
public static SocketError WindowsIoctl(SafeSocketHandle handle, int ioControlCode, byte[]? optionInValue, byte[]? optionOutValue, out int optionLength)
13151340
{
13161341
// Three codes are called out in the Winsock IOCTLs documentation as "The following Unix IOCTL codes (commands) are supported." They are

src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -300,31 +300,15 @@ public static unsafe SocketError SendFile(SafeSocketHandle handle, SafeFileHandl
300300
}
301301
}
302302

303-
public static unsafe SocketError SendTo(SafeSocketHandle handle, byte[] buffer, int offset, int size, SocketFlags socketFlags, byte[] peerAddress, int peerAddressSize, out int bytesTransferred)
303+
public static SocketError SendTo(SafeSocketHandle handle, byte[] buffer, int offset, int size, SocketFlags socketFlags, byte[] peerAddress, int peerAddressSize, out int bytesTransferred) =>
304+
SendTo(handle, buffer.AsSpan(offset, size), socketFlags, peerAddress, peerAddressSize, out bytesTransferred);
305+
306+
public static unsafe SocketError SendTo(SafeSocketHandle handle, ReadOnlySpan<byte> buffer, SocketFlags socketFlags, byte[] peerAddress, int peerAddressSize, out int bytesTransferred)
304307
{
305308
int bytesSent;
306-
if (buffer.Length == 0)
309+
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
307310
{
308-
bytesSent = Interop.Winsock.sendto(
309-
handle,
310-
null,
311-
0,
312-
socketFlags,
313-
peerAddress,
314-
peerAddressSize);
315-
}
316-
else
317-
{
318-
fixed (byte* pinnedBuffer = &buffer[0])
319-
{
320-
bytesSent = Interop.Winsock.sendto(
321-
handle,
322-
pinnedBuffer + offset,
323-
size,
324-
socketFlags,
325-
peerAddress,
326-
peerAddressSize);
327-
}
311+
bytesSent = Interop.Winsock.sendto(handle, bufferPtr, buffer.Length, socketFlags, peerAddress, peerAddressSize);
328312
}
329313

330314
if (bytesSent == (int)SocketError.SocketError)
@@ -528,19 +512,16 @@ public static unsafe SocketError ReceiveMessageFrom(Socket socket, SafeSocketHan
528512
return SocketError.Success;
529513
}
530514

531-
public static unsafe SocketError ReceiveFrom(SafeSocketHandle handle, byte[] buffer, int offset, int size, SocketFlags socketFlags, byte[] socketAddress, ref int addressLength, out int bytesTransferred)
515+
public static unsafe SocketError ReceiveFrom(SafeSocketHandle handle, byte[] buffer, int offset, int size, SocketFlags socketFlags, byte[] socketAddress, ref int addressLength, out int bytesTransferred) =>
516+
ReceiveFrom(handle, buffer.AsSpan(offset, size), SocketFlags.None, socketAddress, ref addressLength, out bytesTransferred);
517+
518+
public static unsafe SocketError ReceiveFrom(SafeSocketHandle handle, Span<byte> buffer, SocketFlags socketFlags, byte[] socketAddress, ref int addressLength, out int bytesTransferred)
532519
{
533520
int bytesReceived;
534-
if (buffer.Length == 0)
535-
{
536-
bytesReceived = Interop.Winsock.recvfrom(handle, null, 0, socketFlags, socketAddress, ref addressLength);
537-
}
538-
else
521+
522+
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
539523
{
540-
fixed (byte* pinnedBuffer = &buffer[0])
541-
{
542-
bytesReceived = Interop.Winsock.recvfrom(handle, pinnedBuffer + offset, size, socketFlags, socketAddress, ref addressLength);
543-
}
524+
bytesReceived = Interop.Winsock.recvfrom(handle, bufferPtr, buffer.Length, socketFlags, socketAddress, ref addressLength);
544525
}
545526

546527
if (bytesReceived == (int)SocketError.SocketError)

0 commit comments

Comments
 (0)