* Adds synchronous span APIs for datagram sockets.
Part of #33418/#938
* Doc comments for new APIs.
* Fix review nits.
* Add 0-byte send tests.
public int ReceiveFrom(byte[] buffer, int size, System.Net.Sockets.SocketFlags socketFlags, ref System.Net.EndPoint remoteEP) { throw null; }
public int ReceiveFrom(byte[] buffer, ref System.Net.EndPoint remoteEP) { throw null; }
public int ReceiveFrom(byte[] buffer, System.Net.Sockets.SocketFlags socketFlags, ref System.Net.EndPoint remoteEP) { throw null; }
+ public int ReceiveFrom(System.Span<byte> buffer, ref System.Net.EndPoint remoteEP) { throw null; }
+ public int ReceiveFrom(System.Span<byte> buffer, System.Net.Sockets.SocketFlags socketFlags, ref System.Net.EndPoint remoteEP) { throw null; }
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; }
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; }
public bool ReceiveFromAsync(System.Net.Sockets.SocketAsyncEventArgs e) { throw null; }
public int SendTo(byte[] buffer, int size, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; }
public int SendTo(byte[] buffer, System.Net.EndPoint remoteEP) { throw null; }
public int SendTo(byte[] buffer, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; }
+ public int SendTo(System.ReadOnlySpan<byte> buffer, System.Net.EndPoint remoteEP) { throw null; }
+ public int SendTo(System.ReadOnlySpan<byte> buffer, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; }
public System.Threading.Tasks.Task<int> SendToAsync(System.ArraySegment<byte> buffer, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; }
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; }
public bool SendToAsync(System.Net.Sockets.SocketAsyncEventArgs e) { throw null; }
return SendTo(buffer, 0, buffer != null ? buffer.Length : 0, SocketFlags.None, remoteEP);
}
+ /// <summary>
+ /// Sends data to the specified endpoint.
+ /// </summary>
+ /// <param name="buffer">A span of bytes that contains the data to be sent.</param>
+ /// <param name="remoteEP">The <see cref="EndPoint"/> that represents the destination for the data.</param>
+ /// <returns>The number of bytes sent.</returns>
+ /// <exception cref="ArgumentNullException"><c>remoteEP</c> is <see langword="null" />.</exception>
+ /// <exception cref="SocketException">An error occurred when attempting to access the socket.</exception>
+ /// <exception cref="ObjectDisposedException">The <see cref="Socket"/> has been closed.</exception>
+ public int SendTo(ReadOnlySpan<byte> buffer, EndPoint remoteEP)
+ {
+ return SendTo(buffer, SocketFlags.None, remoteEP);
+ }
+
+ /// <summary>
+ /// Sends data to a specific endpoint using the specified <see cref="SocketFlags"/>.
+ /// </summary>
+ /// <param name="buffer">A span of bytes that contains the data to be sent.</param>
+ /// <param name="socketFlags">A bitwise combination of the <see cref="SocketFlags"/> values.</param>
+ /// <param name="remoteEP">The <see cref="EndPoint"/> that represents the destination for the data.</param>
+ /// <returns>The number of bytes sent.</returns>
+ /// <exception cref="ArgumentNullException"><c>remoteEP</c> is <see langword="null" />.</exception>
+ /// <exception cref="SocketException">An error occurred when attempting to access the socket.</exception>
+ /// <exception cref="ObjectDisposedException">The <see cref="Socket"/> has been closed.</exception>
+ public int SendTo(ReadOnlySpan<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP)
+ {
+ ThrowIfDisposed();
+ if (remoteEP == null)
+ {
+ throw new ArgumentNullException(nameof(remoteEP));
+ }
+
+ ValidateBlockingMode();
+
+ Internals.SocketAddress socketAddress = Serialize(ref remoteEP);
+
+ int bytesTransferred;
+ SocketError errorCode = SocketPal.SendTo(_handle, buffer, socketFlags, socketAddress.Buffer, socketAddress.Size, out bytesTransferred);
+
+ // Throw an appropriate SocketException if the native call fails.
+ if (errorCode != SocketError.Success)
+ {
+ UpdateSendSocketErrorForDisposed(ref errorCode);
+
+ UpdateStatusAfterSocketErrorAndThrowException(errorCode);
+ }
+ else if (SocketsTelemetry.Log.IsEnabled())
+ {
+ SocketsTelemetry.Log.BytesSent(bytesTransferred);
+ if (SocketType == SocketType.Dgram) SocketsTelemetry.Log.DatagramSent();
+ }
+
+ if (_rightEndPoint == null)
+ {
+ // Save a copy of the EndPoint so we can use it for Create().
+ _rightEndPoint = remoteEP;
+ }
+
+ return bytesTransferred;
+ }
+
// Receives data from a connected socket.
public int Receive(byte[] buffer, int size, SocketFlags socketFlags)
{
return ReceiveFrom(buffer, 0, buffer != null ? buffer.Length : 0, SocketFlags.None, ref remoteEP);
}
+ /// <summary>
+ /// Receives a datagram into the data buffer and stores the endpoint.
+ /// </summary>
+ /// <param name="buffer">A span of bytes that is the storage location for received data.</param>
+ /// <param name="remoteEP">An <see cref="EndPoint"/>, passed by reference, that represents the remote server.</param>
+ /// <returns>The number of bytes received.</returns>
+ /// <exception cref="ArgumentNullException"><c>remoteEP</c> is <see langword="null" />.</exception>
+ /// <exception cref="SocketException">An error occurred when attempting to access the socket.</exception>
+ /// <exception cref="ObjectDisposedException">The <see cref="Socket"/> has been closed.</exception>
+ public int ReceiveFrom(Span<byte> buffer, ref EndPoint remoteEP)
+ {
+ return ReceiveFrom(buffer, SocketFlags.None, ref remoteEP);
+ }
+
+ /// <summary>
+ /// Receives a datagram into the data buffer, using the specified <see cref="SocketFlags"/>, and stores the endpoint.
+ /// </summary>
+ /// <param name="buffer">A span of bytes that is the storage location for received data.</param>
+ /// <param name="socketFlags">A bitwise combination of the <see cref="SocketFlags"/> values.</param>
+ /// <param name="remoteEP">An <see cref="EndPoint"/>, passed by reference, that represents the remote server.</param>
+ /// <returns>The number of bytes received.</returns>
+ /// <exception cref="ArgumentNullException"><c>remoteEP</c> is <see langword="null" />.</exception>
+ /// <exception cref="SocketException">An error occurred when attempting to access the socket.</exception>
+ /// <exception cref="ObjectDisposedException">The <see cref="Socket"/> has been closed.</exception>
+ public int ReceiveFrom(Span<byte> buffer, SocketFlags socketFlags, ref EndPoint remoteEP)
+ {
+ ThrowIfDisposed();
+ ValidateReceiveFromEndpointAndState(remoteEP, nameof(remoteEP));
+
+ SocketPal.CheckDualModeReceiveSupport(this);
+
+ ValidateBlockingMode();
+
+ // We don't do a CAS demand here because the contents of remoteEP aren't used by
+ // WSARecvFrom; all that matters is that we generate a unique-to-this-call SocketAddress
+ // with the right address family.
+ EndPoint endPointSnapshot = remoteEP;
+ Internals.SocketAddress socketAddress = Serialize(ref endPointSnapshot);
+ Internals.SocketAddress socketAddressOriginal = IPEndPointExtensions.Serialize(endPointSnapshot);
+
+ int bytesTransferred;
+ SocketError errorCode = SocketPal.ReceiveFrom(_handle, buffer, socketFlags, socketAddress.Buffer, ref socketAddress.InternalSize, out bytesTransferred);
+
+ UpdateReceiveSocketErrorForDisposed(ref errorCode, bytesTransferred);
+ // If the native call fails we'll throw a SocketException.
+ SocketException? socketException = null;
+ if (errorCode != SocketError.Success)
+ {
+ socketException = new SocketException((int)errorCode);
+ UpdateStatusAfterSocketError(socketException);
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, socketException);
+
+ if (socketException.SocketErrorCode != SocketError.MessageSize)
+ {
+ throw socketException;
+ }
+ }
+ else if (SocketsTelemetry.Log.IsEnabled())
+ {
+ SocketsTelemetry.Log.BytesReceived(bytesTransferred);
+ if (SocketType == SocketType.Dgram) SocketsTelemetry.Log.DatagramReceived();
+ }
+
+ if (!socketAddressOriginal.Equals(socketAddress))
+ {
+ try
+ {
+ remoteEP = endPointSnapshot.Create(socketAddress);
+ }
+ catch
+ {
+ }
+ if (_rightEndPoint == null)
+ {
+ // Save a copy of the EndPoint so we can use it for Create().
+ _rightEndPoint = endPointSnapshot;
+ }
+ }
+
+ if (socketException != null)
+ {
+ throw socketException;
+ }
+
+ return bytesTransferred;
+ }
+
public int IOControl(int ioControlCode, byte[]? optionInValue, byte[]? optionOutValue)
{
ThrowIfDisposed();
return errorCode;
}
+ public static SocketError SendTo(SafeSocketHandle handle, ReadOnlySpan<byte> buffer, SocketFlags socketFlags, byte[] socketAddress, int socketAddressLen, out int bytesTransferred)
+ {
+ if (!handle.IsNonBlocking)
+ {
+ return handle.AsyncContext.SendTo(buffer, socketFlags, socketAddress, socketAddressLen, handle.SendTimeout, out bytesTransferred);
+ }
+
+ bytesTransferred = 0;
+ SocketError errorCode;
+ TryCompleteSendTo(handle, buffer, socketFlags, socketAddress, socketAddressLen, ref bytesTransferred, out errorCode);
+ return errorCode;
+ }
+
public static SocketError Receive(SafeSocketHandle handle, IList<ArraySegment<byte>> buffers, SocketFlags socketFlags, out int bytesTransferred)
{
SocketError errorCode;
return completed ? errorCode : SocketError.WouldBlock;
}
+ public static SocketError ReceiveFrom(SafeSocketHandle handle, Span<byte> buffer, SocketFlags socketFlags, byte[] socketAddress, ref int socketAddressLen, out int bytesTransferred)
+ {
+ if (!handle.IsNonBlocking)
+ {
+ return handle.AsyncContext.ReceiveFrom(buffer, ref socketFlags, socketAddress, ref socketAddressLen, handle.ReceiveTimeout, out bytesTransferred);
+ }
+
+ SocketError errorCode;
+ bool completed = TryCompleteReceiveFrom(handle, buffer, socketFlags, socketAddress, ref socketAddressLen, out bytesTransferred, out socketFlags, out errorCode);
+ return completed ? errorCode : SocketError.WouldBlock;
+ }
+
public static SocketError WindowsIoctl(SafeSocketHandle handle, int ioControlCode, byte[]? optionInValue, byte[]? optionOutValue, out int optionLength)
{
// Three codes are called out in the Winsock IOCTLs documentation as "The following Unix IOCTL codes (commands) are supported." They are
}
}
- public static unsafe SocketError SendTo(SafeSocketHandle handle, byte[] buffer, int offset, int size, SocketFlags socketFlags, byte[] peerAddress, int peerAddressSize, out int bytesTransferred)
+ public static SocketError SendTo(SafeSocketHandle handle, byte[] buffer, int offset, int size, SocketFlags socketFlags, byte[] peerAddress, int peerAddressSize, out int bytesTransferred) =>
+ SendTo(handle, buffer.AsSpan(offset, size), socketFlags, peerAddress, peerAddressSize, out bytesTransferred);
+
+ public static unsafe SocketError SendTo(SafeSocketHandle handle, ReadOnlySpan<byte> buffer, SocketFlags socketFlags, byte[] peerAddress, int peerAddressSize, out int bytesTransferred)
{
int bytesSent;
- if (buffer.Length == 0)
+ fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
{
- bytesSent = Interop.Winsock.sendto(
- handle,
- null,
- 0,
- socketFlags,
- peerAddress,
- peerAddressSize);
- }
- else
- {
- fixed (byte* pinnedBuffer = &buffer[0])
- {
- bytesSent = Interop.Winsock.sendto(
- handle,
- pinnedBuffer + offset,
- size,
- socketFlags,
- peerAddress,
- peerAddressSize);
- }
+ bytesSent = Interop.Winsock.sendto(handle, bufferPtr, buffer.Length, socketFlags, peerAddress, peerAddressSize);
}
if (bytesSent == (int)SocketError.SocketError)
return SocketError.Success;
}
- public static unsafe SocketError ReceiveFrom(SafeSocketHandle handle, byte[] buffer, int offset, int size, SocketFlags socketFlags, byte[] socketAddress, ref int addressLength, out int bytesTransferred)
+ public static unsafe SocketError ReceiveFrom(SafeSocketHandle handle, byte[] buffer, int offset, int size, SocketFlags socketFlags, byte[] socketAddress, ref int addressLength, out int bytesTransferred) =>
+ ReceiveFrom(handle, buffer.AsSpan(offset, size), SocketFlags.None, socketAddress, ref addressLength, out bytesTransferred);
+
+ public static unsafe SocketError ReceiveFrom(SafeSocketHandle handle, Span<byte> buffer, SocketFlags socketFlags, byte[] socketAddress, ref int addressLength, out int bytesTransferred)
{
int bytesReceived;
- if (buffer.Length == 0)
- {
- bytesReceived = Interop.Winsock.recvfrom(handle, null, 0, socketFlags, socketAddress, ref addressLength);
- }
- else
+
+ fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
{
- fixed (byte* pinnedBuffer = &buffer[0])
- {
- bytesReceived = Interop.Winsock.recvfrom(handle, pinnedBuffer + offset, size, socketFlags, socketAddress, ref addressLength);
- }
+ bytesReceived = Interop.Winsock.recvfrom(handle, bufferPtr, buffer.Length, socketFlags, socketAddress, ref addressLength);
}
if (bytesReceived == (int)SocketError.SocketError)
{
for (int i = 0; i < 3; i++)
{
- // Zero byte send should be a no-op
+ // Zero byte send should be a no-op
int bytesSent = await SendAsync(client, new ArraySegment<byte>(Array.Empty<byte>()));
Assert.Equal(0, bytesSent);
}
[Fact]
+ public async Task Send_0ByteSendTo_Success()
+ {
+ using (Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
+ using (Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
+ {
+ server.Bind(new IPEndPoint(IPAddress.Loopback, 0));
+ client.Bind(new IPEndPoint(IPAddress.Loopback, 0));
+
+ for (int i = 0; i < 3; i++)
+ {
+ // Send empty packet then real data.
+ int bytesSent = await SendToAsync(
+ client, new ArraySegment<byte>(Array.Empty<byte>()), server.LocalEndPoint!);
+ Assert.Equal(0, bytesSent);
+
+ await SendToAsync(client, new byte[] { 99 }, server.LocalEndPoint);
+
+ // Read empty packet
+ byte[] buffer = new byte[10];
+ SocketReceiveFromResult result = await ReceiveFromAsync(server, buffer, new IPEndPoint(IPAddress.Any, 0));
+
+ Assert.Equal(0, result.ReceivedBytes);
+ Assert.Equal(client.LocalEndPoint, result.RemoteEndPoint);
+
+ // Read real packet.
+ result = await ReceiveFromAsync(server, buffer, new IPEndPoint(IPAddress.Any, 0));
+
+ Assert.Equal(1, result.ReceivedBytes);
+ Assert.Equal(client.LocalEndPoint, result.RemoteEndPoint);
+ Assert.Equal(99, buffer[0]);
+ }
+ }
+ }
+
+ [Fact]
public async Task Receive0ByteReturns_WhenPeerDisconnects()
{
using (Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
for (int i = 0; i < 3; i++)
{
- // Zero byte send should be a no-op
+ // Zero byte send should be a no-op
int bytesSent = client.Send(ReadOnlySpan<byte>.Empty, SocketFlags.None);
Assert.Equal(0, bytesSent);
}
}
}
+
+ [Fact]
+ public async Task Send_0ByteSendTo_Span_Success()
+ {
+ using (Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
+ using (Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
+ {
+ server.Bind(new IPEndPoint(IPAddress.Loopback, 0));
+ client.Bind(new IPEndPoint(IPAddress.Loopback, 0));
+
+ for (int i = 0; i < 3; i++)
+ {
+ // Send empty packet then real data.
+ int bytesSent = client.SendTo(ReadOnlySpan<byte>.Empty, server.LocalEndPoint!);
+ Assert.Equal(0, bytesSent);
+
+ client.SendTo(new byte[] { 99 }, server.LocalEndPoint);
+
+ // Read empty packet
+ byte[] buffer = new byte[10];
+ SocketReceiveFromResult result = await ReceiveFromAsync(server, buffer, new IPEndPoint(IPAddress.Any, 0));
+
+ Assert.Equal(0, result.ReceivedBytes);
+ Assert.Equal(client.LocalEndPoint, result.RemoteEndPoint);
+
+ // Read real packet.
+ result = await ReceiveFromAsync(server, buffer, new IPEndPoint(IPAddress.Any, 0));
+
+ Assert.Equal(1, result.ReceivedBytes);
+ Assert.Equal(client.LocalEndPoint, result.RemoteEndPoint);
+ Assert.Equal(99, buffer[0]);
+ }
+ }
+ }
+
}
public sealed class SendReceive_SpanSyncForceNonBlocking : SendReceive<SocketHelperSpanSyncForceNonBlocking>
}
[Fact]
+ public async Task Send_0ByteSendTo_Memory_Success()
+ {
+ using (Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
+ using (Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
+ {
+ server.Bind(new IPEndPoint(IPAddress.Loopback, 0));
+ client.Bind(new IPEndPoint(IPAddress.Loopback, 0));
+
+ for (int i = 0; i < 3; i++)
+ {
+ // Send empty packet then real data.
+ int bytesSent = await client.SendToAsync(
+ ReadOnlyMemory<byte>.Empty, SocketFlags.None, server.LocalEndPoint!);
+ Assert.Equal(0, bytesSent);
+
+ await client.SendToAsync(new byte[] { 99 }, SocketFlags.None, server.LocalEndPoint);
+
+ // Read empty packet
+ byte[] buffer = new byte[10];
+ SocketReceiveFromResult result = await ReceiveFromAsync(server, buffer, new IPEndPoint(IPAddress.Any, 0));
+
+ Assert.Equal(0, result.ReceivedBytes);
+ Assert.Equal(client.LocalEndPoint, result.RemoteEndPoint);
+
+ // Read real packet.
+ result = await ReceiveFromAsync(server, buffer, new IPEndPoint(IPAddress.Any, 0));
+
+ Assert.Equal(1, result.ReceivedBytes);
+ Assert.Equal(client.LocalEndPoint, result.RemoteEndPoint);
+ Assert.Equal(99, buffer[0]);
+ }
+ }
+ }
+
+ [Fact]
public async Task Precanceled_Throws()
{
using (var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
Task.Run(() => s.Receive((Span<byte>)buffer, SocketFlags.None));
public override Task<int> SendAsync(Socket s, ArraySegment<byte> buffer) =>
Task.Run(() => s.Send((ReadOnlySpan<byte>)buffer, SocketFlags.None));
+ public override Task<SocketReceiveFromResult> ReceiveFromAsync(Socket s, ArraySegment<byte> buffer,
+ EndPoint endPoint) =>
+ Task.Run(() =>
+ {
+ SocketFlags socketFlags = SocketFlags.None;
+ int received = s.ReceiveFrom((Span<byte>)buffer, socketFlags, ref endPoint);
+ return new SocketReceiveFromResult
+ {
+ ReceivedBytes = received,
+ RemoteEndPoint = endPoint,
+ };
+ });
+ public override Task<int> SendToAsync(Socket s, ArraySegment<byte> buffer, EndPoint endPoint) =>
+ Task.Run(() => s.SendTo((ReadOnlySpan<byte>)buffer, endPoint));
public override Task<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(Socket s, ArraySegment<byte> buffer, EndPoint endPoint) =>
Task.Run(() =>
{
PacketInformation = ipPacketInformation
};
});
+
public override Task SendFileAsync(Socket s, string fileName, ArraySegment<byte> preBuffer, ArraySegment<byte> postBuffer, TransmitFileOptions flags) =>
Task.Run(() => s.SendFile(fileName, preBuffer, postBuffer, flags));
public override bool UsesSync => true;