File: System\Net\Sockets\TCPClient.cs
Web Access
Project: src\src\libraries\System.Net.Sockets\src\System.Net.Sockets.csproj (System.Net.Sockets)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
 
namespace System.Net.Sockets
{
    // The System.Net.Sockets.TcpClient class provide TCP services at a higher level
    // of abstraction than the System.Net.Sockets.Socket class. System.Net.Sockets.TcpClient
    // is used to create a Client connection to a remote host.
    public class TcpClient : IDisposable
    {
        private AddressFamily _family;
        private Socket _clientSocket = null!; // initialized by helper called from ctor
        private NetworkStream? _dataStream;
        private volatile bool _disposed;
        private bool _active;
 
        private bool Disposed => _disposed;
 
        // Initializes a new instance of the System.Net.Sockets.TcpClient class.
        public TcpClient() : this(AddressFamily.Unknown)
        {
        }
 
        // Initializes a new instance of the System.Net.Sockets.TcpClient class.
        public TcpClient(AddressFamily family)
        {
            if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, family);
 
            if (family is not (AddressFamily.InterNetwork or AddressFamily.InterNetworkV6 or AddressFamily.Unknown))
            {
                throw new ArgumentException(SR.Format(SR.net_protocol_invalid_family, "TCP"), nameof(family));
            }
 
            _family = family;
            InitializeClientSocket();
        }
 
        // Initializes a new instance of the System.Net.Sockets.TcpClient class with the specified end point.
        public TcpClient(IPEndPoint localEP)
        {
            ArgumentNullException.ThrowIfNull(localEP);
 
            if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, localEP);
            _family = localEP.AddressFamily; // set before calling CreateSocket
            InitializeClientSocket();
            _clientSocket.Bind(localEP);
        }
 
        // Initializes a new instance of the System.Net.Sockets.TcpClient class and connects to the specified port on
        // the specified host.
        public TcpClient(string hostname, int port) : this(AddressFamily.Unknown)
        {
            ArgumentNullException.ThrowIfNull(hostname);
 
            if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, hostname);
            if (!TcpValidationHelpers.ValidatePortNumber(port))
            {
                throw new ArgumentOutOfRangeException(nameof(port));
            }
 
            try
            {
                Connect(hostname, port);
            }
            catch
            {
                _clientSocket?.Close();
                throw;
            }
        }
 
        // Used by TcpListener.Accept().
        internal TcpClient(Socket acceptedSocket)
        {
            _clientSocket = acceptedSocket;
            _active = true;
        }
 
        // Used by the class to indicate that a connection has been made.
        protected bool Active
        {
            get { return _active; }
            set { _active = value; }
        }
 
        public int Available => Client?.Available ?? 0;
 
        // Used by the class to provide the underlying network socket.
        public Socket Client
        {
            get { return Disposed ? null! : _clientSocket; }
            set
            {
                _clientSocket = value;
                _family = _clientSocket?.AddressFamily ?? AddressFamily.Unknown;
                if (_clientSocket == null)
                {
                    InitializeClientSocket();
                }
            }
        }
 
        public bool Connected => Client?.Connected ?? false;
 
        public bool ExclusiveAddressUse
        {
            get { return Client?.ExclusiveAddressUse ?? false; }
            set
            {
                if (_clientSocket != null)
                {
                    _clientSocket.ExclusiveAddressUse = value;
                }
            }
        }
 
        // Connects the Client to the specified port on the specified host.
        public void Connect(string hostname, int port)
        {
            if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
 
            ThrowIfDisposed();
 
            ArgumentNullException.ThrowIfNull(hostname);
            if (!TcpValidationHelpers.ValidatePortNumber(port))
            {
                throw new ArgumentOutOfRangeException(nameof(port));
            }
 
            Client.Connect(hostname, port);
            _family = Client.AddressFamily;
            _active = true;
 
        }
 
        // Connects the Client to the specified port on the specified host.
        public void Connect(IPAddress address, int port)
        {
            if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
 
            ThrowIfDisposed();
 
            ArgumentNullException.ThrowIfNull(address);
            if (!TcpValidationHelpers.ValidatePortNumber(port))
            {
                throw new ArgumentOutOfRangeException(nameof(port));
            }
 
            IPEndPoint remoteEP = new IPEndPoint(address, port);
            Connect(remoteEP);
        }
 
        // Connect the Client to the specified end point.
        public void Connect(IPEndPoint remoteEP)
        {
            if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
 
            ThrowIfDisposed();
 
            ArgumentNullException.ThrowIfNull(remoteEP);
 
            Client.Connect(remoteEP);
            _family = Client.AddressFamily;
            _active = true;
        }
 
        public void Connect(IPAddress[] ipAddresses, int port)
        {
            if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
 
            ThrowIfDisposed();
 
            Client.Connect(ipAddresses, port);
            _family = Client.AddressFamily;
            _active = true;
        }
 
        public Task ConnectAsync(IPAddress address, int port) =>
            CompleteConnectAsync(Client.ConnectAsync(address, port));
 
        public Task ConnectAsync(string host, int port) =>
            CompleteConnectAsync(Client.ConnectAsync(host, port));
 
        public Task ConnectAsync(IPAddress[] addresses, int port) =>
            CompleteConnectAsync(Client.ConnectAsync(addresses, port));
 
        /// <summary>
        /// Connects the client to a remote TCP host using the specified endpoint as an asynchronous operation.
        /// </summary>
        /// <param name="remoteEP">The <see cref="IPEndPoint"/> to which you intend to connect.</param>
        /// <returns>A task representing the asynchronous operation.</returns>
        public Task ConnectAsync(IPEndPoint remoteEP) =>
            CompleteConnectAsync(Client.ConnectAsync(remoteEP));
 
        private async Task CompleteConnectAsync(Task task)
        {
            await task.ConfigureAwait(false);
            _active = true;
        }
 
        public ValueTask ConnectAsync(IPAddress address, int port, CancellationToken cancellationToken) =>
            CompleteConnectAsync(Client.ConnectAsync(address, port, cancellationToken));
 
        public ValueTask ConnectAsync(string host, int port, CancellationToken cancellationToken) =>
            CompleteConnectAsync(Client.ConnectAsync(host, port, cancellationToken));
 
        public ValueTask ConnectAsync(IPAddress[] addresses, int port, CancellationToken cancellationToken) =>
            CompleteConnectAsync(Client.ConnectAsync(addresses, port, cancellationToken));
 
        /// <summary>
        /// Connects the client to a remote TCP host using the specified endpoint as an asynchronous operation.
        /// </summary>
        /// <param name="remoteEP">The <see cref="IPEndPoint"/> to which you intend to connect.</param>
        /// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
        /// <returns>A task representing the asynchronous operation.</returns>
        public ValueTask ConnectAsync(IPEndPoint remoteEP, CancellationToken cancellationToken) =>
            CompleteConnectAsync(Client.ConnectAsync(remoteEP, cancellationToken));
 
        private async ValueTask CompleteConnectAsync(ValueTask task)
        {
            await task.ConfigureAwait(false);
            _active = true;
        }
 
        public IAsyncResult BeginConnect(IPAddress address, int port, AsyncCallback? requestCallback, object? state) =>
            Client.BeginConnect(address, port, requestCallback, state);
 
        public IAsyncResult BeginConnect(string host, int port, AsyncCallback? requestCallback, object? state) =>
            Client.BeginConnect(host, port, requestCallback, state);
 
        public IAsyncResult BeginConnect(IPAddress[] addresses, int port, AsyncCallback? requestCallback, object? state) =>
            Client.BeginConnect(addresses, port, requestCallback, state);
 
        public void EndConnect(IAsyncResult asyncResult)
        {
            if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
 
            _clientSocket.EndConnect(asyncResult);
            _active = true;
 
        }
 
        // Returns the stream used to read and write data to the remote host.
        public NetworkStream GetStream()
        {
            ThrowIfDisposed();
 
            if (!Connected)
            {
                throw new InvalidOperationException(SR.net_notconnected);
            }
 
            return _dataStream ??= new NetworkStream(Client, true);
        }
 
        public void Close() => Dispose();
 
        // Disposes the Tcp connection.
        protected virtual void Dispose(bool disposing)
        {
            if (!Interlocked.Exchange(ref _disposed, true))
            {
                if (disposing)
                {
                    NetworkStream? dataStream = _dataStream;
                    if (dataStream != null)
                    {
                        dataStream.Dispose();
                    }
                    else
                    {
                        // If the NetworkStream wasn't created, the Socket might
                        // still be there and needs to be closed. In the case in which
                        // we are bound to a local IPEndPoint this will remove the
                        // binding and free up the IPEndPoint for later uses.
                        Socket chkClientSocket = Volatile.Read(ref _clientSocket);
                        if (chkClientSocket != null)
                        {
                            try
                            {
                                chkClientSocket.InternalShutdown(SocketShutdown.Both);
                            }
                            finally
                            {
                                chkClientSocket.Close();
                            }
                        }
                    }
 
                    GC.SuppressFinalize(this);
                }
            }
        }
 
        public void Dispose() => Dispose(true);
 
        ~TcpClient() => Dispose(false);
 
        // Gets or sets the size of the receive buffer in bytes.
        public int ReceiveBufferSize
        {
            get { return (int)Client.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer)!; }
            set { Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, value); }
        }
 
        // Gets or sets the size of the send buffer in bytes.
        public int SendBufferSize
        {
            get { return (int)Client.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer)!; }
            set { Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, value); }
        }
 
        // Gets or sets the receive time out value of the connection in milliseconds.
        public int ReceiveTimeout
        {
            get
            {
                if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // https://github.com/dotnet/runtime/issues/108151
                return (int)Client.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout)!;
            }
            set
            {
                if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // https://github.com/dotnet/runtime/issues/108151
                Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, value);
            }
        }
 
        // Gets or sets the send time out value of the connection in milliseconds.
        public int SendTimeout
        {
            get
            {
                if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // https://github.com/dotnet/runtime/issues/108151
                return (int)Client.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout)!;
            }
            set
            {
                if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // https://github.com/dotnet/runtime/issues/108151
                Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, value);
            }
        }
 
        // Gets or sets the value of the connection's linger option.
        [DisallowNull]
        public LingerOption? LingerState
        {
            get
            {
                return Client.LingerState;
            }
            set
            {
                if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // https://github.com/dotnet/runtime/issues/108151
                Client.LingerState = value!;
            }
        }
 
        // Enables or disables delay when send or receive buffers are full.
        public bool NoDelay
        {
            get { return Client.NoDelay; }
            set { Client.NoDelay = value; }
        }
 
        private void InitializeClientSocket()
        {
            Debug.Assert(_clientSocket == null);
            if (_family == AddressFamily.Unknown)
            {
                // If AF was not explicitly set try to initialize dual mode socket or fall-back to IPv4.
                _clientSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
                if (_clientSocket.AddressFamily == AddressFamily.InterNetwork)
                {
                    _family = AddressFamily.InterNetwork;
                }
            }
            else
            {
                _clientSocket = new Socket(_family, SocketType.Stream, ProtocolType.Tcp);
            }
        }
 
        private void ThrowIfDisposed()
        {
            ObjectDisposedException.ThrowIf(Disposed, this);
        }
    }
}