#if !NETFX_CORE || UNITY_EDITOR // TcpClient.cs // // Author: // Phillip Pearson (pp@myelin.co.nz) // Gonzalo Paniagua Javier (gonzalo@novell.com) // Sridhar Kulkarni (sridharkulkarni@gmail.com) // Marek Safar (marek.safar@gmail.com) // // Copyright (C) 2001, Phillip Pearson http://www.myelin.co.nz // Copyright (c) 2006 Novell, Inc. (http://www.novell.com) // Copyright 2011 Xamarin Inc. // // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; namespace BestHTTP.PlatformSupport.TcpClient.General { // This is a little modified TcpClient class from the Mono src tree. public class TcpClient : IDisposable { enum Properties : uint { LingerState = 1, NoDelay = 2, ReceiveBufferSize = 4, ReceiveTimeout = 8, SendBufferSize = 16, SendTimeout = 32 } // private data NetworkStream stream; bool active; Socket client; bool disposed; Properties values; int recv_timeout, send_timeout; int recv_buffer_size, send_buffer_size; LingerOption linger_state; bool no_delay; private void Init(AddressFamily family) { active = false; if (client != null) { client.Close(); client = null; } client = new Socket(family, SocketType.Stream, ProtocolType.Tcp); } public TcpClient() { Init(AddressFamily.InterNetwork); //client.Bind(new IPEndPoint(IPAddress.Any, 0)); ConnectTimeout = TimeSpan.FromSeconds(2); } public TcpClient(AddressFamily family) { if (family != AddressFamily.InterNetwork && family != AddressFamily.InterNetworkV6) { throw new ArgumentException("Family must be InterNetwork or InterNetworkV6", "family"); } Init(family); /*IPAddress any = IPAddress.Any; if (family == AddressFamily.InterNetworkV6) any = IPAddress.IPv6Any; client.Bind(new IPEndPoint(any, 0));*/ ConnectTimeout = TimeSpan.FromSeconds(2); } public TcpClient(IPEndPoint localEP) { Init(localEP.AddressFamily); //client.Bind(localEP); ConnectTimeout = TimeSpan.FromSeconds(2); } public TcpClient(string hostname, int port) { ConnectTimeout = TimeSpan.FromSeconds(2); Connect(hostname, port); } protected bool Active { get { return active; } set { active = value; } } public Socket Client { get { return client; } set { client = value; stream = null; } } public int Available { get { return client.Available; } } public bool Connected { get { return client.Connected; } } public bool IsConnected() { try { return !(Client.Poll(1, SelectMode.SelectRead) && Client.Available == 0); } catch (Exception) { return false; } } public bool ExclusiveAddressUse { get { return (client.ExclusiveAddressUse); } set { client.ExclusiveAddressUse = value; } } internal void SetTcpClient(Socket s) { Client = s; } public LingerOption LingerState { get { if ((values & Properties.LingerState) != 0) return linger_state; return (LingerOption)client.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger); } set { if (!client.Connected) { linger_state = value; values |= Properties.LingerState; return; } client.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.Linger, value); } } public bool NoDelay { get { if ((values & Properties.NoDelay) != 0) return no_delay; return (bool)client.GetSocketOption( SocketOptionLevel.Tcp, SocketOptionName.NoDelay); } set { if (!client.Connected) { no_delay = value; values |= Properties.NoDelay; return; } client.SetSocketOption( SocketOptionLevel.Tcp, SocketOptionName.NoDelay, value ? 1 : 0); } } public int ReceiveBufferSize { get { if ((values & Properties.ReceiveBufferSize) != 0) return recv_buffer_size; return (int)client.GetSocketOption( SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer); } set { if (!client.Connected) { recv_buffer_size = value; values |= Properties.ReceiveBufferSize; return; } client.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, value); } } public int ReceiveTimeout { get { if ((values & Properties.ReceiveTimeout) != 0) return recv_timeout; return (int)client.GetSocketOption( SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout); } set { if (!client.Connected) { recv_timeout = value; values |= Properties.ReceiveTimeout; return; } client.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, value); } } public int SendBufferSize { get { if ((values & Properties.SendBufferSize) != 0) return send_buffer_size; return (int)client.GetSocketOption( SocketOptionLevel.Socket, SocketOptionName.SendBuffer); } set { if (!client.Connected) { send_buffer_size = value; values |= Properties.SendBufferSize; return; } client.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.SendBuffer, value); } } public int SendTimeout { get { if ((values & Properties.SendTimeout) != 0) return send_timeout; return (int)client.GetSocketOption( SocketOptionLevel.Socket, SocketOptionName.SendTimeout); } set { if (!client.Connected) { send_timeout = value; values |= Properties.SendTimeout; return; } client.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.SendTimeout, value); } } public TimeSpan ConnectTimeout { get; set; } // methods public void Close() { ((IDisposable)this).Dispose(); } public void Connect(IPEndPoint remoteEP) { try { if (ConnectTimeout > TimeSpan.Zero) { // Third version, works in WebPlayer System.Threading.ManualResetEvent mre = new System.Threading.ManualResetEvent(false); IAsyncResult result = client.BeginConnect(remoteEP, (res) => mre.Set(), null); active = mre.WaitOne(ConnectTimeout); if (active) client.EndConnect(result); else { try { client.Disconnect(true); } catch { } throw new TimeoutException("Connection timed out!"); } // Second version with timeout, in WebPlayer can't connect: // Attempt to access a private/protected method failed. at System.Security.SecurityManager.ThrowException (System.Exception ex) [0x00000] in :0 /*IAsyncResult result = client.BeginConnect(remoteEP, null, null); Active = result.AsyncWaitHandle.WaitOne(ConnectTimeout, true); if (active) { client.EndConnect(result); } else { client.Close(); //throw new SocketException(10060); throw new TimeoutException("Connection timed out!"); }*/ } else { // First(old) version, no timeout client.Connect(remoteEP); active = true; } } finally { CheckDisposed(); } } public void Connect(IPAddress address, int port) { Connect(new IPEndPoint(address, port)); } void SetOptions() { Properties props = values; values = 0; if ((props & Properties.LingerState) != 0) LingerState = linger_state; if ((props & Properties.NoDelay) != 0) NoDelay = no_delay; if ((props & Properties.ReceiveBufferSize) != 0) ReceiveBufferSize = recv_buffer_size; if ((props & Properties.ReceiveTimeout) != 0) ReceiveTimeout = recv_timeout; if ((props & Properties.SendBufferSize) != 0) SendBufferSize = send_buffer_size; if ((props & Properties.SendTimeout) != 0) SendTimeout = send_timeout; } public void Connect(string hostname, int port) { if (ConnectTimeout > TimeSpan.Zero) { // https://forum.unity3d.com/threads/best-http-released.200006/page-37#post-3150972 System.Threading.ManualResetEvent mre = new System.Threading.ManualResetEvent(false); IAsyncResult result = Dns.BeginGetHostAddresses(hostname, (res) => mre.Set(), null); bool success = mre.WaitOne(ConnectTimeout); if (success) { IPAddress[] addresses = Dns.EndGetHostAddresses(result); Connect(addresses, port, null); } else { throw new TimeoutException("DNS resolve timed out!"); } } else { IPAddress[] addresses = Dns.GetHostAddresses(hostname); Connect(addresses, port, null); } } public void Connect(IPAddress[] ipAddresses, int port, HTTPRequest request) { CheckDisposed(); if (ipAddresses == null) { throw new ArgumentNullException("ipAddresses"); } List addresses = new List(ipAddresses); addresses.Sort((a, b) => a.AddressFamily - b.AddressFamily); for (int i = 0; i < addresses.Count; i++) { try { IPAddress address = addresses[i]; if (address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any)) { throw new SocketException((int)SocketError.AddressNotAvailable); } Init(address.AddressFamily); if (address.AddressFamily == AddressFamily.InterNetwork) { //client.Bind(new IPEndPoint(IPAddress.Any, 0)); } else if (address.AddressFamily == AddressFamily.InterNetworkV6) { //client.Bind(new IPEndPoint(IPAddress.IPv6Any, 0)); } else { throw new NotSupportedException("This method is only valid for sockets in the InterNetwork and InterNetworkV6 families"); } if (request != null && request.IsCancellationRequested) throw new Exception("IsCancellationRequested"); HTTPManager.Logger.Verbose("TcpClient", string.Format("Trying to connect to {0}:{1}", address.ToString(), port.ToString()), request.Context); Connect(new IPEndPoint(address, port)); if (values != 0) { SetOptions(); } try { // Enable Keep-Alive packets client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); /* TCP_KEEPIDLE 4 // Start keeplives after this period TCP_KEEPINTVL 5 // Interval between keepalives TCP_KEEPCNT 6 // Number of keepalives before death */ //client.SetSocketOption(SocketOptionLevel.Tcp, (SocketOptionName)4, 30); //client.SetSocketOption(SocketOptionLevel.Tcp, (SocketOptionName)5, 10); } catch { } #if UNITY_WINDOWS || UNITY_EDITOR // Set the keep-alive time and interval on windows // https://msdn.microsoft.com/en-us/library/windows/desktop/dd877220%28v=vs.85%29.aspx // https://msdn.microsoft.com/en-us/library/windows/desktop/ee470551%28v=vs.85%29.aspx try { //SetKeepAlive(true, 30000, 1000); } catch{ } #endif HTTPManager.Logger.Information("TcpClient", string.Format("Connected to {0}:{1}", address.ToString(), port.ToString()), request.Context); break; } catch (Exception e) { /* Reinitialise the socket so * other properties still work * (see no-arg constructor) */ Init(AddressFamily.InterNetwork); /* This is the last known * address, so re-throw the * exception */ if (i == addresses.Count - 1) { throw e; } } } } public void EndConnect(IAsyncResult asyncResult) { client.EndConnect(asyncResult); } public IAsyncResult BeginConnect(IPAddress address, int port, AsyncCallback requestCallback, object state) { return client.BeginConnect(address, port, requestCallback, state); } public IAsyncResult BeginConnect(IPAddress[] addresses, int port, AsyncCallback requestCallback, object state) { return client.BeginConnect(addresses, port, requestCallback, state); } public IAsyncResult BeginConnect(string host, int port, AsyncCallback requestCallback, object state) { return client.BeginConnect(host, port, requestCallback, state); } void IDisposable.Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposed) return; disposed = true; if (disposing) { // release managed resources NetworkStream s = stream; stream = null; if (s != null) { // This closes the socket as well, as the NetworkStream // owns the socket. s.Close(); active = false; s = null; } else if (client != null) { client.Close(); client = null; } } } ~TcpClient() { Dispose(false); } public Stream GetStream() { try { if (stream == null) stream = new NetworkStream(client, true); return stream; } finally { CheckDisposed(); } } private void CheckDisposed() { if (disposed) throw new ObjectDisposedException(GetType().FullName); } #if UNITY_WINDOWS || UNITY_EDITOR public void SetKeepAlive(bool on, uint keepAliveTime, uint keepAliveInterval) { int size = System.Runtime.InteropServices.Marshal.SizeOf(new uint()); var inOptionValues = new byte[size * 3]; BitConverter.GetBytes((uint)(on ? 1 : 0)).CopyTo(inOptionValues, 0); BitConverter.GetBytes((uint)keepAliveTime).CopyTo(inOptionValues, size); BitConverter.GetBytes((uint)keepAliveInterval).CopyTo(inOptionValues, size * 2); //client.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); int dwBytesRet = 0; WSAIoctl(client.Handle, /*SIO_KEEPALIVE_VALS*/ System.Net.Sockets.IOControlCode.KeepAliveValues, inOptionValues, inOptionValues.Length, /*NULL*/IntPtr.Zero, 0, ref dwBytesRet, /*NULL*/IntPtr.Zero, /*NULL*/IntPtr.Zero); } [System.Runtime.InteropServices.DllImport("Ws2_32.dll")] public static extern int WSAIoctl( /* Socket, Mode */ IntPtr s, System.Net.Sockets.IOControlCode dwIoControlCode, /* Optional Or IntPtr.Zero, 0 */ byte[] lpvInBuffer, int cbInBuffer, /* Optional Or IntPtr.Zero, 0 */ IntPtr lpvOutBuffer, int cbOutBuffer, /* reference to receive Size */ ref int lpcbBytesReturned, /* IntPtr.Zero, IntPtr.Zero */ IntPtr lpOverlapped, IntPtr lpCompletionRoutine); #endif } } #endif