ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ptupit...@apache.org
Subject [2/2] ignite git commit: IGNITE-7282 .NET: Thin client: Implement Automatic Reconnect and Failover
Date Thu, 27 Sep 2018 16:49:57 GMT
IGNITE-7282 .NET: Thin client: Implement Automatic Reconnect and Failover

This closes #3467


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/45abb9c7
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/45abb9c7
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/45abb9c7

Branch: refs/heads/master
Commit: 45abb9c7069291d1bafa4a26edf23a36cc527716
Parents: 4527fe6
Author: Pavel Tupitsyn <ptupitsyn@apache.org>
Authored: Thu Sep 27 19:49:38 2018 +0300
Committer: Pavel Tupitsyn <ptupitsyn@apache.org>
Committed: Thu Sep 27 19:49:38 2018 +0300

----------------------------------------------------------------------
 .../Interop/PlatformBenchmarkBase.cs            |   2 +-
 .../Apache.Ignite.Core.Tests.DotNetCore.csproj  |   1 +
 .../Apache.Ignite.Core.Tests.csproj             |   1 +
 .../Client/Cache/CacheTestNoMeta.cs             |   4 +-
 .../Client/Cache/CacheTestSsl.cs                |   5 +-
 .../Client/ClientConnectionTest.cs              | 222 ++++++++++++++---
 .../Client/ClientTestBase.cs                    |   4 +-
 .../Client/EndpointTest.cs                      |  96 +++++++
 .../Client/IgniteClientConfigurationTest.cs     |  12 +-
 .../Client/RawSecureSocketTest.cs               |  18 +-
 .../Config/Client/IgniteClientConfiguration.xml | Bin 3144 -> 3384 bytes
 .../Dataload/DataStreamerTest.cs                |  23 +-
 .../Apache.Ignite.Core.Tests/ExceptionsTest.cs  |   3 +-
 .../Apache.Ignite.Core.Tests/custom_app.config  |   6 +-
 .../Apache.Ignite.Core.csproj                   |   5 +-
 .../Apache.Ignite.Core/Client/IIgniteClient.cs  |  15 ++
 .../Client/IgniteClientConfiguration.cs         |  44 ++++
 .../IgniteClientConfigurationSection.xsd        |  19 +-
 .../Apache.Ignite.Core/IgniteConfiguration.cs   |   1 +
 .../dotnet/Apache.Ignite.Core/Ignition.cs       |   1 -
 .../Impl/Binary/BinaryProcessorClient.cs        |   4 +-
 .../Impl/Client/Cache/CacheClient.cs            |  10 +-
 .../Impl/Client/ClientFailoverSocket.cs         | 247 +++++++++++++++++++
 .../Impl/Client/ClientSocket.cs                 | 166 +++++++------
 .../Apache.Ignite.Core/Impl/Client/Endpoint.cs  | 148 +++++++++++
 .../Impl/Client/IClientSocket.cs                |  58 +++++
 .../Impl/Client/IgniteClient.cs                 |  28 ++-
 .../Impl/Common/IgniteArgumentCheck.cs          |  10 +-
 .../dotnet/Apache.Ignite.sln.DotSettings        |   1 +
 .../ThinClient/ThinClientPutGetExample.cs       |   5 +-
 .../ThinClient/ThinClientQueryExample.cs        |   5 +-
 .../ThinClient/ThinClientSqlExample.cs          |  21 +-
 32 files changed, 999 insertions(+), 186 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Interop/PlatformBenchmarkBase.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Interop/PlatformBenchmarkBase.cs b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Interop/PlatformBenchmarkBase.cs
index ca9fb0c..a234e9a 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Interop/PlatformBenchmarkBase.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Interop/PlatformBenchmarkBase.cs
@@ -135,7 +135,7 @@ namespace Apache.Ignite.Benchmarks.Interop
         {
             return new IgniteClientConfiguration
             {
-                Host = IPAddress.Loopback.ToString()
+                Endpoints = new[] {IPAddress.Loopback.ToString()}
             };
         }
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Apache.Ignite.Core.Tests.DotNetCore.csproj
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Apache.Ignite.Core.Tests.DotNetCore.csproj b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Apache.Ignite.Core.Tests.DotNetCore.csproj
index 69170c2..e27f8b7 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Apache.Ignite.Core.Tests.DotNetCore.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Apache.Ignite.Core.Tests.DotNetCore.csproj
@@ -107,6 +107,7 @@
     <Compile Include="..\Apache.Ignite.Core.Tests\Client\Cache\LinqTest.cs" Link="ThinClient\Cache\LinqTest.cs" />
     <Compile Include="..\Apache.Ignite.Core.Tests\Client\ClientConnectionTest.cs" Link="ThinClient\ClientConnectionTest.cs" />
     <Compile Include="..\Apache.Ignite.Core.Tests\Client\ClientTestBase.cs" Link="ThinClient\ClientTestBase.cs" />
+    <Compile Include="..\Apache.Ignite.Core.Tests\Client\EndpointTest.cs" Link="ThinClient\EndpointTest.cs" />    
     <Compile Include="..\Apache.Ignite.Core.Tests\Client\IgniteClientConfigurationTest.cs" Link="ThinClient\IgniteClientConfigurationTest.cs" />
     <Compile Include="..\Apache.Ignite.Core.Tests\Client\RawSecureSocketTest.cs" Link="ThinClient\RawSecureSocketTest.cs" />
     <Compile Include="..\Apache.Ignite.Core.Tests\Compute\ComputeApiTest.cs" Link="Compute\ComputeApiTest.cs" />

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
index 3a4ef03..aa58afc 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
@@ -145,6 +145,7 @@
     <Compile Include="Client\Cache\SqlQueryTest.cs" />
     <Compile Include="Client\Cache\SqlQueryTestBase.cs" />
     <Compile Include="Client\ClientTestBase.cs" />
+    <Compile Include="Client\EndpointTest.cs" />
     <Compile Include="Client\RawSecureSocketTest.cs" />
     <Compile Include="Client\RawSocketTest.cs" />
     <Compile Include="Client\ClientConnectionTest.cs" />

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTestNoMeta.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTestNoMeta.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTestNoMeta.cs
index 0a8b146..1978243 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTestNoMeta.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTestNoMeta.cs
@@ -46,7 +46,7 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
                 {
                     CompactFooter = false
                 },
-                Host = IPAddress.Loopback.ToString()
+                Endpoints = new[] {IPAddress.Loopback.ToString()}
             };
 
             using (var client = Ignition.StartClient(cfg))
@@ -126,4 +126,4 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
             }
         }
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTestSsl.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTestSsl.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTestSsl.cs
index 95d45af..0f55ce5 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTestSsl.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTestSsl.cs
@@ -18,6 +18,7 @@
 namespace Apache.Ignite.Core.Tests.Client.Cache
 {
     using System.IO;
+    using System.Net;
     using System.Security.Authentication;
     using Apache.Ignite.Core.Client;
     using NUnit.Framework;
@@ -46,7 +47,7 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
         {
             return new IgniteClientConfiguration(base.GetClientConfiguration())
             {
-                Port = 11110,
+                Endpoints = new[] {IPAddress.Loopback + ":11110"},
                 SslStreamFactory = new SslStreamFactory
                 {
                     CertificatePath = Path.Combine("Config", "Client", "thin-client-cert.pfx"),
@@ -57,7 +58,7 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
                     SslProtocols = SslProtocols.Tls
 #else
                     SslProtocols = SslProtocols.Tls12
-#endif                
+#endif
                 }
             };
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientConnectionTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientConnectionTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientConnectionTest.cs
index 14d1abf..0c202bc 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientConnectionTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientConnectionTest.cs
@@ -18,7 +18,6 @@
 namespace Apache.Ignite.Core.Tests.Client
 {
     using System;
-    using System.Collections.Generic;
     using System.IO;
     using System.Linq;
     using System.Net;
@@ -53,7 +52,7 @@ namespace Apache.Ignite.Core.Tests.Client
         }
 
         /// <summary>
-        /// Fixture tear down.
+        /// Test tear down.
         /// </summary>
         [TearDown]
         public void TearDown()
@@ -87,7 +86,7 @@ namespace Apache.Ignite.Core.Tests.Client
         {
             using (Ignition.Start(SecureServerConfig()))
             {
-                var cliCfg = SecureClientConfig();
+                var cliCfg = GetSecureClientConfig();
 
                 cliCfg.Password = null;
                 var ex = Assert.Throws<IgniteClientException>(() => { Ignition.StartClient(cliCfg); });
@@ -117,7 +116,7 @@ namespace Apache.Ignite.Core.Tests.Client
         {
             using (Ignition.Start(SecureServerConfig()))
             {
-                var cliCfg = SecureClientConfig();
+                var cliCfg = GetSecureClientConfig();
 
                 cliCfg.UserName = "invalid";
 
@@ -142,9 +141,9 @@ namespace Apache.Ignite.Core.Tests.Client
             {
                 srv.GetCluster().SetActive(true);
 
-                using (var cli = Ignition.StartClient(SecureClientConfig()))
+                using (var cli = Ignition.StartClient(GetSecureClientConfig()))
                 {
-                    CacheClientConfiguration ccfg = new CacheClientConfiguration()
+                    CacheClientConfiguration ccfg = new CacheClientConfiguration
                     {
                         Name = "TestCache",
                         QueryEntities = new[]
@@ -164,7 +163,7 @@ namespace Apache.Ignite.Core.Tests.Client
                     cache.Query(new SqlFieldsQuery("CREATE USER \"my_User\" WITH PASSWORD 'my_Password'")).GetAll();
                 }
 
-                var cliCfg = SecureClientConfig();
+                var cliCfg = GetSecureClientConfig();
 
                 cliCfg.UserName = "my_User";
                 cliCfg.Password = "my_Password";
@@ -219,8 +218,7 @@ namespace Apache.Ignite.Core.Tests.Client
 
             var clientCfg = new IgniteClientConfiguration
             {
-                Host = "localhost",
-                Port = 2000
+                Endpoints = new[] {"localhost:2000"}
             };
 
             using (Ignition.Start(servCfg))
@@ -233,12 +231,48 @@ namespace Apache.Ignite.Core.Tests.Client
         }
 
         /// <summary>
+        /// Tests client config with EndPoints property.
+        /// </summary>
+        [Test]
+        public void TestEndPoints()
+        {
+            using (var ignite = Ignition.Start(TestUtils.GetTestConfiguration()))
+            {
+                ignite.CreateCache<int, int>("foo");
+
+                const int port = IgniteClientConfiguration.DefaultPort;
+
+                // DnsEndPoint.
+                var cfg = new IgniteClientConfiguration
+                {
+                    Endpoints = new[] { "localhost" }
+                };
+
+                using (var client = Ignition.StartClient(cfg))
+                {
+                    Assert.AreEqual("foo", client.GetCacheNames().Single());
+                }
+
+                // IPEndPoint.
+                cfg = new IgniteClientConfiguration
+                {
+                    Endpoints = new[] { "127.0.0.1:" + port }
+                };
+
+                using (var client = Ignition.StartClient(cfg))
+                {
+                    Assert.AreEqual("foo", client.GetCacheNames().Single());
+                }
+            }
+        }
+
+        /// <summary>
         /// Tests that default configuration throws.
         /// </summary>
         [Test]
         public void TestDefaultConfigThrows()
         {
-            Assert.Throws<ArgumentNullException>(() => Ignition.StartClient(new IgniteClientConfiguration()));
+            Assert.Throws<IgniteClientException>(() => Ignition.StartClient(new IgniteClientConfiguration()));
         }
 
         /// <summary>
@@ -253,7 +287,13 @@ namespace Apache.Ignite.Core.Tests.Client
                 // ReSharper disable once ObjectCreationAsStatement
                 var ex = Assert.Throws<IgniteClientException>(() =>
                     new ClientSocket(GetClientConfiguration(),
-                    new ClientProtocolVersion(-1, -1, -1)));
+                        new DnsEndPoint(
+                            "localhost",
+                            ClientConnectorConfiguration.DefaultPort,
+                            AddressFamily.InterNetwork),
+                        null,
+                        null,
+                        new ClientProtocolVersion(-1, -1, -1)));
 
                 Assert.AreEqual(ClientStatusCode.Fail, ex.StatusCode);
 
@@ -275,7 +315,7 @@ namespace Apache.Ignite.Core.Tests.Client
 
             var clientCfg = new IgniteClientConfiguration
             {
-                Host = "localhost"
+                Endpoints = new[] {"localhost"}
             };
 
             using (Ignition.Start(servCfg))
@@ -298,7 +338,7 @@ namespace Apache.Ignite.Core.Tests.Client
             {
                 var ex = Assert.Throws<IgniteClientException>(() => Ignition.StartClient(clientCfg));
                 Assert.AreEqual("Client handshake failed: 'Thin client connection is not allowed, " +
-                                "see ClientConnectorConfiguration.thinClientEnabled.'.", 
+                                "see ClientConnectorConfiguration.thinClientEnabled.'.",
                                 ex.Message.Substring(0, 118));
             }
         }
@@ -385,16 +425,17 @@ namespace Apache.Ignite.Core.Tests.Client
         {
             Ignition.Start(TestUtils.GetTestConfiguration());
 
-            var ops = new List<Task>();
+            const int count = 100000;
+            var ops = new Task[count];
 
             using (var client = StartClient())
             {
                 var cache = client.GetOrCreateCache<int, int>("foo");
-                for (var i = 0; i < 100000; i++)
-                {
-                    ops.Add(cache.PutAsync(i, i));
-                }
-                ops.First().Wait();
+                Parallel.For(0, count, new ParallelOptions {MaxDegreeOfParallelism = 16},
+                    i =>
+                    {
+                        ops[i] = cache.PutAsync(i, i);
+                    });
             }
 
             var completed = ops.Count(x => x.Status == TaskStatus.RanToCompletion);
@@ -408,7 +449,7 @@ namespace Apache.Ignite.Core.Tests.Client
                 var ex = task.Exception;
                 Assert.IsNotNull(ex);
                 var baseEx = ex.GetBaseException();
-                Assert.IsNotNull((object) (baseEx as SocketException) ?? baseEx as ObjectDisposedException, 
+                Assert.IsNotNull((object) (baseEx as SocketException) ?? baseEx as ObjectDisposedException,
                     ex.ToString());
             }
         }
@@ -436,10 +477,10 @@ namespace Apache.Ignite.Core.Tests.Client
                 var cache = client.GetOrCreateCache<int, int>("foo");
                 cache[1] = 1;
                 Assert.AreEqual(1, cache[1]);
-                
+
                 Thread.Sleep(90);
                 Assert.AreEqual(1, cache[1]);
-                
+
                 // Idle check frequency is 2 seconds.
                 Thread.Sleep(4000);
                 var ex = Assert.Catch(() => cache.Get(1));
@@ -456,13 +497,133 @@ namespace Apache.Ignite.Core.Tests.Client
             using (Ignition.Start(TestUtils.GetTestConfiguration()))
             {
                 // Connect to Ignite REST endpoint.
-                var cfg = new IgniteClientConfiguration {Host = "127.0.0.1", Port = 11211 };
-                var ex = Assert.Throws<SocketException>(() => Ignition.StartClient(cfg));
+                var cfg = new IgniteClientConfiguration("127.0.0.1:11211");
+                var ex = GetSocketException(Assert.Catch(() => Ignition.StartClient(cfg)));
                 Assert.AreEqual(SocketError.ConnectionAborted, ex.SocketErrorCode);
             }
         }
 
         /// <summary>
+        /// Tests reconnect logic with single server.
+        /// </summary>
+        [Test]
+        public void TestReconnect()
+        {
+            // Connect client and check.
+            Ignition.Start(TestUtils.GetTestConfiguration());
+            var client = Ignition.StartClient(new IgniteClientConfiguration("127.0.0.1"));
+            Assert.AreEqual(0, client.GetCacheNames().Count);
+
+            var ep = client.RemoteEndPoint as IPEndPoint;
+            Assert.IsNotNull(ep);
+            Assert.AreEqual(IgniteClientConfiguration.DefaultPort, ep.Port);
+            Assert.AreEqual("127.0.0.1", ep.Address.ToString());
+
+            ep = client.LocalEndPoint as IPEndPoint;
+            Assert.IsNotNull(ep);
+            Assert.AreNotEqual(IgniteClientConfiguration.DefaultPort, ep.Port);
+            Assert.AreEqual("127.0.0.1", ep.Address.ToString());
+
+            // Stop server.
+            Ignition.StopAll(true);
+
+            // First request fails, error is detected.
+            var ex = Assert.Catch(() => client.GetCacheNames());
+            Assert.IsNotNull(GetSocketException(ex));
+
+            // Second request causes reconnect attempt which fails (server is stopped).
+            var aex = Assert.Throws<AggregateException>(() => client.GetCacheNames());
+            Assert.AreEqual("Failed to establish Ignite thin client connection, " +
+                            "examine inner exceptions for details.", aex.Message.Substring(0, 88));
+
+            // Start server, next operation succeeds.
+            Ignition.Start(TestUtils.GetTestConfiguration());
+            Assert.AreEqual(0, client.GetCacheNames().Count);
+        }
+
+        /// <summary>
+        /// Tests disabled reconnect behavior.
+        /// </summary>
+        [Test]
+        public void TestReconnectDisabled()
+        {
+            // Connect client and check.
+            Ignition.Start(TestUtils.GetTestConfiguration());
+            using (var client = Ignition.StartClient(new IgniteClientConfiguration("127.0.0.1")
+            {
+                ReconnectDisabled = true
+            }))
+            {
+                Assert.AreEqual(0, client.GetCacheNames().Count);
+
+                // Stop server.
+                Ignition.StopAll(true);
+
+                // Request fails, error is detected.
+                var ex = Assert.Catch(() => client.GetCacheNames());
+                Assert.IsNotNull(GetSocketException(ex));
+
+                // Restart server, client does not reconnect.
+                Ignition.Start(TestUtils.GetTestConfiguration());
+                ex = Assert.Catch(() => client.GetCacheNames());
+                Assert.IsNotNull(GetSocketException(ex));
+            }
+        }
+
+        /// <summary>
+        /// Tests reconnect logic with multiple servers.
+        /// </summary>
+        [Test]
+        public void TestFailover()
+        {
+            // Start 3 nodes.
+            Ignition.Start(TestUtils.GetTestConfiguration(name: "0"));
+            Ignition.Start(TestUtils.GetTestConfiguration(name: "1"));
+            Ignition.Start(TestUtils.GetTestConfiguration(name: "2"));
+
+            // Connect client.
+            var port = IgniteClientConfiguration.DefaultPort;
+            var cfg = new IgniteClientConfiguration
+            {
+                Endpoints = new[]
+                {
+                    "localhost",
+                    string.Format("127.0.0.1:{0}..{1}", port + 1, port + 2)
+                }
+            };
+
+            using (var client = Ignition.StartClient(cfg))
+            {
+                Assert.AreEqual(0, client.GetCacheNames().Count);
+
+                // Stop target node.
+                var nodeId = ((IPEndPoint) client.RemoteEndPoint).Port - port;
+                Ignition.Stop(nodeId.ToString(), true);
+
+                // Check failure.
+                Assert.IsNotNull(GetSocketException(Assert.Catch(() => client.GetCacheNames())));
+
+                // Check reconnect.
+                Assert.AreEqual(0, client.GetCacheNames().Count);
+
+                // Stop target node.
+                nodeId = ((IPEndPoint) client.RemoteEndPoint).Port - port;
+                Ignition.Stop(nodeId.ToString(), true);
+
+                // Check failure.
+                Assert.IsNotNull(GetSocketException(Assert.Catch(() => client.GetCacheNames())));
+
+                // Check reconnect.
+                Assert.AreEqual(0, client.GetCacheNames().Count);
+
+                // Stop all nodes.
+                Ignition.StopAll(true);
+                Assert.IsNotNull(GetSocketException(Assert.Catch(() => client.GetCacheNames())));
+                Assert.IsNotNull(GetSocketException(Assert.Catch(() => client.GetCacheNames())));
+            }
+        }
+
+        /// <summary>
         /// Starts the client.
         /// </summary>
         private static IIgniteClient StartClient()
@@ -475,7 +636,7 @@ namespace Apache.Ignite.Core.Tests.Client
         /// </summary>
         private static IgniteClientConfiguration GetClientConfiguration()
         {
-            return new IgniteClientConfiguration { Host = IPAddress.Loopback.ToString() };
+            return new IgniteClientConfiguration(IPAddress.Loopback.ToString());
         }
 
         /// <summary>
@@ -497,7 +658,7 @@ namespace Apache.Ignite.Core.Tests.Client
 
                 ex = ex.InnerException;
             }
-            
+
             throw new Exception("SocketException not found.", origEx);
         }
 
@@ -510,12 +671,12 @@ namespace Apache.Ignite.Core.Tests.Client
             return new IgniteConfiguration(TestUtils.GetTestConfiguration())
             {
                 AuthenticationEnabled = true,
-                DataStorageConfiguration = new DataStorageConfiguration()
+                DataStorageConfiguration = new DataStorageConfiguration
                 {
                     StoragePath = Path.Combine(_tempDir, "Store"),
                     WalPath = Path.Combine(_tempDir, "WalStore"),
                     WalArchivePath = Path.Combine(_tempDir, "WalArchive"),
-                    DefaultDataRegionConfiguration = new DataRegionConfiguration()
+                    DefaultDataRegionConfiguration = new DataRegionConfiguration
                     {
                         Name = "default",
                         PersistenceEnabled = true
@@ -528,11 +689,10 @@ namespace Apache.Ignite.Core.Tests.Client
         /// Create client configuration with enabled authentication.
         /// </summary>
         /// <returns>Client configuration.</returns>
-        private static IgniteClientConfiguration SecureClientConfig()
+        private static IgniteClientConfiguration GetSecureClientConfig()
         {
-            return new IgniteClientConfiguration()
+            return new IgniteClientConfiguration("localhost")
             {
-                Host = "localhost",
                 UserName = "ignite",
                 Password = "ignite"
             };

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientTestBase.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientTestBase.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientTestBase.cs
index db5e621..c6aee32 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientTestBase.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientTestBase.cs
@@ -91,7 +91,7 @@ namespace Apache.Ignite.Core.Tests.Client
             var cache = GetCache<int>();
             cache.RemoveAll();
             cache.Clear();
-            
+
             Assert.AreEqual(0, cache.GetSize(CachePeekMode.All));
             Assert.AreEqual(0, GetClientCache<int>().GetSize(CachePeekMode.All));
         }
@@ -140,7 +140,7 @@ namespace Apache.Ignite.Core.Tests.Client
         {
             return new IgniteClientConfiguration
             {
-                Host = IPAddress.Loopback.ToString()
+                Endpoints = new[] {IPAddress.Loopback.ToString()}
             };
         }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/EndpointTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/EndpointTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/EndpointTest.cs
new file mode 100644
index 0000000..05366ed
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/EndpointTest.cs
@@ -0,0 +1,96 @@
+namespace Apache.Ignite.Core.Tests.Client
+{
+    using System.Linq;
+    using Apache.Ignite.Core.Client;
+    using Apache.Ignite.Core.Impl.Client;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests for <see cref="Endpoint"/> class.
+    /// </summary>
+    public class EndpointTest
+    {
+        [Test]
+        public void GetEndpoints_InvalidConfigFormat_ThrowsIgniteClientException()
+        {
+            var ex = AssertThrowsClientException("");
+            Assert.AreEqual("IgniteClientConfiguration.Endpoints[...] can't be null or whitespace.", ex.Message);
+
+            ex = AssertThrowsClientException("host:");
+            Assert.AreEqual(
+                "Unrecognized format of IgniteClientConfiguration.Endpoint, failed to parse port: host:",
+                ex.Message);
+
+            ex = AssertThrowsClientException("host:port");
+            Assert.AreEqual(
+                "Unrecognized format of IgniteClientConfiguration.Endpoint, failed to parse port: host:port",
+                ex.Message);
+
+            ex = AssertThrowsClientException("host:1..");
+            Assert.AreEqual(
+                "Unrecognized format of IgniteClientConfiguration.Endpoint, failed to parse port: host:1..",
+                ex.Message);
+
+            ex = AssertThrowsClientException("host:1..2..3");
+            Assert.AreEqual(
+                "Unrecognized format of IgniteClientConfiguration.Endpoint: host:1..2..3",
+                ex.Message);
+        }
+
+        [Test]
+        public void GetEndpoints_ParsesPortsAndRanges()
+        {
+            const string ip = "1.2.3.4";
+            const string host = "example.com";
+            const int port = 678;
+            const int port2 = 680;
+
+            var ipWithDefaultPort = Endpoint.GetEndpoints(new IgniteClientConfiguration(ip)).Single();
+            Assert.AreEqual(ip, ipWithDefaultPort.Host);
+            Assert.AreEqual(IgniteClientConfiguration.DefaultPort, ipWithDefaultPort.Port);
+            Assert.AreEqual(0, ipWithDefaultPort.PortRange);
+
+            var ipWithCustomPort = Endpoint
+                .GetEndpoints(new IgniteClientConfiguration(string.Format("{0}:{1}", ip, port)))
+                .Single();
+            Assert.AreEqual(ip, ipWithCustomPort.Host);
+            Assert.AreEqual(port, ipWithCustomPort.Port);
+            Assert.AreEqual(0, ipWithCustomPort.PortRange);
+
+            var ipWithPortRange = Endpoint
+                .GetEndpoints(new IgniteClientConfiguration(string.Format("{0}:{1}..{2}", ip, port, port2)))
+                .Single();
+            Assert.AreEqual(ip, ipWithPortRange.Host);
+            Assert.AreEqual(port, ipWithPortRange.Port);
+            Assert.AreEqual(port2 - port, ipWithPortRange.PortRange);
+
+            var hostWithDefaultPort = Endpoint.GetEndpoints(new IgniteClientConfiguration(host)).Single();
+            Assert.AreEqual(host, hostWithDefaultPort.Host);
+            Assert.AreEqual(IgniteClientConfiguration.DefaultPort, hostWithDefaultPort.Port);
+            Assert.AreEqual(0, hostWithDefaultPort.PortRange);
+
+            var hostWithCustomPort = Endpoint
+                .GetEndpoints(new IgniteClientConfiguration(string.Format("{0}:{1}", host, port)))
+                .Single();
+            Assert.AreEqual(host, hostWithCustomPort.Host);
+            Assert.AreEqual(port, hostWithCustomPort.Port);
+            Assert.AreEqual(0, hostWithCustomPort.PortRange);
+
+            var hostWithPortRange = Endpoint
+                .GetEndpoints(new IgniteClientConfiguration(string.Format("{0}:{1}..{2}", host, port, port2)))
+                .Single();
+            Assert.AreEqual(host, hostWithPortRange.Host);
+            Assert.AreEqual(port, hostWithPortRange.Port);
+            Assert.AreEqual(port2 - port, hostWithPortRange.PortRange);
+
+        }
+
+        private static IgniteClientException AssertThrowsClientException(string endpoint)
+        {
+            var endpoints = Endpoint.GetEndpoints(new IgniteClientConfiguration(endpoint));
+
+            // ReSharper disable once ReturnValueOfPureMethodIsNotUsed
+            return Assert.Throws<IgniteClientException>(() => endpoints.ToList());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/IgniteClientConfigurationTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/IgniteClientConfigurationTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/IgniteClientConfigurationTest.cs
index 09decc6..3d55f4c 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/IgniteClientConfigurationTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/IgniteClientConfigurationTest.cs
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 
+#pragma warning disable 618
 namespace Apache.Ignite.Core.Tests.Client
 {
     using System;
@@ -84,6 +85,12 @@ namespace Apache.Ignite.Core.Tests.Client
                     CheckCertificateRevocation = true,
                     SkipServerCertificateValidation = true,
                     SslProtocols = SslProtocols.None
+                },
+                Endpoints = new []
+                {
+                    "foo",
+                    "bar:123",
+                    "baz:100..103"
                 }
             };
 
@@ -177,7 +184,7 @@ namespace Apache.Ignite.Core.Tests.Client
                 }
 
                 // Missing section content.
-                ex = Assert.Throws<ConfigurationErrorsException>(() => 
+                ex = Assert.Throws<ConfigurationErrorsException>(() =>
                     Ignition.StartClient("igniteClientConfiguration3"));
                 Assert.AreEqual("IgniteClientConfigurationSection with name 'igniteClientConfiguration3' is " +
                                 "defined in <configSections>, but not present in configuration.", ex.Message);
@@ -210,7 +217,8 @@ namespace Apache.Ignite.Core.Tests.Client
         public void TestAllPropertiesArePresentInSchema()
         {
             IgniteConfigurationSerializerTest.CheckAllPropertiesArePresentInSchema(
-                "IgniteClientConfigurationSection.xsd", "igniteClientConfiguration", typeof(IgniteClientConfiguration));
+                "IgniteClientConfigurationSection.xsd", "igniteClientConfiguration",
+                typeof(IgniteClientConfiguration));
         }
 #endif
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/RawSecureSocketTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/RawSecureSocketTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/RawSecureSocketTest.cs
index 11b4e86..379e7e3 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/RawSecureSocketTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/RawSecureSocketTest.cs
@@ -23,7 +23,6 @@ namespace Apache.Ignite.Core.Tests.Client
     using System.Net.Sockets;
     using System.Security.Authentication;
     using System.Security.Cryptography.X509Certificates;
-    using Apache.Ignite.Core.Client;
     using Apache.Ignite.Core.Impl.Binary.IO;
     using NUnit.Framework;
 
@@ -38,25 +37,22 @@ namespace Apache.Ignite.Core.Tests.Client
         [Test]
         public void TestHandshake()
         {
-            var icfg = new IgniteConfiguration(TestUtils.GetTestConfiguration())
+            var igniteConfiguration = new IgniteConfiguration(TestUtils.GetTestConfiguration())
             {
                 SpringConfigUrl = Path.Combine("Config", "Client", "server-with-ssl.xml")
             };
 
-            using (Ignition.Start(icfg))
+            using (Ignition.Start(igniteConfiguration))
             {
-                var cfg = new IgniteClientConfiguration
-                {
-                    Host = "127.0.0.1",
-                    Port = 11110
-                };
+                const string host = "127.0.0.1";
+                const int port = 11110;
 
-                using (var client = new TcpClient(cfg.Host, cfg.Port))
+                using (var client = new TcpClient(host, port))
                 using (var sslStream = new SslStream(client.GetStream(), false, ValidateServerCertificate, null))
                 {
                     var certsCollection = new X509CertificateCollection(new X509Certificate[] {LoadCertificateFile()});
 
-                    sslStream.AuthenticateAsClient(cfg.Host, certsCollection, SslProtocols.Tls, false);
+                    sslStream.AuthenticateAsClient(host, certsCollection, SslProtocols.Tls, false);
 
                     Assert.IsTrue(sslStream.IsAuthenticated);
                     Assert.IsTrue(sslStream.IsMutuallyAuthenticated);
@@ -87,7 +83,7 @@ namespace Apache.Ignite.Core.Tests.Client
         /// </summary>
         private static X509Certificate2 LoadCertificateFile()
         {
-            // Conveting from JKS to PFX:
+            // Converting from JKS to PFX:
             // keytool -importkeystore -srckeystore thekeystore.jks -srcstoretype JKS
             // -destkeystore thekeystore.pfx -deststoretype PKCS12
             return new X509Certificate2(Path.Combine("Config", "Client", "thin-client-cert.pfx"), "123456");

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/Client/IgniteClientConfiguration.xml
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/Client/IgniteClientConfiguration.xml b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/Client/IgniteClientConfiguration.xml
index 8d8d9bf..89f9a69 100644
Binary files a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/Client/IgniteClientConfiguration.xml and b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/Client/IgniteClientConfiguration.xml differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Dataload/DataStreamerTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Dataload/DataStreamerTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Dataload/DataStreamerTest.cs
index f1a25c6..3e69d20 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Dataload/DataStreamerTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Dataload/DataStreamerTest.cs
@@ -115,7 +115,7 @@ namespace Apache.Ignite.Core.Tests.Dataload
                 Assert.AreEqual(1, ldr.PerNodeBufferSize);
                 ldr.PerNodeBufferSize = 2;
                 Assert.AreEqual(2, ldr.PerNodeBufferSize);
-                
+
                 Assert.AreEqual(DataStreamerDefaults.DefaultPerThreadBufferSize, ldr.PerThreadBufferSize);
                 ldr.PerThreadBufferSize = 1;
                 Assert.AreEqual(1, ldr.PerThreadBufferSize);
@@ -141,7 +141,7 @@ namespace Apache.Ignite.Core.Tests.Dataload
         /// <summary>
         /// Test data add/remove.
         /// </summary>
-        [Test]        
+        [Test]
         public void TestAddRemove()
         {
             IDataStreamer<int, int> ldr;
@@ -154,7 +154,7 @@ namespace Apache.Ignite.Core.Tests.Dataload
 
                 // Additions.
                 var task = ldr.AddData(1, 1);
-                ldr.Flush();                
+                ldr.Flush();
                 Assert.AreEqual(1, _cache.Get(1));
                 Assert.IsTrue(task.IsCompleted);
                 Assert.IsFalse(ldr.Task.IsCompleted);
@@ -177,7 +177,7 @@ namespace Apache.Ignite.Core.Tests.Dataload
                 Assert.IsTrue(task.IsCompleted);
 
                 // Mixed.
-                ldr.AddData(5, 5);                
+                ldr.AddData(5, 5);
                 ldr.RemoveData(2);
                 ldr.AddData(new KeyValuePair<int, int>(7, 7));
                 ldr.AddData(6, 6);
@@ -234,7 +234,7 @@ namespace Apache.Ignite.Core.Tests.Dataload
             Assert.IsNotNull(cache[2].Inner.Inner);
             Assert.IsNotNull(cache[3].Inner);
             Assert.IsNotNull(cache[3].Inner.Inner);
-            
+
             Assert.IsNotNull(cache[4].Inner);
             Assert.IsNull(cache[4].Inner.Inner);
         }
@@ -310,7 +310,7 @@ namespace Apache.Ignite.Core.Tests.Dataload
         private static int[] GetPrimaryPartitionKeys(IIgnite ignite, int count)
         {
             var affinity = ignite.GetAffinity(CacheName);
-            
+
             var localNode = ignite.GetCluster().GetLocalNode();
 
             var part = affinity.GetPrimaryPartitions(localNode).First();
@@ -361,10 +361,9 @@ namespace Apache.Ignite.Core.Tests.Dataload
         /// Tests that streamer gets collected when there are no references to it.
         /// </summary>
         [Test]
+        [Ignore("IGNITE-8731")]
         public void TestFinalizer()
         {
-            Assert.Fail("https://issues.apache.org/jira/browse/IGNITE-8731");
-
             var streamer = _grid.GetDataStreamer<int, int>(CacheName);
             var streamerRef = new WeakReference(streamer);
 
@@ -391,7 +390,7 @@ namespace Apache.Ignite.Core.Tests.Dataload
                 var fut = ldr.AddData(1, 1);
                 Thread.Sleep(100);
                 Assert.IsFalse(fut.IsCompleted);
-                ldr.AutoFlushFrequency = 1000;                
+                ldr.AutoFlushFrequency = 1000;
                 fut.Wait();
 
                 // Test forced flush after frequency change.
@@ -423,7 +422,7 @@ namespace Apache.Ignite.Core.Tests.Dataload
         }
 
         /// <summary>
-        /// Test multithreaded behavior. 
+        /// Test multithreaded behavior.
         /// </summary>
         [Test]
         [Category(TestUtils.CategoryIntensive)]
@@ -619,7 +618,7 @@ namespace Apache.Ignite.Core.Tests.Dataload
             public int Process(IMutableCacheEntry<int, int> entry, int arg)
             {
                 entry.Value = entry.Key + 1;
-                
+
                 return 0;
             }
         }
@@ -633,7 +632,7 @@ namespace Apache.Ignite.Core.Tests.Dataload
             public int Process(IMutableCacheEntry<int, int> entry, int arg)
             {
                 entry.Value = entry.Key + 1;
-                
+
                 return 0;
             }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ExceptionsTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ExceptionsTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ExceptionsTest.cs
index 0b06ea3..5e84823 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ExceptionsTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ExceptionsTest.cs
@@ -16,14 +16,13 @@
  */
 
 #pragma warning disable 618
-namespace Apache.Ignite.Core.Tests 
+namespace Apache.Ignite.Core.Tests
 {
     using System;
     using System.IO;
     using System.Linq;
     using System.Runtime.Serialization.Formatters.Binary;
     using System.Threading;
-    using System.Threading.Tasks;
     using Apache.Ignite.Core.Binary;
     using Apache.Ignite.Core.Cache;
     using Apache.Ignite.Core.Cluster;

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core.Tests/custom_app.config
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/custom_app.config b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/custom_app.config
index b79279ae..9fa96f5 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/custom_app.config
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/custom_app.config
@@ -36,5 +36,9 @@
         </discoverySpi>
     </igniteConfiguration3>
 
-    <igniteClientConfiguration host="127.0.0.1" socketSendBufferSize="512" />
+    <igniteClientConfiguration socketSendBufferSize="512">
+        <endpoints>
+            <string>127.0.0.1</string>
+        </endpoints>
+    </igniteClientConfiguration>
 </configuration>

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
index 273e088..57357da 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
@@ -78,6 +78,7 @@
     <Compile Include="Failure\StopNodeFailureHandler.cs" />
     <Compile Include="Failure\StopNodeOrHaltFailureHandler.cs" />
     <Compile Include="Impl\Cache\QueryMetricsImpl.cs" />
+    <Compile Include="Impl\Client\Endpoint.cs" />
     <Compile Include="Impl\Common\TaskRunner.cs" />
     <Compile Include="Impl\Transactions\TransactionCollectionImpl.cs" />
     <Compile Include="Ssl\ISslContextFactory.cs" />
@@ -103,6 +104,8 @@
     <Compile Include="Impl\Client\Cache\Query\StatementType.cs" />
     <Compile Include="Client\ClientStatusCode.cs" />
     <Compile Include="Events\LocalEventListener.cs" />
+    <Compile Include="Impl\Client\ClientFailoverSocket.cs" />
+    <Compile Include="Impl\Client\IClientSocket.cs" />
     <Compile Include="Impl\Cluster\BaselineNode.cs" />
     <Compile Include="Ssl\SslContextFactory.cs" />
     <Compile Include="Impl\Ssl\SslFactorySerializer.cs" />
@@ -597,4 +600,4 @@
   <Target Name="AfterBuild">
   </Target>
   -->
-</Project>
+</Project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core/Client/IIgniteClient.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Client/IIgniteClient.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Client/IIgniteClient.cs
index 2b24aa4..813634b 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Client/IIgniteClient.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Client/IIgniteClient.cs
@@ -20,6 +20,7 @@ namespace Apache.Ignite.Core.Client
     using System;
     using System.Collections.Generic;
     using System.Diagnostics.CodeAnalysis;
+    using System.Net;
     using Apache.Ignite.Core.Binary;
     using Apache.Ignite.Core.Client.Cache;
 
@@ -108,5 +109,19 @@ namespace Apache.Ignite.Core.Client
         /// </summary>
         [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Semantics.")]
         IgniteClientConfiguration GetConfiguration();
+
+        /// <summary>
+        /// Gets the current remote EndPoint.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly",
+            Justification = "Consistency with EndPoint class name.")]
+        EndPoint RemoteEndPoint { get; }
+
+        /// <summary>
+        /// Gets the current local EndPoint.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly",
+            Justification = "Consistency with EndPoint class name.")]
+        EndPoint LocalEndPoint { get; }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core/Client/IgniteClientConfiguration.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Client/IgniteClientConfiguration.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Client/IgniteClientConfiguration.cs
index 80f26cf..311dfbe 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Client/IgniteClientConfiguration.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Client/IgniteClientConfiguration.cs
@@ -18,7 +18,10 @@
 namespace Apache.Ignite.Core.Client
 {
     using System;
+    using System.Collections.Generic;
     using System.ComponentModel;
+    using System.Diagnostics.CodeAnalysis;
+    using System.Linq;
     using System.Xml;
     using Apache.Ignite.Core.Binary;
     using Apache.Ignite.Core.Impl.Binary;
@@ -58,7 +61,9 @@ namespace Apache.Ignite.Core.Client
         /// </summary>
         public IgniteClientConfiguration()
         {
+#pragma warning disable 618
             Port = DefaultPort;
+#pragma warning restore 618
             SocketSendBufferSize = DefaultSocketBufferSize;
             SocketReceiveBufferSize = DefaultSocketBufferSize;
             TcpNoDelay = DefaultTcpNoDelay;
@@ -66,6 +71,17 @@ namespace Apache.Ignite.Core.Client
         }
 
         /// <summary>
+        /// Initializes a new instance of the <see cref="IgniteClientConfiguration" /> class.
+        /// </summary>
+        /// <param name="host">The host to connect to.</param>
+        public IgniteClientConfiguration(string host) : this()
+        {
+            IgniteArgumentCheck.NotNull(host, "host");
+
+            Endpoints = new List<string> {host};
+        }
+
+        /// <summary>
         /// Initializes a new instance of the <see cref="IgniteClientConfiguration"/> class.
         /// </summary>
         /// <param name="cfg">The configuration to copy.</param>
@@ -76,8 +92,10 @@ namespace Apache.Ignite.Core.Client
                 return;
             }
 
+#pragma warning disable 618
             Host = cfg.Host;
             Port = cfg.Port;
+#pragma warning restore 618
             SocketSendBufferSize = cfg.SocketSendBufferSize;
             SocketReceiveBufferSize = cfg.SocketReceiveBufferSize;
             TcpNoDelay = cfg.TcpNoDelay;
@@ -93,20 +111,46 @@ namespace Apache.Ignite.Core.Client
 
             UserName = cfg.UserName;
             Password = cfg.Password;
+            Endpoints = cfg.Endpoints == null ? null : cfg.Endpoints.ToList();
+            ReconnectDisabled = cfg.ReconnectDisabled;
         }
 
         /// <summary>
         /// Gets or sets the host. Should not be null.
         /// </summary>
+        [Obsolete("Use Endpoints instead")]
         public string Host { get; set; }
 
         /// <summary>
         /// Gets or sets the port.
         /// </summary>
         [DefaultValue(DefaultPort)]
+        [Obsolete("Use Endpoints instead")]
         public int Port { get; set; }
 
         /// <summary>
+        /// Gets or sets endpoints to connect to.
+        /// Examples of supported formats:
+        ///  * 192.168.1.25 (default port is used, see <see cref="DefaultPort"/>).
+        ///  * 192.168.1.25:780 (custom port)
+        ///  * 192.168.1.25:780-787 (custom port range)
+        ///  * my-host.com (default port is used, see <see cref="DefaultPort"/>).
+        ///  * my-host.com:780 (custom port)
+        ///  * my-host.com:780-787 (custom port range)
+        /// <para />
+        /// When multiple endpoints are specified, failover and load-balancing mechanism is enabled:
+        /// * Ignite picks random endpoint and connects to it.
+        /// * On disconnect, next endpoint is picked from the list (.
+        /// </summary>
+        [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
+        public ICollection<string> Endpoints { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether automatic reconnect is disabled.
+        /// </summary>
+        public bool ReconnectDisabled { get; set; }
+
+        /// <summary>
         /// Gets or sets the size of the socket send buffer. When set to 0, operating system default is used.
         /// </summary>
         [DefaultValue(DefaultSocketBufferSize)]

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core/IgniteClientConfigurationSection.xsd
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteClientConfigurationSection.xsd b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteClientConfigurationSection.xsd
index b9a04b8..d7ebc12 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteClientConfigurationSection.xsd
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteClientConfigurationSection.xsd
@@ -30,7 +30,7 @@
         </xs:annotation>
         <xs:complexType>
             <xs:all>
-                <xs:element name="binaryConfiguration" minOccurs="0">
+                <xs:element name="binaryConfiguration" minOccurs="0" maxOccurs="1">
                     <xs:annotation>
                         <xs:documentation>Binary configuration.</xs:documentation>
                     </xs:annotation>
@@ -172,6 +172,16 @@
                         </xs:attribute>
                     </xs:complexType>
                 </xs:element>
+                <xs:element name="endpoints" minOccurs="0" maxOccurs="1">
+                    <xs:annotation>
+                        <xs:documentation>Endpoints to connect to.</xs:documentation>
+                    </xs:annotation>
+                    <xs:complexType>
+                        <xs:sequence>
+                            <xs:element maxOccurs="unbounded" name="string" type="xs:string" />
+                        </xs:sequence>
+                    </xs:complexType>
+                </xs:element>
                 <xs:element name="sslStreamFactory">
                     <xs:complexType>
                         <xs:attribute name="type" type="xs:string" use="required">
@@ -237,6 +247,11 @@
                     <xs:documentation>Socket operation timeout. Zero or negative for infinite timeout.</xs:documentation>
                 </xs:annotation>
             </xs:attribute>
+            <xs:attribute name="reconnectDisabled" type="xs:boolean">
+                <xs:annotation>
+                    <xs:documentation>Disables automatic reconnect on network or server failure.</xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
             <xs:attribute name="userName" type="xs:string">
                 <xs:annotation>
                     <xs:documentation>Username to be used to connect to secured cluster.</xs:documentation>
@@ -249,4 +264,4 @@
             </xs:attribute>
         </xs:complexType>
     </xs:element>
-</xs:schema>
\ No newline at end of file
+</xs:schema>

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs
index 55d358a..bd53118 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs
@@ -1568,6 +1568,7 @@ namespace Apache.Ignite.Core
         /// <para/>
         /// By default schema names are case-insensitive. Use quotes to enforce case sensitivity.
         /// </summary>
+        [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
         public ICollection<string> SqlSchemas { get; set; }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs
index fe80ba1..25ce4f5 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs
@@ -774,7 +774,6 @@ namespace Apache.Ignite.Core
         public static IIgniteClient StartClient(IgniteClientConfiguration clientConfiguration)
         {
             IgniteArgumentCheck.NotNull(clientConfiguration, "clientConfiguration");
-            IgniteArgumentCheck.NotNull(clientConfiguration.Host, "clientConfiguration.Host");
 
             return new IgniteClient(clientConfiguration);
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryProcessorClient.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryProcessorClient.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryProcessorClient.cs
index febecd4..d68f66e 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryProcessorClient.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryProcessorClient.cs
@@ -32,7 +32,7 @@ namespace Apache.Ignite.Core.Impl.Binary
         private const byte DotNetPlatformId = 1;
 
         /** Socket. */
-        private readonly ClientSocket _socket;
+        private readonly IClientSocket _socket;
 
         /** Marshaller. */
         private readonly Marshaller _marsh = BinaryUtils.Marshaller;
@@ -41,7 +41,7 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// Initializes a new instance of the <see cref="BinaryProcessorClient"/> class.
         /// </summary>
         /// <param name="socket">The socket.</param>
-        public BinaryProcessorClient(ClientSocket socket)
+        public BinaryProcessorClient(IClientSocket socket)
         {
             Debug.Assert(socket != null);
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Cache/CacheClient.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Cache/CacheClient.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Cache/CacheClient.cs
index a5a9246..8cc2741 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Cache/CacheClient.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Cache/CacheClient.cs
@@ -231,7 +231,7 @@ namespace Apache.Ignite.Core.Impl.Client.Cache
         /** <inheritDoc /> */
         public IQueryCursor<T> Query<T>(SqlFieldsQuery sqlFieldsQuery, Func<IBinaryRawReader, int, T> readerFunc)
         {
-            return DoOutInOp(ClientOp.QuerySqlFields, 
+            return DoOutInOp(ClientOp.QuerySqlFields,
                 w => WriteSqlFieldsQuery(w, sqlFieldsQuery, false),
                 s => GetFieldsCursorNoColumnNames(s, readerFunc));
         }
@@ -277,7 +277,7 @@ namespace Apache.Ignite.Core.Impl.Client.Cache
         {
             IgniteArgumentCheck.NotNull(key, "key");
 
-            return DoOutInOp(ClientOp.CacheGetAndRemove, w => w.WriteObjectDetached(key), 
+            return DoOutInOp(ClientOp.CacheGetAndRemove, w => w.WriteObjectDetached(key),
                 UnmarshalCacheResult<TV>);
         }
 
@@ -514,7 +514,7 @@ namespace Apache.Ignite.Core.Impl.Client.Cache
         public CacheClientConfiguration GetConfiguration()
         {
             return DoOutInOp(ClientOp.CacheGetConfiguration, null,
-                s => new CacheClientConfiguration(s, _ignite.ServerVersion()));
+                s => new CacheClientConfiguration(s, _ignite.ServerVersion));
         }
 
         /** <inheritDoc /> */
@@ -574,7 +574,7 @@ namespace Apache.Ignite.Core.Impl.Client.Cache
         private T DoOutInOp<T>(ClientOp opId, Action<BinaryWriter> writeAction,
             Func<IBinaryStream, T> readFunc)
         {
-            return _ignite.Socket.DoOutInOp(opId, stream => WriteRequest(writeAction, stream), 
+            return _ignite.Socket.DoOutInOp(opId, stream => WriteRequest(writeAction, stream),
                 readFunc, HandleError<T>);
         }
 
@@ -584,7 +584,7 @@ namespace Apache.Ignite.Core.Impl.Client.Cache
         private Task<T> DoOutInOpAsync<T>(ClientOp opId, Action<BinaryWriter> writeAction,
             Func<IBinaryStream, T> readFunc)
         {
-            return _ignite.Socket.DoOutInOpAsync(opId, stream => WriteRequest(writeAction, stream), 
+            return _ignite.Socket.DoOutInOpAsync(opId, stream => WriteRequest(writeAction, stream),
                 readFunc, HandleError<T>);
         }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientFailoverSocket.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientFailoverSocket.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientFailoverSocket.cs
new file mode 100644
index 0000000..8a08368
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientFailoverSocket.cs
@@ -0,0 +1,247 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Core.Impl.Client
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics;
+    using System.Diagnostics.CodeAnalysis;
+    using System.Linq;
+    using System.Net;
+    using System.Net.Sockets;
+    using System.Threading;
+    using System.Threading.Tasks;
+    using Apache.Ignite.Core.Client;
+    using Apache.Ignite.Core.Impl.Binary.IO;
+
+    /// <summary>
+    /// Socket wrapper with reconnect/failover functionality: reconnects on failure.
+    /// </summary>
+    internal class ClientFailoverSocket : IClientSocket
+    {
+        /** Underlying socket. */
+        private ClientSocket _socket;
+
+        /** Current global endpoint index for Round-robin. */
+        private static long _endPointIndex;
+
+        /** Config. */
+        private readonly IgniteClientConfiguration _config;
+
+        /** Endpoints with corresponding hosts. */
+        private readonly List<KeyValuePair<IPEndPoint, string>> _endPoints;
+
+        /** Locker. */
+        private readonly object _syncRoot = new object();
+
+        /** Disposed flag. */
+        private bool _disposed;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ClientFailoverSocket"/> class.
+        /// </summary>
+        /// <param name="config">The configuration.</param>
+        public ClientFailoverSocket(IgniteClientConfiguration config)
+        {
+            Debug.Assert(config != null);
+
+            _config = config;
+
+#pragma warning disable 618 // Type or member is obsolete
+            if (config.Host == null && (config.Endpoints == null || config.Endpoints.Count == 0))
+            {
+                throw new IgniteClientException("Invalid IgniteClientConfiguration: Host is null, " +
+                                                "Endpoints is null or empty. Nowhere to connect.");
+            }
+#pragma warning restore 618
+
+            _endPoints = GetIpEndPoints(config).ToList();
+
+            if (_endPoints.Count == 0)
+            {
+                throw new IgniteClientException("Failed to resolve all specified hosts.");
+            }
+
+            Connect();
+        }
+
+        /** <inheritdoc /> */
+        public T DoOutInOp<T>(ClientOp opId, Action<IBinaryStream> writeAction, Func<IBinaryStream, T> readFunc,
+            Func<ClientStatusCode, string, T> errorFunc = null)
+        {
+            return GetSocket().DoOutInOp(opId, writeAction, readFunc, errorFunc);
+        }
+
+        /** <inheritdoc /> */
+        public Task<T> DoOutInOpAsync<T>(ClientOp opId, Action<IBinaryStream> writeAction, Func<IBinaryStream, T> readFunc, Func<ClientStatusCode, string, T> errorFunc = null)
+        {
+            return GetSocket().DoOutInOpAsync(opId, writeAction, readFunc, errorFunc);
+        }
+
+        /** <inheritdoc /> */
+        public ClientProtocolVersion ServerVersion
+        {
+            get { return GetSocket().ServerVersion; }
+        }
+
+        /** <inheritdoc /> */
+        public EndPoint RemoteEndPoint
+        {
+            get
+            {
+                lock (_syncRoot)
+                {
+                    return _socket != null ? _socket.RemoteEndPoint : null;
+                }
+            }
+        }
+
+        /** <inheritdoc /> */
+        public EndPoint LocalEndPoint
+        {
+            get
+            {
+                lock (_syncRoot)
+                {
+                    return _socket != null ? _socket.LocalEndPoint : null;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Checks the disposed state.
+        /// </summary>
+        private ClientSocket GetSocket()
+        {
+            lock (_syncRoot)
+            {
+                if (_disposed)
+                {
+                    throw new ObjectDisposedException("ClientFailoverSocket");
+                }
+
+                if (_socket == null)
+                {
+                    Connect();
+                }
+
+                return _socket;
+            }
+        }
+
+        /** <inheritdoc /> */
+        [SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly",
+            Justification = "There is no finalizer.")]
+        public void Dispose()
+        {
+            lock (_syncRoot)
+            {
+                _disposed = true;
+
+                if (_socket != null)
+                {
+                    _socket.Dispose();
+                    _socket = null;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Connects the socket.
+        /// </summary>
+        private void Connect()
+        {
+            List<Exception> errors = null;
+            var startIdx = (int) Interlocked.Increment(ref _endPointIndex);
+
+            for (var i = 0; i < _endPoints.Count; i++)
+            {
+                var idx = (startIdx + i) % _endPoints.Count;
+                var endPoint = _endPoints[idx];
+
+                try
+                {
+                    _socket = new ClientSocket(_config, endPoint.Key, endPoint.Value, OnSocketError);
+                    return;
+                }
+                catch (SocketException e)
+                {
+                    if (errors == null)
+                    {
+                        errors = new List<Exception>();
+                    }
+
+                    errors.Add(e);
+                }
+            }
+
+            throw new AggregateException("Failed to establish Ignite thin client connection, " +
+                                         "examine inner exceptions for details.", errors);
+        }
+
+        /// <summary>
+        /// Called when socket error occurs.
+        /// </summary>
+        private void OnSocketError()
+        {
+            if (_config.ReconnectDisabled)
+            {
+                return;
+            }
+
+            // Reconnect on next operation.
+            lock (_syncRoot)
+            {
+                _socket = null;
+            }
+        }
+
+        /// <summary>
+        /// Gets the endpoints: all combinations of IP addresses and ports according to configuration.
+        /// </summary>
+        private static IEnumerable<KeyValuePair<IPEndPoint, string>> GetIpEndPoints(IgniteClientConfiguration cfg)
+        {
+            foreach (var e in Endpoint.GetEndpoints(cfg))
+            {
+                var host = e.Host;
+                Debug.Assert(host != null);  // Checked by GetEndpoints.
+
+                // GetHostEntry accepts IPs, but TryParse is a more efficient shortcut.
+                IPAddress ip;
+
+                if (IPAddress.TryParse(host, out ip))
+                {
+                    for (var i = 0; i <= e.PortRange; i++)
+                    {
+                        yield return new KeyValuePair<IPEndPoint, string>(new IPEndPoint(ip, e.Port + i), host);
+                    }
+                }
+                else
+                {
+                    for (var i = 0; i <= e.PortRange; i++)
+                    {
+                        foreach (var x in Dns.GetHostEntry(host).AddressList)
+                        {
+                            yield return new KeyValuePair<IPEndPoint, string>(new IPEndPoint(x, e.Port + i), host);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientSocket.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientSocket.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientSocket.cs
index 8a8b53b..b9eee99 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientSocket.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientSocket.cs
@@ -19,7 +19,6 @@ namespace Apache.Ignite.Core.Impl.Client
 {
     using System;
     using System.Collections.Concurrent;
-    using System.Collections.Generic;
     using System.Diagnostics;
     using System.Diagnostics.CodeAnalysis;
     using System.IO;
@@ -29,7 +28,6 @@ namespace Apache.Ignite.Core.Impl.Client
     using System.Threading;
     using System.Threading.Tasks;
     using Apache.Ignite.Core.Client;
-    using Apache.Ignite.Core.Common;
     using Apache.Ignite.Core.Impl.Binary;
     using Apache.Ignite.Core.Impl.Binary.IO;
     using Apache.Ignite.Core.Impl.Common;
@@ -37,7 +35,7 @@ namespace Apache.Ignite.Core.Impl.Client
     /// <summary>
     /// Wrapper over framework socket for Ignite thin client operations.
     /// </summary>
-    internal sealed class ClientSocket : IDisposable
+    internal sealed class ClientSocket : IClientSocket
     {
         /** Version 1.0.0. */
         private static readonly ClientProtocolVersion Ver100 = new ClientProtocolVersion(1, 0, 0);
@@ -97,19 +95,27 @@ namespace Apache.Ignite.Core.Impl.Client
         /** Disposed flag. */
         private bool _isDisposed;
 
+        /** Error callback. */
+        private readonly Action _onError;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ClientSocket" /> class.
         /// </summary>
         /// <param name="clientConfiguration">The client configuration.</param>
+        /// <param name="endPoint">The end point to connect to.</param>
+        /// <param name="host">The host name (required for SSL).</param>
+        /// <param name="onError">Error callback.</param>
         /// <param name="version">Protocol version.</param>
-        public ClientSocket(IgniteClientConfiguration clientConfiguration, ClientProtocolVersion? version = null)
+        public ClientSocket(IgniteClientConfiguration clientConfiguration, EndPoint endPoint, string host,
+            Action onError = null, ClientProtocolVersion? version = null)
         {
             Debug.Assert(clientConfiguration != null);
 
+            _onError = onError;
             _timeout = clientConfiguration.SocketTimeout;
 
-            _socket = Connect(clientConfiguration);
-            _stream = GetSocketStream(_socket, clientConfiguration);
+            _socket = Connect(clientConfiguration, endPoint);
+            _stream = GetSocketStream(_socket, clientConfiguration, host);
 
             ServerVersion = version ?? CurrentProtocolVersion;
 
@@ -161,7 +167,7 @@ namespace Apache.Ignite.Core.Impl.Client
         {
             // Encode.
             var reqMsg = WriteMessage(writeAction, opId);
-            
+
             // Send.
             var response = SendRequest(ref reqMsg);
 
@@ -186,6 +192,16 @@ namespace Apache.Ignite.Core.Impl.Client
         }
 
         /// <summary>
+        /// Gets the current remote EndPoint.
+        /// </summary>
+        public EndPoint RemoteEndPoint { get { return _socket.RemoteEndPoint; } }
+
+        /// <summary>
+        /// Gets the current local EndPoint.
+        /// </summary>
+        public EndPoint LocalEndPoint { get { return _socket.LocalEndPoint; } }
+
+        /// <summary>
         /// Starts waiting for the new message.
         /// </summary>
         [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
@@ -245,7 +261,7 @@ namespace Apache.Ignite.Core.Impl.Client
         /// <summary>
         /// Decodes the response that we got from <see cref="HandleResponse"/>.
         /// </summary>
-        private static T DecodeResponse<T>(BinaryHeapStream stream, Func<IBinaryStream, T> readFunc, 
+        private static T DecodeResponse<T>(BinaryHeapStream stream, Func<IBinaryStream, T> readFunc,
             Func<ClientStatusCode, string, T> errorFunc)
         {
             var statusCode = (ClientStatusCode)stream.ReadInt();
@@ -298,8 +314,8 @@ namespace Apache.Ignite.Core.Impl.Client
                     BinaryUtils.Marshaller.FinishMarshal(writer);
                 }
             }, 12, out messageLen);
-            
-            _stream.Write(buf, 0, messageLen);
+
+            SocketWrite(buf, messageLen);
 
             // Decode response.
             var res = ReceiveMessage();
@@ -370,16 +386,20 @@ namespace Apache.Ignite.Core.Impl.Client
             // Socket.Receive can return any number of bytes, even 1.
             // We should repeat Receive calls until required amount of data has been received.
             var buf = new byte[size];
-            var received = _stream.Read(buf,0, size);
+            var received = SocketRead(buf, 0, size);
 
             while (received < size)
             {
-                var res = _stream.Read(buf, received, size - received);
+                var res = SocketRead(buf, received, size - received);
 
                 if (res == 0)
                 {
                     // Disconnected.
                     _exception = _exception ?? new SocketException((int) SocketError.ConnectionAborted);
+                    if (_onError != null)
+                    {
+                        _onError();
+                    }
                     Dispose();
                     CheckException();
                 }
@@ -410,7 +430,7 @@ namespace Apache.Ignite.Core.Impl.Client
 
                     if (_requests.IsEmpty)
                     {
-                        _stream.Write(reqMsg.Buffer, 0, reqMsg.Length);
+                        SocketWrite(reqMsg.Buffer, reqMsg.Length);
 
                         var respMsg = ReceiveMessage();
                         var response = new BinaryHeapStream(respMsg);
@@ -451,7 +471,7 @@ namespace Apache.Ignite.Core.Impl.Client
                 Debug.Assert(added);
 
                 // Send.
-                _stream.Write(reqMsg.Buffer, 0, reqMsg.Length);
+                SocketWrite(reqMsg.Buffer, reqMsg.Length);
                 _listenerEvent.Set();
                 return req.CompletionSource.Task;
             }
@@ -490,100 +510,90 @@ namespace Apache.Ignite.Core.Impl.Client
         }
 
         /// <summary>
-        /// Connects the socket.
+        /// Writes to the socket. All socket writes should go through this method.
         /// </summary>
-        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", 
-            Justification = "Socket is returned from this method.")]
-        private static Socket Connect(IgniteClientConfiguration cfg)
+        private void SocketWrite(byte[] buf, int len)
         {
-            List<Exception> errors = null;
-
-            foreach (var ipEndPoint in GetEndPoints(cfg))
+            try
             {
-                try
-                {
-                    var socket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
-                    {
-                        NoDelay = cfg.TcpNoDelay,
-                        Blocking = true,
-                        SendTimeout = (int) cfg.SocketTimeout.TotalMilliseconds,
-                        ReceiveTimeout = (int) cfg.SocketTimeout.TotalMilliseconds
-                    };
-
-                    if (cfg.SocketSendBufferSize != IgniteClientConfiguration.DefaultSocketBufferSize)
-                    {
-                        socket.SendBufferSize = cfg.SocketSendBufferSize;
-                    }
-
-                    if (cfg.SocketReceiveBufferSize != IgniteClientConfiguration.DefaultSocketBufferSize)
-                    {
-                        socket.ReceiveBufferSize = cfg.SocketReceiveBufferSize;
-                    }
-
-                    socket.Connect(ipEndPoint);
-
-                    return socket;
-                }
-                catch (SocketException e)
+                _stream.Write(buf, 0, len);
+            }
+            catch (Exception)
+            {
+                if (_onError != null)
                 {
-                    if (errors == null)
-                    {
-                        errors = new List<Exception>();
-                    }
-
-                    errors.Add(e);
+                    _onError();
                 }
+                throw;
             }
+        }
 
-            if (errors == null)
+        /// <summary>
+        /// Reads from the socket. All socket reads should go through this method.
+        /// </summary>
+        private int SocketRead(byte[] buf, int pos, int len)
+        {
+            try
             {
-                throw new IgniteException("Failed to resolve client host: " + cfg.Host);
+                return _stream.Read(buf, pos, len);
+            }
+            catch (Exception)
+            {
+                if (_onError != null)
+                {
+                    _onError();
+                }
+                throw;
             }
-
-            throw new AggregateException("Failed to establish Ignite thin client connection, " +
-                                         "examine inner exceptions for details.", errors);
         }
 
         /// <summary>
-        /// Gets the socket stream.
+        /// Connects the socket.
         /// </summary>
-        private static Stream GetSocketStream(Socket socket, IgniteClientConfiguration cfg)
+        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope",
+            Justification = "Socket is returned from this method.")]
+        private static Socket Connect(IgniteClientConfiguration cfg, EndPoint endPoint)
         {
-            var stream = new NetworkStream(socket)
+            var socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
             {
-                ReadTimeout = (int) cfg.SocketTimeout.TotalMilliseconds,
-                WriteTimeout = (int) cfg.SocketTimeout.TotalMilliseconds
+                NoDelay = cfg.TcpNoDelay,
+                Blocking = true,
+                SendTimeout = (int) cfg.SocketTimeout.TotalMilliseconds,
+                ReceiveTimeout = (int) cfg.SocketTimeout.TotalMilliseconds
             };
 
-            if (cfg.SslStreamFactory == null)
+            if (cfg.SocketSendBufferSize != IgniteClientConfiguration.DefaultSocketBufferSize)
             {
-                return stream;
+                socket.SendBufferSize = cfg.SocketSendBufferSize;
+            }
+
+            if (cfg.SocketReceiveBufferSize != IgniteClientConfiguration.DefaultSocketBufferSize)
+            {
+                socket.ReceiveBufferSize = cfg.SocketReceiveBufferSize;
             }
 
-            return cfg.SslStreamFactory.Create(stream, cfg.Host);
+            socket.Connect(endPoint);
+
+            return socket;
         }
 
         /// <summary>
-        /// Gets the endpoints: all combinations of IP addresses and ports according to configuration.
+        /// Gets the socket stream.
         /// </summary>
-        private static IEnumerable<IPEndPoint> GetEndPoints(IgniteClientConfiguration cfg)
+        private static Stream GetSocketStream(Socket socket, IgniteClientConfiguration cfg, string host)
         {
-            var host = cfg.Host;
-
-            if (host == null)
+            var stream = new NetworkStream(socket)
             {
-                throw new IgniteException("IgniteClientConfiguration.Host cannot be null.");
-            }
-
-            // GetHostEntry accepts IPs, but TryParse is a more efficient shortcut.
-            IPAddress ip;
+                ReadTimeout = (int) cfg.SocketTimeout.TotalMilliseconds,
+                WriteTimeout = (int) cfg.SocketTimeout.TotalMilliseconds
+            };
 
-            if (IPAddress.TryParse(host, out ip))
+            if (cfg.SslStreamFactory == null)
             {
-                return new[] {new IPEndPoint(ip, cfg.Port)};
+                return stream;
             }
 
-            return Dns.GetHostEntry(host).AddressList.Select(x => new IPEndPoint(x, cfg.Port));
+            return cfg.SslStreamFactory.Create(stream, host);
         }
 
         /// <summary>

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Endpoint.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Endpoint.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Endpoint.cs
new file mode 100644
index 0000000..a42d6cd
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Endpoint.cs
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Core.Impl.Client
+{
+    using System;
+    using System.Collections.Generic;
+    using System.ComponentModel;
+    using Apache.Ignite.Core.Client;
+    using Apache.Ignite.Core.Impl.Common;
+
+    /// <summary>
+    /// Internal representation of client endpoint.
+    /// </summary>
+    internal class Endpoint
+    {
+        /** */
+        private static readonly string[] HostSeparators = {":"};
+
+        /** */
+        private static readonly string[] PortsSeparators = {".."};
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Endpoint"/> class.
+        /// </summary>
+        private Endpoint(string host, int port = IgniteClientConfiguration.DefaultPort, int portRange = 0)
+        {
+            Host = IgniteArgumentCheck.NotNullOrEmpty(host, "host");
+            Port = port;
+            PortRange = portRange;
+        }
+
+        /// <summary>
+        /// Gets or sets the host.
+        /// </summary>
+        public string Host { get; private set; }
+
+        /// <summary>
+        /// Gets or sets the port.
+        /// </summary>
+        [DefaultValue(IgniteClientConfiguration.DefaultPort)]
+        public int Port { get; private set; }
+
+        /// <summary>
+        /// Size of the port range. Default is 0, meaning only one port is used, defined by <see cref="Port"/>.
+        /// </summary>
+        public int PortRange { get; private set; }
+
+        /// <summary>
+        /// Gets the client endpoints from given configuration.
+        /// </summary>
+        public static IEnumerable<Endpoint> GetEndpoints(IgniteClientConfiguration cfg)
+        {
+#pragma warning disable 618 // Type or member is obsolete
+            if (cfg.Host != null)
+            {
+                yield return new Endpoint(cfg.Host, cfg.Port);
+            }
+#pragma warning restore 618
+
+            if (cfg.Endpoints != null)
+            {
+                foreach (var endpoint in cfg.Endpoints)
+                {
+                    yield return ParseEndpoint(endpoint);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Parses the endpoint string.
+        /// </summary>
+        private static Endpoint ParseEndpoint(string endpoint)
+        {
+            if (string.IsNullOrWhiteSpace(endpoint))
+            {
+                throw new IgniteClientException(
+                    "IgniteClientConfiguration.Endpoints[...] can't be null or whitespace.");
+            }
+
+            var parts = endpoint.Split(HostSeparators, StringSplitOptions.None);
+
+            if (parts.Length == 1)
+            {
+                return new Endpoint(endpoint);
+            }
+
+            if (parts.Length == 2)
+            {
+                var host = parts[0];
+                var port = parts[1];
+
+                var ports = port.Split(PortsSeparators, StringSplitOptions.None);
+
+                if (ports.Length == 1)
+                {
+                    return new Endpoint(host, ParsePort(endpoint, port));
+                }
+
+                if (ports.Length == 2)
+                {
+                    var minPort = ParsePort(endpoint, ports[0]);
+                    var maxPort = ParsePort(endpoint, ports[1]);
+
+                    if (maxPort < minPort)
+                    {
+                        throw new IgniteClientException(
+                            "Invalid format of IgniteClientConfiguration.Endpoint, port range is empty: " + endpoint);
+                    }
+
+                    return new Endpoint(host, minPort, maxPort - minPort);
+                }
+            }
+
+            throw new IgniteClientException("Unrecognized format of IgniteClientConfiguration.Endpoint: " + endpoint);
+        }
+
+        /// <summary>
+        /// Parses the port string.
+        /// </summary>
+        private static int ParsePort(string endpoint, string portString)
+        {
+            int port;
+
+            if (int.TryParse(portString, out port))
+            {
+                return port;
+            }
+
+            throw new IgniteClientException(
+                "Unrecognized format of IgniteClientConfiguration.Endpoint, failed to parse port: " + endpoint);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/45abb9c7/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/IClientSocket.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/IClientSocket.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/IClientSocket.cs
new file mode 100644
index 0000000..c81f45f
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/IClientSocket.cs
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Core.Impl.Client
+{
+    using System;
+    using System.Net;
+    using System.Threading.Tasks;
+    using Apache.Ignite.Core.Client;
+    using Apache.Ignite.Core.Impl.Binary.IO;
+
+    /// <summary>
+    /// Wrapper over framework socket for Ignite thin client operations.
+    /// </summary>
+    internal interface IClientSocket : IDisposable
+    {
+        /// <summary>
+        /// Performs a send-receive operation.
+        /// </summary>
+        T DoOutInOp<T>(ClientOp opId, Action<IBinaryStream> writeAction,
+            Func<IBinaryStream, T> readFunc, Func<ClientStatusCode, string, T> errorFunc = null);
+
+        /// <summary>
+        /// Performs a send-receive operation asynchronously.
+        /// </summary>
+        Task<T> DoOutInOpAsync<T>(ClientOp opId, Action<IBinaryStream> writeAction,
+            Func<IBinaryStream, T> readFunc, Func<ClientStatusCode, string, T> errorFunc = null);
+
+        /// <summary>
+        /// Gets the server version.
+        /// </summary>
+        ClientProtocolVersion ServerVersion { get; }
+
+        /// <summary>
+        /// Gets the current remote EndPoint.
+        /// </summary>
+        EndPoint RemoteEndPoint { get; }
+
+        /// <summary>
+        /// Gets the current local EndPoint.
+        /// </summary>
+        EndPoint LocalEndPoint { get; }
+    }
+}


Mime
View raw message