|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace System.Net.Sockets
{
// Provides the underlying stream of data for network access.
public class NetworkStream : Stream
{
// Used by the class to hold the underlying socket the stream uses.
private readonly Socket _streamSocket;
// Whether the stream should dispose of the socket when the stream is disposed
private readonly bool _ownsSocket;
// Used by the class to indicate that the stream is m_Readable.
private bool _readable;
// Used by the class to indicate that the stream is writable.
private bool _writeable;
// Whether Dispose has been called.
private bool _disposed;
// Creates a new instance of the System.Net.Sockets.NetworkStream class for the specified System.Net.Sockets.Socket.
public NetworkStream(Socket socket)
: this(socket, FileAccess.ReadWrite, ownsSocket: false)
{
}
public NetworkStream(Socket socket, bool ownsSocket)
: this(socket, FileAccess.ReadWrite, ownsSocket)
{
}
public NetworkStream(Socket socket, FileAccess access)
: this(socket, access, ownsSocket: false)
{
}
public NetworkStream(Socket socket, FileAccess access, bool ownsSocket)
{
ArgumentNullException.ThrowIfNull(socket);
if (!OperatingSystem.IsWasi() && !socket.Blocking)
{
// Stream.Read*/Write* are incompatible with the semantics of non-blocking sockets, and
// allowing non-blocking sockets could result in non-deterministic failures from those
// operations. A developer that requires using NetworkStream with a non-blocking socket can
// temporarily flip Socket.Blocking as a workaround.
throw new IOException(SR.net_sockets_blocking);
}
if (!socket.Connected)
{
throw new IOException(SR.net_notconnected);
}
if (socket.SocketType != SocketType.Stream)
{
throw new IOException(SR.net_notstream);
}
_streamSocket = socket;
_ownsSocket = ownsSocket;
switch (access)
{
case FileAccess.Read:
_readable = true;
break;
case FileAccess.Write:
_writeable = true;
break;
case FileAccess.ReadWrite:
default: // assume FileAccess.ReadWrite
_readable = true;
_writeable = true;
break;
}
}
public Socket Socket => _streamSocket;
// Used by the class to indicate that the stream is m_Readable.
protected bool Readable
{
get { return _readable; }
set { _readable = value; }
}
// Used by the class to indicate that the stream is writable.
protected bool Writeable
{
get { return _writeable; }
set { _writeable = value; }
}
// Indicates that data can be read from the stream.
// We return the readability of this stream. This is a read only property.
public override bool CanRead => _readable;
// Indicates that the stream can seek a specific location
// in the stream. This property always returns false.
public override bool CanSeek => false;
// Indicates that data can be written to the stream.
public override bool CanWrite => _writeable;
// Indicates whether we can timeout
public override bool CanTimeout => true;
// Set/Get ReadTimeout, note of a strange behavior, 0 timeout == infinite for sockets,
// so we map this to -1, and if you set 0, we cannot support it
public override int ReadTimeout
{
get
{
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // https://github.com/dotnet/runtime/issues/108151
int timeout = (int)_streamSocket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout)!;
if (timeout == 0)
{
return -1;
}
return timeout;
}
set
{
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // https://github.com/dotnet/runtime/issues/108151
if (value <= 0 && value != System.Threading.Timeout.Infinite)
{
throw new ArgumentOutOfRangeException(nameof(value), SR.net_io_timeout_use_gt_zero);
}
SetSocketTimeoutOption(SocketShutdown.Receive, value, false);
}
}
// Set/Get WriteTimeout, note of a strange behavior, 0 timeout == infinite for sockets,
// so we map this to -1, and if you set 0, we cannot support it
public override int WriteTimeout
{
get
{
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // https://github.com/dotnet/runtime/issues/108151
int timeout = (int)_streamSocket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout)!;
if (timeout == 0)
{
return -1;
}
return timeout;
}
set
{
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // https://github.com/dotnet/runtime/issues/108151
if (value <= 0 && value != System.Threading.Timeout.Infinite)
{
throw new ArgumentOutOfRangeException(nameof(value), SR.net_io_timeout_use_gt_zero);
}
SetSocketTimeoutOption(SocketShutdown.Send, value, false);
}
}
// Indicates data is available on the stream to be read.
// This property checks to see if at least one byte of data is currently available
public virtual bool DataAvailable
{
get
{
ThrowIfDisposed();
// Ask the socket how many bytes are available. If it's
// not zero, return true.
return _streamSocket.Available != 0;
}
}
// The length of data available on the stream. Always throws NotSupportedException.
public override long Length
{
get
{
throw new NotSupportedException(SR.net_noseek);
}
}
// Gets or sets the position in the stream. Always throws NotSupportedException.
public override long Position
{
get
{
throw new NotSupportedException(SR.net_noseek);
}
set
{
throw new NotSupportedException(SR.net_noseek);
}
}
// Seeks a specific position in the stream. This method is not supported by the
// NetworkStream class.
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException(SR.net_noseek);
}
// Read - provide core Read functionality.
//
// Provide core read functionality. All we do is call through to the
// socket Receive functionality.
//
// Input:
//
// Buffer - Buffer to read into.
// Offset - Offset into the buffer where we're to read.
// Count - Number of bytes to read.
//
// Returns:
//
// Number of bytes we read, or 0 if the socket is closed.
public override int Read(byte[] buffer, int offset, int count)
{
if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
ValidateBufferArguments(buffer, offset, count);
ThrowIfDisposed();
if (!CanRead)
{
throw new InvalidOperationException(SR.net_writeonlystream);
}
try
{
return _streamSocket.Receive(buffer, offset, count, 0);
}
catch (Exception exception) when (!(exception is OutOfMemoryException))
{
throw WrapException(SR.net_io_readfailure, exception);
}
}
public override int Read(Span<byte> buffer)
{
if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
if (GetType() != typeof(NetworkStream))
{
// NetworkStream is not sealed, and a derived type may have overridden Read(byte[], int, int) prior
// to this Read(Span<byte>) overload being introduced. In that case, this Read(Span<byte>) overload
// should use the behavior of Read(byte[],int,int) overload.
return base.Read(buffer);
}
ThrowIfDisposed();
if (!CanRead) throw new InvalidOperationException(SR.net_writeonlystream);
try
{
return _streamSocket.Receive(buffer, SocketFlags.None);
}
catch (Exception exception) when (!(exception is OutOfMemoryException))
{
throw WrapException(SR.net_io_readfailure, exception);
}
}
public override unsafe int ReadByte()
{
if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
byte b;
return Read(new Span<byte>(&b, 1)) == 0 ? -1 : b;
}
// Write - provide core Write functionality.
//
// Provide core write functionality. All we do is call through to the
// socket Send method..
//
// Input:
//
// Buffer - Buffer to write from.
// Offset - Offset into the buffer from where we'll start writing.
// Count - Number of bytes to write.
//
// Returns:
//
// Number of bytes written. We'll throw an exception if we
// can't write everything. It's brutal, but there's no other
// way to indicate an error.
public override void Write(byte[] buffer, int offset, int count)
{
if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
ValidateBufferArguments(buffer, offset, count);
ThrowIfDisposed();
if (!CanWrite)
{
throw new InvalidOperationException(SR.net_readonlystream);
}
try
{
// Since the socket is in blocking mode this will always complete
// after ALL the requested number of bytes was transferred.
_streamSocket.Send(buffer, offset, count, SocketFlags.None);
}
catch (Exception exception) when (!(exception is OutOfMemoryException))
{
throw WrapException(SR.net_io_writefailure, exception);
}
}
public override void Write(ReadOnlySpan<byte> buffer)
{
if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
if (GetType() != typeof(NetworkStream))
{
// NetworkStream is not sealed, and a derived type may have overridden Write(byte[], int, int) prior
// to this Write(ReadOnlySpan<byte>) overload being introduced. In that case, this Write(ReadOnlySpan<byte>)
// overload should use the behavior of Write(byte[],int,int) overload.
base.Write(buffer);
return;
}
ThrowIfDisposed();
if (!CanWrite) throw new InvalidOperationException(SR.net_readonlystream);
try
{
_streamSocket.Send(buffer, SocketFlags.None);
}
catch (Exception exception) when (!(exception is OutOfMemoryException))
{
throw WrapException(SR.net_io_writefailure, exception);
}
}
public override unsafe void WriteByte(byte value) =>
Write(new ReadOnlySpan<byte>(&value, 1));
private int _closeTimeout = Socket.DefaultCloseTimeout; // -1 = respect linger options
/// <summary>Closes the <see cref="NetworkStream"/> after waiting the specified time to allow data to be sent.</summary>
/// <param name="timeout">The number of milliseconds to wait to send any remaining data before closing.</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="timeout"/> is less than -1.</exception>
/// <remarks>
/// The Close method frees both unmanaged and managed resources associated with the <see cref="NetworkStream"/>.
/// If the <see cref="NetworkStream"/> owns the underlying <see cref="Socket"/>, it is closed as well.
/// If a <see cref="NetworkStream"/> was associated with a <see cref="TcpClient"/>, the <see cref="Close(int)"/> method
/// will close the TCP connection, but not dispose of the associated <see cref="TcpClient"/>.
/// </remarks>
public void Close(int timeout)
{
ArgumentOutOfRangeException.ThrowIfLessThan(timeout, -1);
_closeTimeout = timeout;
Dispose();
}
/// <summary>Closes the <see cref="NetworkStream"/> after waiting the specified time to allow data to be sent.</summary>
/// <param name="timeout">The amount of time to wait to send any remaining data before closing.</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="timeout"/> is less than -1 milliseconds or greater than <see cref="int.MaxValue"/> milliseconds.</exception>
/// <remarks>
/// The Close method frees both unmanaged and managed resources associated with the <see cref="NetworkStream"/>.
/// If the <see cref="NetworkStream"/> owns the underlying <see cref="Socket"/>, it is closed as well.
/// If a <see cref="NetworkStream"/> was associated with a <see cref="TcpClient"/>, the <see cref="Close(int)"/> method
/// will close the TCP connection, but not dispose of the associated <see cref="TcpClient"/>.
/// </remarks>
public void Close(TimeSpan timeout) => Close(ToTimeoutMilliseconds(timeout));
private static int ToTimeoutMilliseconds(TimeSpan timeout)
{
long totalMilliseconds = (long)timeout.TotalMilliseconds;
ArgumentOutOfRangeException.ThrowIfLessThan(totalMilliseconds, -1, nameof(timeout));
ArgumentOutOfRangeException.ThrowIfGreaterThan(totalMilliseconds, int.MaxValue, nameof(timeout));
return (int)totalMilliseconds;
}
protected override void Dispose(bool disposing)
{
if (Interlocked.Exchange(ref _disposed, true))
{
return;
}
if (disposing)
{
// The only resource we need to free is the network stream, since this
// is based on the client socket, closing the stream will cause us
// to flush the data to the network, close the stream and (in the
// NetoworkStream code) close the socket as well.
_readable = false;
_writeable = false;
if (_ownsSocket)
{
// If we own the Socket (false by default), close it
// ignoring possible exceptions (eg: the user told us
// that we own the Socket but it closed at some point of time,
// here we would get an ObjectDisposedException)
_streamSocket.InternalShutdown(SocketShutdown.Both);
_streamSocket.Close(_closeTimeout);
}
}
base.Dispose(disposing);
}
~NetworkStream() => Dispose(false);
// BeginRead - provide async read functionality.
//
// This method provides async read functionality. All we do is
// call through to the underlying socket async read.
//
// Input:
//
// buffer - Buffer to read into.
// offset - Offset into the buffer where we're to read.
// size - Number of bytes to read.
//
// Returns:
//
// An IASyncResult, representing the read.
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state)
{
if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
ValidateBufferArguments(buffer, offset, count);
ThrowIfDisposed();
if (!CanRead)
{
throw new InvalidOperationException(SR.net_writeonlystream);
}
try
{
return _streamSocket.BeginReceive(
buffer,
offset,
count,
SocketFlags.None,
callback,
state);
}
catch (Exception exception) when (!(exception is OutOfMemoryException))
{
throw WrapException(SR.net_io_readfailure, exception);
}
}
// EndRead - handle the end of an async read.
//
// This method is called when an async read is completed. All we
// do is call through to the core socket EndReceive functionality.
//
// Returns:
//
// The number of bytes read. May throw an exception.
public override int EndRead(IAsyncResult asyncResult)
{
if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
ThrowIfDisposed();
ArgumentNullException.ThrowIfNull(asyncResult);
try
{
return _streamSocket.EndReceive(asyncResult);
}
catch (Exception exception) when (!(exception is OutOfMemoryException))
{
throw WrapException(SR.net_io_readfailure, exception);
}
}
// BeginWrite - provide async write functionality.
//
// This method provides async write functionality. All we do is
// call through to the underlying socket async send.
//
// Input:
//
// buffer - Buffer to write into.
// offset - Offset into the buffer where we're to write.
// size - Number of bytes to written.
//
// Returns:
//
// An IASyncResult, representing the write.
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state)
{
if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
ValidateBufferArguments(buffer, offset, count);
ThrowIfDisposed();
if (!CanWrite)
{
throw new InvalidOperationException(SR.net_readonlystream);
}
try
{
// Call BeginSend on the Socket.
return _streamSocket.BeginSend(
buffer,
offset,
count,
SocketFlags.None,
callback,
state);
}
catch (Exception exception) when (!(exception is OutOfMemoryException))
{
throw WrapException(SR.net_io_writefailure, exception);
}
}
// Handle the end of an asynchronous write.
// This method is called when an async write is completed. All we
// do is call through to the core socket EndSend functionality.
// Returns: The number of bytes read. May throw an exception.
public override void EndWrite(IAsyncResult asyncResult)
{
if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
ThrowIfDisposed();
ArgumentNullException.ThrowIfNull(asyncResult);
try
{
_streamSocket.EndSend(asyncResult);
}
catch (Exception exception) when (!(exception is OutOfMemoryException))
{
throw WrapException(SR.net_io_writefailure, exception);
}
}
// ReadAsync - provide async read functionality.
//
// This method provides async read functionality. All we do is
// call through to the Begin/EndRead methods.
//
// Input:
//
// buffer - Buffer to read into.
// offset - Offset into the buffer where we're to read.
// size - Number of bytes to read.
// cancellationToken - Token used to request cancellation of the operation
//
// Returns:
//
// A Task<int> representing the read.
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ValidateBufferArguments(buffer, offset, count);
ThrowIfDisposed();
if (!CanRead)
{
throw new InvalidOperationException(SR.net_writeonlystream);
}
try
{
return _streamSocket.ReceiveAsync(
new Memory<byte>(buffer, offset, count),
SocketFlags.None,
fromNetworkStream: true,
cancellationToken).AsTask();
}
catch (Exception exception) when (!(exception is OutOfMemoryException))
{
throw WrapException(SR.net_io_readfailure, exception);
}
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
bool canRead = CanRead; // Prevent race with Dispose.
ThrowIfDisposed();
if (!canRead)
{
throw new InvalidOperationException(SR.net_writeonlystream);
}
try
{
return _streamSocket.ReceiveAsync(
buffer,
SocketFlags.None,
fromNetworkStream: true,
cancellationToken: cancellationToken);
}
catch (Exception exception) when (!(exception is OutOfMemoryException))
{
throw WrapException(SR.net_io_readfailure, exception);
}
}
// WriteAsync - provide async write functionality.
//
// This method provides async write functionality. All we do is
// call through to the Begin/EndWrite methods.
//
// Input:
//
// buffer - Buffer to write into.
// offset - Offset into the buffer where we're to write.
// size - Number of bytes to write.
// cancellationToken - Token used to request cancellation of the operation
//
// Returns:
//
// A Task representing the write.
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ValidateBufferArguments(buffer, offset, count);
ThrowIfDisposed();
if (!CanWrite)
{
throw new InvalidOperationException(SR.net_readonlystream);
}
try
{
return _streamSocket.SendAsyncForNetworkStream(
new ReadOnlyMemory<byte>(buffer, offset, count),
SocketFlags.None,
cancellationToken).AsTask();
}
catch (Exception exception) when (!(exception is OutOfMemoryException))
{
throw WrapException(SR.net_io_writefailure, exception);
}
}
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
bool canWrite = CanWrite; // Prevent race with Dispose.
ThrowIfDisposed();
if (!canWrite)
{
throw new InvalidOperationException(SR.net_readonlystream);
}
try
{
return _streamSocket.SendAsyncForNetworkStream(
buffer,
SocketFlags.None,
cancellationToken);
}
catch (Exception exception) when (!(exception is OutOfMemoryException))
{
throw WrapException(SR.net_io_writefailure, exception);
}
}
// Flushes data from the stream. This is meaningless for us, so it does nothing.
public override void Flush()
{
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
// Sets the length of the stream. Always throws NotSupportedException
public override void SetLength(long value)
{
throw new NotSupportedException(SR.net_noseek);
}
private int _currentReadTimeout = -1;
private int _currentWriteTimeout = -1;
internal void SetSocketTimeoutOption(SocketShutdown mode, int timeout, bool silent)
{
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // https://github.com/dotnet/runtime/issues/108151
if (timeout < 0)
{
timeout = 0; // -1 becomes 0 for the winsock stack
}
if (mode == SocketShutdown.Send || mode == SocketShutdown.Both)
{
if (timeout != _currentWriteTimeout)
{
_streamSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, timeout, silent);
_currentWriteTimeout = timeout;
}
}
if (mode == SocketShutdown.Receive || mode == SocketShutdown.Both)
{
if (timeout != _currentReadTimeout)
{
_streamSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, timeout, silent);
_currentReadTimeout = timeout;
}
}
}
private void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
private static IOException WrapException(string resourceFormatString, Exception innerException)
{
return new IOException(SR.Format(resourceFormatString, innerException.Message), innerException);
}
}
}
|