├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── CSRedis.Tests ├── CSRedis.Tests.csproj ├── ConnectionTests.cs ├── HashTests.cs ├── HyperLogLogTests.cs ├── KeyTests.cs ├── ListTests.cs ├── Properties │ └── AssemblyInfo.cs ├── PubSubTests.cs ├── RegressionTests.cs ├── ScriptingTests.cs ├── SentinelTests.cs ├── ServerTests.cs ├── SetTests.cs ├── SortedSetTests.cs ├── StringTests.cs └── TransactionTests.cs ├── CSRedis.sln ├── CSRedis ├── CSRedis.csproj ├── CSRedis.nuspec ├── Events.cs ├── Exceptions.cs ├── IRedisClient.cs ├── IRedisClientAsync.cs ├── IRedisClientSync.cs ├── Internal │ ├── Commands │ │ ├── RedisArray.cs │ │ ├── RedisBool.cs │ │ ├── RedisBytes.cs │ │ ├── RedisDate.cs │ │ ├── RedisFloat.cs │ │ ├── RedisHash.cs │ │ ├── RedisInt.cs │ │ ├── RedisIsMasterDownByAddrCommand.cs │ │ ├── RedisObject.cs │ │ ├── RedisRoleCommand.cs │ │ ├── RedisScanCommand.cs │ │ ├── RedisSlowLogCommand.cs │ │ ├── RedisStatus.cs │ │ ├── RedisString.cs │ │ ├── RedisSubscription.cs │ │ └── RedisTuple.cs │ ├── Fakes │ │ ├── FakeRedisSocket.cs │ │ └── FakeStream.cs │ ├── IO │ │ ├── AsyncConnector.cs │ │ ├── IRedisSocket.cs │ │ ├── RedisAsyncCommandToken.cs │ │ ├── RedisIO.cs │ │ ├── RedisPooledSocket.cs │ │ ├── RedisReader.cs │ │ ├── RedisSocket.cs │ │ ├── RedisWriter.cs │ │ ├── SocketAsyncPool.cs │ │ └── SocketPool.cs │ ├── MonitorListener.cs │ ├── RedisCommand.cs │ ├── RedisConnector.cs │ ├── RedisListener.cs │ ├── RedisPipeline.cs │ ├── RedisTransaction.cs │ ├── SubscriptionListener.cs │ └── Utilities │ │ ├── RedisArgs.cs │ │ └── Serializer.cs ├── Properties │ └── AssemblyInfo.cs ├── RedisClient.Async.cs ├── RedisClient.Sync.cs ├── RedisClient.cs ├── RedisConnectionPool.cs ├── RedisSentinelClient.Async.cs ├── RedisSentinelClient.Sync.cs ├── RedisSentinelClient.cs ├── RedisSentinelManager.cs └── Types.cs ├── LICENSE ├── README.md └── csredis.pub.snk /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behaviour to automatically normalize line endings. 2 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Configuration 10 | [Ap]pp[Ll]ocal.config 11 | 12 | # Build results 13 | 14 | [Dd]ebug/ 15 | [Rr]elease/ 16 | x64/ 17 | build/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | 21 | # MSTest test Results 22 | [Tt]est[Rr]esult*/ 23 | [Bb]uild[Ll]og.* 24 | 25 | *_i.c 26 | *_p.c 27 | *.ilk 28 | *.meta 29 | *.obj 30 | *.pch 31 | *.pdb 32 | *.pgc 33 | *.pgd 34 | *.rsp 35 | *.sbr 36 | *.tlb 37 | *.tli 38 | *.tlh 39 | *.tmp 40 | *.tmp_proj 41 | *.log 42 | *.vspscc 43 | *.vssscc 44 | .builds 45 | *.pidb 46 | *.log 47 | *.scc 48 | *.sln.ide 49 | 50 | # Visual C++ cache files 51 | ipch/ 52 | *.aps 53 | *.ncb 54 | *.opensdf 55 | *.sdf 56 | *.cachefile 57 | 58 | # Visual Studio profiler 59 | *.psess 60 | *.vsp 61 | *.vspx 62 | 63 | # Guidance Automation Toolkit 64 | *.gpState 65 | 66 | # ReSharper is a .NET coding add-in 67 | _ReSharper*/ 68 | *.[Rr]e[Ss]harper 69 | 70 | # TeamCity is a build add-in 71 | _TeamCity* 72 | 73 | # DotCover is a Code Coverage Tool 74 | *.dotCover 75 | 76 | # NCrunch 77 | *.ncrunch* 78 | .*crunch*.local.xml 79 | 80 | # Installshield output folder 81 | [Ee]xpress/ 82 | 83 | # DocProject is a documentation generator add-in 84 | DocProject/buildhelp/ 85 | DocProject/Help/*.HxT 86 | DocProject/Help/*.HxC 87 | DocProject/Help/*.hhc 88 | DocProject/Help/*.hhk 89 | DocProject/Help/*.hhp 90 | DocProject/Help/Html2 91 | DocProject/Help/html 92 | 93 | # Click-Once directory 94 | publish/ 95 | 96 | # Publish Web Output 97 | *.Publish.xml 98 | *.pubxml 99 | 100 | # NuGet Packages Directory 101 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 102 | #packages/ 103 | 104 | # Windows Azure Build Output 105 | csx 106 | *.build.csdef 107 | 108 | # Windows Store app package directory 109 | AppPackages/ 110 | 111 | # Others 112 | sql/ 113 | *.Cache 114 | ClientBin/ 115 | [Ss]tyle[Cc]op.* 116 | ~$* 117 | *~ 118 | *.dbmdl 119 | *.[Pp]ublish.xml 120 | *.pfx 121 | *.publishsettings 122 | 123 | # RIA/Silverlight projects 124 | Generated_Code/ 125 | 126 | # Backup & report files from converting an old project file to a newer 127 | # Visual Studio version. Backup files are not needed, because we have git ;-) 128 | _UpgradeReport_Files/ 129 | Backup*/ 130 | UpgradeLog*.XML 131 | UpgradeLog*.htm 132 | 133 | # SQL Server files 134 | App_Data/*.mdf 135 | App_Data/*.ldf 136 | 137 | 138 | #LightSwitch generated files 139 | GeneratedArtifacts/ 140 | _Pvt_Extensions/ 141 | ModelManifest.xml 142 | 143 | # ========================= 144 | # Windows detritus 145 | # ========================= 146 | 147 | # Windows image file caches 148 | Thumbs.db 149 | ehthumbs.db 150 | 151 | # Folder config file 152 | Desktop.ini 153 | 154 | # Recycle Bin used on file shares 155 | $RECYCLE.BIN/ 156 | 157 | # Mac desktop service store files 158 | .DS_Store 159 | *.nupkg 160 | /ctstone.Redis/nuget-pack-push.cmd 161 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## csredis 3.2.6 2 | **[new]** SSL Support 3 | 4 | ## csredis 3.2.5 5 | **[new]** RedisConnectionPool: a pooled collection of Redis connections 6 | **[fix]** Public interface inheritance 7 | **[change]** Compatibility for sever 2.8.14 8 | **[new]** Exposing host/port 9 | **[fix]** Internal reorganization 10 | 11 | ## csredis 3.2.1 12 | **[fix]** Unnecessary memory allocation when not using async methods 13 | 14 | ## csredis 3.1.18 15 | **[fix]** Encoding issues 16 | **[new]** Strongname signed assembly available on NuGet 17 | 18 | ## csredis 3.1.1 19 | **[breaking]** Changed RedisClient.Connected property to RedisClient.IsConnected 20 | **[breaking]** Changed RedisClient.Reconnected event to RedisClient.Connected. Event is now raised when the connection is first established as well as when the connection reconnects 21 | **[breaking]** Changed RedisClient.ReconnectTimeout to RedisClient.ReconnectWait 22 | **[fix]** Fixes to async handling 23 | **[fix]** Fixes to pipeline handling 24 | 25 | ## csredis 3.0.0.0 26 | Major internal rewrite with several breaking changes. Upgrade with care! 27 | **[breaking]** Changed base namespace: CSRedis 28 | **[breaking]** Consolidated async methods into main client (removed RedisClientAsync) 29 | **[new]** Reconnection support 30 | **[new]** Command support through Redis 2.8.12 31 | **[fix]** Improved unit tests 32 | **[fix]** Improved async handling 33 | **[fix]** Better support for Sentinel and managed connections 34 | 35 | ## csredis 2.1.1.0 36 | Added support for Redis 2.8 commands (SCAN, SSCAN, HSCAN, ZSCAN) 37 | 38 | ## csredis 2.1.0.0 39 | Improved serialization performance 40 | Added support for ISerializable 41 | 42 | ## csredis 2.0.0.0 43 | Improved handling of pipeline and MULTI/EXEC result parsing. 44 | Breaking change: RedisClient.Multi() returns void instead of string. MULTI server reply may be observed by attaching to TransactionStarted event 45 | RedisClient.StartPipe() now accepts optional boolean indicating whether or not result parsing should occur. 46 | RedisClient.StartPipeTransaction() added to facilitate automatic creation of transaction inside a pipeline. 47 | RedisClient.EndPipe(bool) has no effect and should not be used 48 | RedisClient constructor overloads added to handle default values for port and timeout 49 | TransactionQueuedEventArgs now includes the command and arguments that were queued. 50 | 51 | ## csredis 1.4.7.1 52 | Fixed bug in tracing for empty stack 53 | 54 | ## csredis 1.4.7.0 55 | Added tracing 56 | 57 | ## csredis 1.4.6.0 58 | Better handling of sorted set members in ZREM 59 | 60 | ## csredis 1.4.5.0 61 | Better handling of sorted set scores in ZADD 62 | 63 | ## csredis 1.4.4.0 64 | Fix for non-existent hashes now returns null 65 | 66 | ## csredis 1.4.3.0 67 | Fix for null keys/values in hashes 68 | 69 | ## csredis 1.4.2.0 70 | Added XML inline documentation 71 | 72 | ## csredis 1.4.1.0 73 | Added support for extended SET command introduced in Redis 2.6.12 74 | 75 | ## csredis 1.4.0.0 76 | Added async subscriptions and transactions 77 | 78 | ## csredis 1.3.0.0 79 | Fixed byte streaming bug in RedisClient 80 | 81 | ## csredis 1.2.0.0 82 | Fixed bug when reading large amounts of data concurrently 83 | 84 | ## csredis 1.1.0.0 85 | Added async subscriptions and transactions 86 | -------------------------------------------------------------------------------- /CSRedis.Tests/CSRedis.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {422A4579-8C59-4E92-8EB5-2A45C38D27FB} 7 | Library 8 | Properties 9 | CSRedis.Tests 10 | CSRedis.Tests 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | true 39 | $(StrongNameKeyPath) 40 | 41 | 42 | true 43 | true 44 | ..\csredis.pub.snk 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | {d35e185e-a7e1-41e1-846c-21944f56074f} 82 | CSRedis 83 | 84 | 85 | 86 | 87 | 88 | 89 | False 90 | 91 | 92 | False 93 | 94 | 95 | False 96 | 97 | 98 | False 99 | 100 | 101 | 102 | 103 | 104 | 105 | 112 | -------------------------------------------------------------------------------- /CSRedis.Tests/ConnectionTests.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal; 2 | using CSRedis.Internal.Fakes; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace CSRedis.Tests 12 | { 13 | class TestHelper 14 | { 15 | public const string Host = "fakehost"; 16 | public const int Port = 9999; 17 | 18 | public static void Test(string reply, Func syncFunc, Func> asyncFunc, Action test) 19 | { 20 | using (var mock = new FakeRedisSocket(reply, reply)) 21 | using (var redis = new RedisClient(mock, new DnsEndPoint(Host, Port))) 22 | { 23 | if (syncFunc != null) 24 | { 25 | var r1 = syncFunc(redis); 26 | test(mock, r1); 27 | } 28 | 29 | if (asyncFunc != null) 30 | { 31 | var r2 = asyncFunc(redis); 32 | test(mock, r2.Result); 33 | } 34 | } 35 | } 36 | } 37 | 38 | 39 | [TestClass] 40 | public class ConnectionTests 41 | { 42 | [TestMethod, TestCategory("Connection")] 43 | public void AuthTest() 44 | { 45 | TestHelper.Test( 46 | "+OK\r\n", 47 | x => x.Auth("my password"), 48 | x => x.AuthAsync("my password"), 49 | (x, r) => 50 | { 51 | Assert.AreEqual("OK", r); 52 | Assert.AreEqual("*2\r\n$4\r\nAUTH\r\n$11\r\nmy password\r\n", x.GetMessage()); 53 | }); 54 | } 55 | 56 | [TestMethod, TestCategory("Connection")] 57 | public void EchoTest() 58 | { 59 | TestHelper.Test( 60 | "$11\r\nhello world\r\n", 61 | x => x.Echo("hello world"), 62 | x => x.EchoAsync("hello world"), 63 | (x, r) => 64 | { 65 | Assert.AreEqual("hello world", r); 66 | Assert.AreEqual("*2\r\n$4\r\nECHO\r\n$11\r\nhello world\r\n", x.GetMessage()); 67 | }); 68 | } 69 | 70 | [TestMethod, TestCategory("Connection")] 71 | public void PingTest() 72 | { 73 | TestHelper.Test( 74 | "+PONG\r\n", 75 | x => x.Ping(), 76 | x => x.PingAsync(), 77 | (x, r) => 78 | { 79 | Assert.AreEqual("PONG", r); 80 | Assert.AreEqual("*1\r\n$4\r\nPING\r\n", x.GetMessage()); 81 | }); 82 | } 83 | 84 | [TestMethod, TestCategory("Connection")] 85 | public void QuitTest() 86 | { 87 | TestHelper.Test( 88 | "+OK\r\n", 89 | x => x.Quit(), 90 | null, 91 | (x, r) => 92 | { 93 | Assert.AreEqual("OK", r); 94 | Assert.AreEqual("*1\r\n$4\r\nQUIT\r\n", x.GetMessage()); 95 | }); 96 | 97 | TestHelper.Test( 98 | "+OK\r\n", 99 | null, 100 | x => x.QuitAsync(), 101 | (x, r) => 102 | { 103 | Assert.AreEqual("OK", r); 104 | Assert.AreEqual("*1\r\n$4\r\nQUIT\r\n", x.GetMessage()); 105 | }); 106 | // TODO: test Connected==false 107 | } 108 | 109 | [TestMethod, TestCategory("Connection")] 110 | public void SelectTest() 111 | { 112 | TestHelper.Test( 113 | "+OK\r\n", 114 | x => x.Select(2), 115 | x => x.SelectAsync(2), 116 | (x, r) => 117 | { 118 | Assert.AreEqual("OK", r); 119 | Assert.AreEqual("*2\r\n$6\r\nSELECT\r\n$1\r\n2\r\n", x.GetMessage()); 120 | }); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /CSRedis.Tests/HashTests.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal; 2 | using CSRedis.Internal.Fakes; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace CSRedis.Tests 12 | { 13 | public class MyGeneric 14 | { 15 | public string field1 { get; set; } 16 | } 17 | 18 | [TestClass] 19 | public class HashTests 20 | { 21 | [TestMethod, TestCategory("Hashes")] 22 | public void TestHDel() 23 | { 24 | using (var mock = new FakeRedisSocket(":2\r\n")) 25 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 26 | { 27 | Assert.AreEqual(2, redis.HDel("test", "test1", "test2")); 28 | Assert.AreEqual("*4\r\n$4\r\nHDEL\r\n$4\r\ntest\r\n$5\r\ntest1\r\n$5\r\ntest2\r\n", mock.GetMessage()); 29 | } 30 | } 31 | 32 | [TestMethod, TestCategory("Hashes")] 33 | public void TestHExists() 34 | { 35 | using (var mock = new FakeRedisSocket(":1\r\n")) 36 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 37 | { 38 | Assert.IsTrue(redis.HExists("test", "field")); 39 | Assert.AreEqual("*3\r\n$7\r\nHEXISTS\r\n$4\r\ntest\r\n$5\r\nfield\r\n", mock.GetMessage()); 40 | } 41 | } 42 | 43 | [TestMethod, TestCategory("Hashes")] 44 | public void TestHGet() 45 | { 46 | using (var mock = new FakeRedisSocket("$4\r\ntest\r\n")) 47 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 48 | { 49 | Assert.AreEqual("test", redis.HGet("test", "field")); 50 | Assert.AreEqual("*3\r\n$4\r\nHGET\r\n$4\r\ntest\r\n$5\r\nfield\r\n", mock.GetMessage()); 51 | } 52 | } 53 | 54 | [TestMethod, TestCategory("Hashes")] 55 | public void TestHGetAll_Dictionary() 56 | { 57 | using (var mock = new FakeRedisSocket("*2\r\n$6\r\nfield1\r\n$5\r\ntest1\r\n")) 58 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 59 | { 60 | var response = redis.HGetAll("test"); 61 | Assert.AreEqual(1, response.Count); 62 | Assert.IsTrue(response.ContainsKey("field1")); 63 | Assert.AreEqual("test1", response["field1"]); 64 | Assert.AreEqual("*2\r\n$7\r\nHGETALL\r\n$4\r\ntest\r\n", mock.GetMessage()); 65 | } 66 | } 67 | 68 | [TestMethod, TestCategory("Hashes")] 69 | public void TestHGetAll_Generic() 70 | { 71 | using (var mock = new FakeRedisSocket("*2\r\n$6\r\nfield1\r\n$5\r\ntest1\r\n")) 72 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 73 | { 74 | var response = redis.HGetAll("test"); 75 | Assert.AreEqual("test1", response.field1); 76 | Assert.AreEqual("*2\r\n$7\r\nHGETALL\r\n$4\r\ntest\r\n", mock.GetMessage()); 77 | } 78 | } 79 | 80 | [TestMethod, TestCategory("Hashes")] 81 | public void TestHIncrBy() 82 | { 83 | using (var mock = new FakeRedisSocket(":5\r\n")) 84 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 85 | { 86 | Assert.AreEqual(5, redis.HIncrBy("test", "field", 1)); 87 | Assert.AreEqual("*4\r\n$7\r\nHINCRBY\r\n$4\r\ntest\r\n$5\r\nfield\r\n$1\r\n1\r\n", mock.GetMessage()); 88 | } 89 | } 90 | 91 | [TestMethod, TestCategory("Hashes")] 92 | public void TestHIncrByFloat() 93 | { 94 | using (var mock = new FakeRedisSocket("$4\r\n3.14\r\n")) 95 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 96 | { 97 | Assert.AreEqual(3.14, redis.HIncrByFloat("test", "field", 1.14)); 98 | Assert.AreEqual("*4\r\n$12\r\nHINCRBYFLOAT\r\n$4\r\ntest\r\n$5\r\nfield\r\n$4\r\n1.14\r\n", mock.GetMessage()); 99 | } 100 | } 101 | 102 | [TestMethod, TestCategory("Hashes")] 103 | public void TestHKeys() 104 | { 105 | using (var mock = new FakeRedisSocket("*2\r\n$5\r\ntest1\r\n$5\r\ntest2\r\n")) 106 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 107 | { 108 | var response = redis.HKeys("test"); 109 | Assert.AreEqual(2, response.Length); 110 | Assert.AreEqual("test1", response[0]); 111 | Assert.AreEqual("test2", response[1]); 112 | Assert.AreEqual("*2\r\n$5\r\nHKEYS\r\n$4\r\ntest\r\n", mock.GetMessage()); 113 | } 114 | } 115 | 116 | [TestMethod, TestCategory("Hashes")] 117 | public void TestHLen() 118 | { 119 | using (var mock = new FakeRedisSocket(":5\r\n")) 120 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 121 | { 122 | Assert.AreEqual(5, redis.HLen("test")); 123 | Assert.AreEqual("*2\r\n$4\r\nHLEN\r\n$4\r\ntest\r\n", mock.GetMessage()); 124 | } 125 | } 126 | 127 | [TestMethod, TestCategory("Hashes")] 128 | public void TestHMGet() 129 | { 130 | using (var mock = new FakeRedisSocket("*2\r\n$5\r\ntest1\r\n$5\r\ntest2\r\n")) 131 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 132 | { 133 | var response = redis.HMGet("test", "field1", "field2"); 134 | Assert.AreEqual(2, response.Length); 135 | Assert.AreEqual("test1", response[0]); 136 | Assert.AreEqual("test2", response[1]); 137 | Assert.AreEqual("*4\r\n$5\r\nHMGET\r\n$4\r\ntest\r\n$6\r\nfield1\r\n$6\r\nfield2\r\n", mock.GetMessage()); 138 | } 139 | } 140 | 141 | [TestMethod, TestCategory("Hashes")] 142 | public void TestHMSet_Array() 143 | { 144 | using (var mock = new FakeRedisSocket("+OK\r\n")) 145 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 146 | { 147 | Assert.AreEqual("OK", redis.HMSet("test", "field1", "test1")); 148 | Assert.AreEqual("*4\r\n$5\r\nHMSET\r\n$4\r\ntest\r\n$6\r\nfield1\r\n$5\r\ntest1\r\n", mock.GetMessage()); 149 | } 150 | } 151 | 152 | [TestMethod, TestCategory("Hashes")] 153 | public void TestHMSet_Generic() 154 | { 155 | using (var mock = new FakeRedisSocket("+OK\r\n")) 156 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 157 | { 158 | Assert.AreEqual("OK", redis.HMSet("test", new { field1 = "test1" })); 159 | Assert.AreEqual("*4\r\n$5\r\nHMSET\r\n$4\r\ntest\r\n$6\r\nfield1\r\n$5\r\ntest1\r\n", mock.GetMessage()); 160 | } 161 | } 162 | 163 | [TestMethod, TestCategory("Hashes")] 164 | public void TestHMSet_Dictionary() 165 | { 166 | using (var mock = new FakeRedisSocket("+OK\r\n")) 167 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 168 | { 169 | Assert.AreEqual("OK", redis.HMSet("test", new Dictionary { { "field1", "test1" } })); 170 | Assert.AreEqual("*4\r\n$5\r\nHMSET\r\n$4\r\ntest\r\n$6\r\nfield1\r\n$5\r\ntest1\r\n", mock.GetMessage()); 171 | } 172 | } 173 | 174 | [TestMethod, TestCategory("Hashes")] 175 | public void TestHSet() 176 | { 177 | using (var mock = new FakeRedisSocket(":1\r\n")) 178 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 179 | { 180 | Assert.IsTrue(redis.HSet("test", "field1", "test1")); 181 | Assert.AreEqual("*4\r\n$4\r\nHSET\r\n$4\r\ntest\r\n$6\r\nfield1\r\n$5\r\ntest1\r\n", mock.GetMessage()); 182 | } 183 | } 184 | 185 | [TestMethod, TestCategory("Hashes")] 186 | public void TestHSetNX() 187 | { 188 | using (var mock = new FakeRedisSocket(":1\r\n")) 189 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 190 | { 191 | Assert.IsTrue(redis.HSetNx("test", "field1", "test1")); 192 | Assert.AreEqual("*4\r\n$6\r\nHSETNX\r\n$4\r\ntest\r\n$6\r\nfield1\r\n$5\r\ntest1\r\n", mock.GetMessage()); 193 | } 194 | } 195 | 196 | [TestMethod, TestCategory("Hashes")] 197 | public void TestHVals() 198 | { 199 | using (var mock = new FakeRedisSocket("*2\r\n$5\r\ntest1\r\n$5\r\ntest2\r\n")) 200 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 201 | { 202 | var response = redis.HVals("test"); 203 | Assert.AreEqual(2, response.Length); 204 | Assert.AreEqual("test1", response[0]); 205 | Assert.AreEqual("test2", response[1]); 206 | Assert.AreEqual("*2\r\n$5\r\nHVALS\r\n$4\r\ntest\r\n", mock.GetMessage()); 207 | } 208 | } 209 | 210 | [TestMethod, TestCategory("Hashes")] 211 | public void TestHScan() 212 | { 213 | string reply = "*2\r\n$2\r\n23\r\n*2\r\n$6\r\nfield1\r\n$5\r\ntest1\r\n"; 214 | using (var mock = new FakeRedisSocket(reply, reply, reply, reply)) 215 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 216 | { 217 | var response1 = redis.HScan("test", 0); 218 | Assert.AreEqual(23, response1.Cursor); 219 | Assert.AreEqual(1, response1.Items.Length); 220 | Assert.AreEqual("field1", response1.Items[0].Item1); 221 | Assert.AreEqual("test1", response1.Items[0].Item2); 222 | Assert.AreEqual("*3\r\n$5\r\nHSCAN\r\n$4\r\ntest\r\n$1\r\n0\r\n", mock.GetMessage(), "Basic test"); 223 | 224 | var response2 = redis.HScan("test", 0, pattern: "*"); 225 | Assert.AreEqual("*5\r\n$5\r\nHSCAN\r\n$4\r\ntest\r\n$1\r\n0\r\n$5\r\nMATCH\r\n$1\r\n*\r\n", mock.GetMessage(), "Pattern test"); 226 | 227 | var response3 = redis.HScan("test", 0, count: 5); 228 | Assert.AreEqual("*5\r\n$5\r\nHSCAN\r\n$4\r\ntest\r\n$1\r\n0\r\n$5\r\nCOUNT\r\n$1\r\n5\r\n", mock.GetMessage(), "Count test"); 229 | 230 | var response4 = redis.HScan("test", 0, "*", 5); 231 | Assert.AreEqual("*7\r\n$5\r\nHSCAN\r\n$4\r\ntest\r\n$1\r\n0\r\n$5\r\nMATCH\r\n$1\r\n*\r\n$5\r\nCOUNT\r\n$1\r\n5\r\n", mock.GetMessage(), "Pattern + Count test"); 232 | } 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /CSRedis.Tests/HyperLogLogTests.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal; 2 | using CSRedis.Internal.Fakes; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace CSRedis.Tests 12 | { 13 | [TestClass] 14 | public class HyperLogLogTests 15 | { 16 | [TestMethod, TestCategory("HyperLogLog")] 17 | public void PfAddTest() 18 | { 19 | using (var mock = new FakeRedisSocket(":1\r\n")) 20 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 21 | { 22 | Assert.IsTrue(redis.PfAdd("test", "test1", "test2")); 23 | Assert.AreEqual("*4\r\n$5\r\nPFADD\r\n$4\r\ntest\r\n$5\r\ntest1\r\n$5\r\ntest2\r\n", mock.GetMessage()); 24 | } 25 | } 26 | 27 | [TestMethod, TestCategory("HyperLogLog")] 28 | public void PfCountTest() 29 | { 30 | using (var mock = new FakeRedisSocket(":2\r\n")) 31 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 32 | { 33 | Assert.AreEqual(2, redis.PfCount("test1", "test2")); 34 | Assert.AreEqual("*3\r\n$7\r\nPFCOUNT\r\n$5\r\ntest1\r\n$5\r\ntest2\r\n", mock.GetMessage()); 35 | } 36 | } 37 | 38 | [TestMethod, TestCategory("HyperLogLog")] 39 | public void PfMergeTest() 40 | { 41 | using (var mock = new FakeRedisSocket("+OK\r\n")) 42 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 43 | { 44 | Assert.AreEqual("OK", redis.PfMerge("destination", "source1", "source2")); 45 | Assert.AreEqual("*4\r\n$7\r\nPFMERGE\r\n$11\r\ndestination\r\n$7\r\nsource1\r\n$7\r\nsource2\r\n", mock.GetMessage()); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /CSRedis.Tests/ListTests.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal; 2 | using CSRedis.Internal.Fakes; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace CSRedis.Tests 12 | { 13 | [TestClass] 14 | public class ListTests 15 | { 16 | [TestMethod, TestCategory("Lists")] 17 | public void TestBLPopWithKey() 18 | { 19 | string reply = "*2\r\n$4\r\ntest\r\n$5\r\ntest1\r\n"; 20 | using (var mock = new FakeRedisSocket(reply, reply)) 21 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 22 | { 23 | var response1 = redis.BLPopWithKey(60, "test"); 24 | Assert.AreEqual("test", response1.Item1); 25 | Assert.AreEqual("test1", response1.Item2); 26 | Assert.AreEqual("*3\r\n$5\r\nBLPOP\r\n$4\r\ntest\r\n$2\r\n60\r\n", mock.GetMessage()); 27 | 28 | var response2 = redis.BLPopWithKey(TimeSpan.FromMinutes(1), "test"); 29 | Assert.AreEqual("*3\r\n$5\r\nBLPOP\r\n$4\r\ntest\r\n$2\r\n60\r\n", mock.GetMessage()); 30 | } 31 | } 32 | 33 | [TestMethod, TestCategory("Lists")] 34 | public void TestBLPop() 35 | { 36 | string reply = "*2\r\n$4\r\ntest\r\n$5\r\ntest1\r\n"; 37 | using (var mock = new FakeRedisSocket(reply, reply)) 38 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 39 | { 40 | Assert.AreEqual("test1", redis.BLPop(60, "test")); 41 | Assert.AreEqual("*3\r\n$5\r\nBLPOP\r\n$4\r\ntest\r\n$2\r\n60\r\n", mock.GetMessage()); 42 | 43 | Assert.AreEqual("test1", redis.BLPop(TimeSpan.FromMinutes(1), "test")); 44 | Assert.AreEqual("*3\r\n$5\r\nBLPOP\r\n$4\r\ntest\r\n$2\r\n60\r\n", mock.GetMessage()); 45 | } 46 | } 47 | 48 | [TestMethod, TestCategory("Lists")] 49 | public void TestBRPopWithKey() 50 | { 51 | string reply = "*2\r\n$4\r\ntest\r\n$5\r\ntest1\r\n"; 52 | using (var mock = new FakeRedisSocket(reply, reply)) 53 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 54 | { 55 | var response1 = redis.BRPopWithKey(60, "test"); 56 | Assert.AreEqual("test", response1.Item1); 57 | Assert.AreEqual("test1", response1.Item2); 58 | Assert.AreEqual("*3\r\n$5\r\nBRPOP\r\n$4\r\ntest\r\n$2\r\n60\r\n", mock.GetMessage()); 59 | 60 | var response2 = redis.BRPopWithKey(TimeSpan.FromMinutes(1), "test"); 61 | Assert.AreEqual("*3\r\n$5\r\nBRPOP\r\n$4\r\ntest\r\n$2\r\n60\r\n", mock.GetMessage()); 62 | } 63 | } 64 | 65 | [TestMethod, TestCategory("Lists")] 66 | public void TestBRPop() 67 | { 68 | string reply = "*2\r\n$4\r\ntest\r\n$5\r\ntest1\r\n"; 69 | using (var mock = new FakeRedisSocket(reply, reply)) 70 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 71 | { 72 | Assert.AreEqual("test1", redis.BRPop(60, "test")); 73 | Assert.AreEqual("*3\r\n$5\r\nBRPOP\r\n$4\r\ntest\r\n$2\r\n60\r\n", mock.GetMessage()); 74 | 75 | Assert.AreEqual("test1", redis.BRPop(TimeSpan.FromMinutes(1), "test")); 76 | Assert.AreEqual("*3\r\n$5\r\nBRPOP\r\n$4\r\ntest\r\n$2\r\n60\r\n", mock.GetMessage()); 77 | } 78 | } 79 | 80 | [TestMethod, TestCategory("Lists")] 81 | public void TestBRPopLPush() 82 | { 83 | string reply = "$5\r\ntest1\r\n"; 84 | using (var mock = new FakeRedisSocket(reply, reply)) 85 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 86 | { 87 | Assert.AreEqual("test1", redis.BRPopLPush("test", "new", 60)); 88 | Assert.AreEqual("*4\r\n$10\r\nBRPOPLPUSH\r\n$4\r\ntest\r\n$3\r\nnew\r\n$2\r\n60\r\n", mock.GetMessage()); 89 | 90 | Assert.AreEqual("test1", redis.BRPopLPush("test", "new", TimeSpan.FromMinutes(1))); 91 | Assert.AreEqual("*4\r\n$10\r\nBRPOPLPUSH\r\n$4\r\ntest\r\n$3\r\nnew\r\n$2\r\n60\r\n", mock.GetMessage()); 92 | } 93 | } 94 | 95 | [TestMethod, TestCategory("Lists")] 96 | public void TestLIndex() 97 | { 98 | using (var mock = new FakeRedisSocket("$5\r\ntest1\r\n")) 99 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 100 | { 101 | Assert.AreEqual("test1", redis.LIndex("test", 0)); 102 | Assert.AreEqual("*3\r\n$6\r\nLINDEX\r\n$4\r\ntest\r\n$1\r\n0\r\n", mock.GetMessage()); 103 | } 104 | } 105 | 106 | [TestMethod, TestCategory("Lists")] 107 | public void TestLInsert() 108 | { 109 | string reply = ":2\r\n"; 110 | using (var mock = new FakeRedisSocket(reply, reply)) 111 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 112 | { 113 | Assert.AreEqual(2, redis.LInsert("test", RedisInsert.Before, "field1", "test1")); 114 | Assert.AreEqual("*5\r\n$7\r\nLINSERT\r\n$4\r\ntest\r\n$6\r\nBEFORE\r\n$6\r\nfield1\r\n$5\r\ntest1\r\n", mock.GetMessage()); 115 | 116 | Assert.AreEqual(2, redis.LInsert("test", RedisInsert.After, "field1", "test1")); 117 | Assert.AreEqual("*5\r\n$7\r\nLINSERT\r\n$4\r\ntest\r\n$5\r\nAFTER\r\n$6\r\nfield1\r\n$5\r\ntest1\r\n", mock.GetMessage()); 118 | } 119 | } 120 | 121 | [TestMethod, TestCategory("Lists")] 122 | public void TestLLen() 123 | { 124 | using (var mock = new FakeRedisSocket(":3\r\n")) 125 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 126 | { 127 | Assert.AreEqual(3, redis.LLen("test")); 128 | Assert.AreEqual("*2\r\n$4\r\nLLEN\r\n$4\r\ntest\r\n", mock.GetMessage()); 129 | } 130 | } 131 | 132 | [TestMethod, TestCategory("Lists")] 133 | public void TestLPop() 134 | { 135 | using (var mock = new FakeRedisSocket("$5\r\ntest1\r\n")) 136 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 137 | { 138 | Assert.AreEqual("test1", redis.LPop("test")); 139 | Assert.AreEqual("*2\r\n$4\r\nLPOP\r\n$4\r\ntest\r\n", mock.GetMessage()); 140 | } 141 | } 142 | 143 | [TestMethod, TestCategory("Lists")] 144 | public void TestLPush() 145 | { 146 | using (var mock = new FakeRedisSocket(":2\r\n")) 147 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 148 | { 149 | Assert.AreEqual(2, redis.LPush("test", "test1", "test2")); 150 | Assert.AreEqual("*4\r\n$5\r\nLPUSH\r\n$4\r\ntest\r\n$5\r\ntest1\r\n$5\r\ntest2\r\n", mock.GetMessage()); 151 | } 152 | } 153 | 154 | [TestMethod, TestCategory("Lists")] 155 | public void TestLPushX() 156 | { 157 | using (var mock = new FakeRedisSocket(":2\r\n")) 158 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 159 | { 160 | Assert.AreEqual(2, redis.LPushX("test", "test1")); 161 | Assert.AreEqual("*3\r\n$6\r\nLPUSHX\r\n$4\r\ntest\r\n$5\r\ntest1\r\n", mock.GetMessage()); 162 | } 163 | } 164 | 165 | [TestMethod, TestCategory("Lists")] 166 | public void TestLRange() 167 | { 168 | using (var mock = new FakeRedisSocket("*2\r\n$5\r\ntest1\r\n$5\r\ntest2\r\n")) 169 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 170 | { 171 | var response = redis.LRange("test", -10, 10); 172 | Assert.AreEqual(2, response.Length); 173 | Assert.AreEqual("test1", response[0]); 174 | Assert.AreEqual("test2", response[1]); 175 | Assert.AreEqual("*4\r\n$6\r\nLRANGE\r\n$4\r\ntest\r\n$3\r\n-10\r\n$2\r\n10\r\n", mock.GetMessage()); 176 | } 177 | } 178 | 179 | [TestMethod, TestCategory("Lists")] 180 | public void TestLRem() 181 | { 182 | using (var mock = new FakeRedisSocket(":2\r\n")) 183 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 184 | { 185 | Assert.AreEqual(2, redis.LRem("test", -2, "test1")); 186 | Assert.AreEqual("*4\r\n$4\r\nLREM\r\n$4\r\ntest\r\n$2\r\n-2\r\n$5\r\ntest1\r\n", mock.GetMessage()); 187 | } 188 | } 189 | 190 | [TestMethod, TestCategory("Lists")] 191 | public void TestLSet() 192 | { 193 | using (var mock = new FakeRedisSocket("+OK\r\n")) 194 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 195 | { 196 | Assert.AreEqual("OK", redis.LSet("test", 0, "test1")); 197 | Assert.AreEqual("*4\r\n$4\r\nLSET\r\n$4\r\ntest\r\n$1\r\n0\r\n$5\r\ntest1\r\n", mock.GetMessage()); 198 | } 199 | } 200 | 201 | [TestMethod, TestCategory("Lists")] 202 | public void TestLTrim() 203 | { 204 | using (var mock = new FakeRedisSocket("+OK\r\n")) 205 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 206 | { 207 | Assert.AreEqual("OK", redis.LTrim("test", 0, 3)); 208 | Assert.AreEqual("*4\r\n$5\r\nLTRIM\r\n$4\r\ntest\r\n$1\r\n0\r\n$1\r\n3\r\n", mock.GetMessage()); 209 | } 210 | } 211 | 212 | [TestMethod, TestCategory("Lists")] 213 | public void TestRPop() 214 | { 215 | using (var mock = new FakeRedisSocket("$5\r\ntest1\r\n")) 216 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 217 | { 218 | Assert.AreEqual("test1", redis.RPop("test")); 219 | Assert.AreEqual("*2\r\n$4\r\nRPOP\r\n$4\r\ntest\r\n", mock.GetMessage()); 220 | } 221 | } 222 | 223 | [TestMethod, TestCategory("Lists")] 224 | public void TestRPopLPush() 225 | { 226 | using (var mock = new FakeRedisSocket("$5\r\ntest1\r\n")) 227 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 228 | { 229 | Assert.AreEqual("test1", redis.RPopLPush("test", "new")); 230 | Assert.AreEqual("*3\r\n$9\r\nRPOPLPUSH\r\n$4\r\ntest\r\n$3\r\nnew\r\n", mock.GetMessage()); 231 | } 232 | } 233 | 234 | [TestMethod, TestCategory("Lists")] 235 | public void TestRPush() 236 | { 237 | using (var mock = new FakeRedisSocket(":3\r\n")) 238 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 239 | { 240 | Assert.AreEqual(3, redis.RPush("test", "test1")); 241 | Assert.AreEqual("*3\r\n$5\r\nRPUSH\r\n$4\r\ntest\r\n$5\r\ntest1\r\n", mock.GetMessage()); 242 | } 243 | } 244 | 245 | [TestMethod, TestCategory("Lists")] 246 | public void TestRPushX() 247 | { 248 | using (var mock = new FakeRedisSocket(":3\r\n")) 249 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 250 | { 251 | Assert.AreEqual(3, redis.RPushX("test", "test1")); 252 | Assert.AreEqual("*3\r\n$6\r\nRPUSHX\r\n$4\r\ntest\r\n$5\r\ntest1\r\n", mock.GetMessage()); 253 | } 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /CSRedis.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CSRedis.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CSRedis")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("1cb6fd5f-f699-4ce7-8592-e0c4d41df956")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /CSRedis.Tests/PubSubTests.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal; 2 | using CSRedis.Internal.Fakes; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace CSRedis.Tests 12 | { 13 | [TestClass] 14 | public class PubSubTests 15 | { 16 | /*[TestMethod, TestCategory("PubSub")] 17 | public void PSubscriptionTest() 18 | { 19 | using (var mock = new FakeRedisSocket(true, 20 | "*3\r\n$10\r\npsubscribe\r\n$2\r\nf*\r\n:1\r\n" 21 | + "*3\r\n$10\r\npsubscribe\r\n$2\r\ns*\r\n:2\r\n" 22 | + "*4\r\n$8\r\npmessage\r\n$2\r\nf*\r\n$5\r\nfirst\r\n$5\r\nHello\r\n", 23 | "*3\r\n$12\r\npunsubscribe\r\n$2\r\ns*\r\n:1\r\n*3\r\n$12\r\npunsubscribe\r\n$2\r\nf*\r\n:0\r\n")) 24 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 25 | { 26 | var changes = new List(); 27 | var messages = new List(); 28 | redis.SubscriptionChanged += (s,a) => changes.Add(a.Response); 29 | redis.SubscriptionReceived += (s, a) => messages.Add(a.Message); 30 | Task.Delay(500) 31 | .ContinueWith(t => redis.PUnsubscribe()) 32 | .ContinueWith(t => 33 | { 34 | Assert.AreEqual(4, changes.Count); 35 | Assert.AreEqual("f*", changes[0].Pattern); 36 | Assert.AreEqual(1, changes[0].Count); 37 | Assert.IsNull(changes[0].Channel); 38 | Assert.AreEqual("psubscribe", changes[0].Type); 39 | 40 | Assert.AreEqual("s*", changes[1].Pattern); 41 | Assert.AreEqual(2, changes[1].Count); 42 | Assert.IsNull(changes[1].Channel); 43 | Assert.AreEqual("psubscribe", changes[1].Type); 44 | 45 | Assert.AreEqual("s*", changes[2].Pattern); 46 | Assert.AreEqual(1, changes[2].Count); 47 | Assert.IsNull(changes[2].Channel); 48 | Assert.AreEqual("punsubscribe", changes[2].Type); 49 | 50 | Assert.AreEqual("f*", changes[3].Pattern); 51 | Assert.AreEqual(0, changes[3].Count); 52 | Assert.IsNull(changes[3].Channel); 53 | Assert.AreEqual("punsubscribe", changes[3].Type); 54 | 55 | Assert.AreEqual(1, messages.Count); 56 | Assert.AreEqual("f*", messages[0].Pattern); 57 | Assert.AreEqual("first", messages[0].Channel); 58 | Assert.AreEqual("Hello", messages[0].Body); 59 | Assert.AreEqual("pmessage", messages[0].Type); 60 | }); 61 | redis.PSubscribe("f*", "s*"); 62 | } 63 | }*/ 64 | 65 | [TestMethod, TestCategory("PubSub")] 66 | public void PublishTest() 67 | { 68 | using (var mock = new FakeRedisSocket(":3\r\n")) 69 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 70 | { 71 | Assert.AreEqual(3, redis.Publish("test", "message")); 72 | Assert.AreEqual("*3\r\n$7\r\nPUBLISH\r\n$4\r\ntest\r\n$7\r\nmessage\r\n", mock.GetMessage()); 73 | } 74 | } 75 | 76 | [TestMethod, TestCategory("PubSub")] 77 | public void PubSubChannelsTest() 78 | { 79 | using (var mock = new FakeRedisSocket("*2\r\n$5\r\ntest1\r\n$5\r\ntest2\r\n")) 80 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 81 | { 82 | var response = redis.PubSubChannels("pattern"); 83 | Assert.AreEqual(2, response.Length); 84 | Assert.AreEqual("test1", response[0]); 85 | Assert.AreEqual("test2", response[1]); 86 | Assert.AreEqual("*3\r\n$6\r\nPUBSUB\r\n$8\r\nCHANNELS\r\n$7\r\npattern\r\n", mock.GetMessage()); 87 | } 88 | } 89 | 90 | [TestMethod, TestCategory("PubSub")] 91 | public void PubSubNumSubTest() 92 | { 93 | using (var mock = new FakeRedisSocket("*4\r\n$5\r\ntest1\r\n:1\r\n$5\r\ntest2\r\n:5\r\n")) 94 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 95 | { 96 | var response = redis.PubSubNumSub("channel1", "channel2"); 97 | Assert.AreEqual(2, response.Length); 98 | Assert.AreEqual("test1", response[0].Item1); 99 | Assert.AreEqual(1, response[0].Item2); 100 | Assert.AreEqual("test2", response[1].Item1); 101 | Assert.AreEqual(5, response[1].Item2); 102 | Assert.AreEqual("*4\r\n$6\r\nPUBSUB\r\n$6\r\nNUMSUB\r\n$8\r\nchannel1\r\n$8\r\nchannel2\r\n", mock.GetMessage()); 103 | } 104 | } 105 | 106 | [TestMethod, TestCategory("PubSub")] 107 | public void PubSubNumPatTest() 108 | { 109 | using (var mock = new FakeRedisSocket(":3\r\n")) 110 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 111 | { 112 | Assert.AreEqual(3, redis.PubSubNumPat()); 113 | Assert.AreEqual("*2\r\n$6\r\nPUBSUB\r\n$6\r\nNUMPAT\r\n", mock.GetMessage()); 114 | } 115 | } 116 | 117 | /*[TestMethod, TestCategory("PubSub")] 118 | public void SubscriptionTest() 119 | { 120 | using (var mock = new FakeRedisSocket(true, 121 | "*3\r\n$9\r\nsubscribe\r\n$5\r\nfirst\r\n:1\r\n" 122 | + "*3\r\n$9\r\nsubscribe\r\n$6\r\nsecond\r\n:2\r\n" 123 | + "*3\r\n$7\r\nmessage\r\n$5\r\nfirst\r\n$5\r\nHello\r\n", 124 | "*3\r\n$11\r\nunsubscribe\r\n$6\r\nsecond\r\n:1\r\n*3\r\n$11\r\nunsubscribe\r\n$5\r\nfirst\r\n:0\r\n")) 125 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 126 | { 127 | var changes = new List(); 128 | var messages = new List(); 129 | redis.SubscriptionChanged += (s, a) => changes.Add(a.Response); 130 | redis.SubscriptionReceived += (s, a) => messages.Add(a.Message); 131 | Task.Delay(500) 132 | .ContinueWith(t => redis.Unsubscribe()) 133 | .ContinueWith(t => 134 | { 135 | Assert.AreEqual(4, changes.Count); 136 | Assert.AreEqual("first", changes[0].Channel); 137 | Assert.AreEqual(1, changes[0].Count); 138 | Assert.IsNull(changes[0].Pattern); 139 | Assert.AreEqual("subscribe", changes[0].Type); 140 | 141 | Assert.AreEqual("second", changes[1].Channel); 142 | Assert.AreEqual(2, changes[1].Count); 143 | Assert.IsNull(changes[1].Pattern); 144 | Assert.AreEqual("subscribe", changes[1].Type); 145 | 146 | Assert.AreEqual("second", changes[2].Channel); 147 | Assert.AreEqual(1, changes[2].Count); 148 | Assert.IsNull(changes[2].Pattern); 149 | Assert.AreEqual("unsubscribe", changes[2].Type); 150 | 151 | Assert.AreEqual("first", changes[3].Channel); 152 | Assert.AreEqual(0, changes[3].Count); 153 | Assert.IsNull(changes[3].Pattern); 154 | Assert.AreEqual("unsubscribe", changes[3].Type); 155 | 156 | Assert.AreEqual(1, messages.Count); 157 | Assert.IsNull(messages[0].Pattern); 158 | Assert.AreEqual("first", messages[0].Channel); 159 | Assert.AreEqual("Hello", messages[0].Body); 160 | Assert.AreEqual("message", messages[0].Type); 161 | }); 162 | redis.Subscribe("first", "second"); 163 | } 164 | }*/ 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /CSRedis.Tests/RegressionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using CSRedis.Internal; 4 | using CSRedis.Internal.Fakes; 5 | using System.Net; 6 | 7 | namespace CSRedis.Tests 8 | { 9 | [TestClass] 10 | public class RegressionTests 11 | { 12 | [TestMethod, TestCategory("Regression")] 13 | public void SetUTF8Test() 14 | { 15 | using (var mock = new FakeRedisSocket("+OK\r\n", "+OK\r\n")) 16 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 17 | { 18 | Assert.AreEqual("OK", redis.Set("test", "é")); 19 | Assert.AreEqual("*3\r\n$3\r\nSET\r\n$4\r\ntest\r\n$2\r\né\r\n", mock.GetMessage()); 20 | 21 | Assert.AreEqual("OK", redis.SetAsync("test", "é").Result); 22 | Assert.AreEqual("*3\r\n$3\r\nSET\r\n$4\r\ntest\r\n$2\r\né\r\n", mock.GetMessage()); 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CSRedis.Tests/ScriptingTests.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal; 2 | using CSRedis.Internal.Fakes; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace CSRedis.Tests 12 | { 13 | [TestClass] 14 | public class ScriptingTests 15 | { 16 | 17 | 18 | [TestMethod, TestCategory("Scripting")] 19 | public void EvalTest() 20 | { 21 | using (var mock = new FakeRedisSocket("*4\r\n$4\r\nkey1\r\n$4\r\nkey2\r\n$5\r\nfirst\r\n$6\r\nsecond\r\n")) 22 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 23 | { 24 | var response = redis.Eval("return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", new[] { "key1", "key2" }, "first", "second"); 25 | Assert.IsTrue(response is object[]); 26 | Assert.AreEqual(4, (response as object[]).Length); 27 | Assert.AreEqual("key1", (response as object[])[0]); 28 | Assert.AreEqual("key2", (response as object[])[1]); 29 | Assert.AreEqual("first", (response as object[])[2]); 30 | Assert.AreEqual("second", (response as object[])[3]); 31 | Assert.AreEqual("*7\r\n$4\r\nEVAL\r\n$40\r\nreturn {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}\r\n$1\r\n2\r\n$4\r\nkey1\r\n$4\r\nkey2\r\n$5\r\nfirst\r\n$6\r\nsecond\r\n", mock.GetMessage()); 32 | } 33 | } 34 | 35 | [TestMethod, TestCategory("Scripting")] 36 | public void EvalSHATest() 37 | { 38 | using (var mock = new FakeRedisSocket("*4\r\n$4\r\nkey1\r\n$4\r\nkey2\r\n$5\r\nfirst\r\n$6\r\nsecond\r\n")) 39 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 40 | { 41 | var response = redis.EvalSHA("checksum", new[] { "key1", "key2" }, "first", "second"); 42 | Assert.IsTrue(response is object[]); 43 | Assert.AreEqual(4, (response as object[]).Length); 44 | Assert.AreEqual("key1", (response as object[])[0]); 45 | Assert.AreEqual("key2", (response as object[])[1]); 46 | Assert.AreEqual("first", (response as object[])[2]); 47 | Assert.AreEqual("second", (response as object[])[3]); 48 | Assert.AreEqual("*7\r\n$7\r\nEVALSHA\r\n$8\r\nchecksum\r\n$1\r\n2\r\n$4\r\nkey1\r\n$4\r\nkey2\r\n$5\r\nfirst\r\n$6\r\nsecond\r\n", mock.GetMessage()); 49 | } 50 | } 51 | 52 | [TestMethod, TestCategory("Scripting")] 53 | public void ScriptExistsTests() 54 | { 55 | using (var mock = new FakeRedisSocket("*2\r\n:1\r\n:0\r\n")) 56 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 57 | { 58 | var response = redis.ScriptExists("checksum1", "checksum2"); 59 | Assert.AreEqual(2, response.Length); 60 | Assert.IsTrue(response[0]); 61 | Assert.IsFalse(response[1]); 62 | 63 | Assert.AreEqual("*4\r\n$6\r\nSCRIPT\r\n$6\r\nEXISTS\r\n$9\r\nchecksum1\r\n$9\r\nchecksum2\r\n", mock.GetMessage()); 64 | } 65 | } 66 | 67 | [TestMethod, TestCategory("Scripting")] 68 | public void ScriptFlushTest() 69 | { 70 | using (var mock = new FakeRedisSocket("+OK\r\n")) 71 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 72 | { 73 | Assert.AreEqual("OK", redis.ScriptFlush()); 74 | Assert.AreEqual("*2\r\n$6\r\nSCRIPT\r\n$5\r\nFLUSH\r\n", mock.GetMessage()); 75 | } 76 | } 77 | 78 | [TestMethod, TestCategory("Scripting")] 79 | public void ScriptKillTest() 80 | { 81 | using (var mock = new FakeRedisSocket("+OK\r\n")) 82 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 83 | { 84 | Assert.AreEqual("OK", redis.ScriptKill()); 85 | Assert.AreEqual("*2\r\n$6\r\nSCRIPT\r\n$4\r\nKILL\r\n", mock.GetMessage()); 86 | } 87 | } 88 | 89 | [TestMethod, TestCategory("Scripting")] 90 | public void ScriptLoadTest() 91 | { 92 | using (var mock = new FakeRedisSocket("$8\r\nchecksum\r\n")) 93 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 94 | { 95 | Assert.AreEqual("checksum", redis.ScriptLoad("return 1")); 96 | Assert.AreEqual("*3\r\n$6\r\nSCRIPT\r\n$4\r\nLOAD\r\n$8\r\nreturn 1\r\n", mock.GetMessage()); 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /CSRedis.Tests/SetTests.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal; 2 | using CSRedis.Internal.Fakes; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace CSRedis.Tests 12 | { 13 | [TestClass] 14 | public class SetTests 15 | { 16 | [TestMethod, TestCategory("Sets")] 17 | public void TestSAdd() 18 | { 19 | using (var mock = new FakeRedisSocket(":3\r\n")) 20 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 21 | { 22 | Assert.AreEqual(3, redis.SAdd("test", "test1")); 23 | Assert.AreEqual("*3\r\n$4\r\nSADD\r\n$4\r\ntest\r\n$5\r\ntest1\r\n", mock.GetMessage()); 24 | } 25 | } 26 | 27 | [TestMethod, TestCategory("Sets")] 28 | public void TestSCard() 29 | { 30 | using (var mock = new FakeRedisSocket(":3\r\n")) 31 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 32 | { 33 | Assert.AreEqual(3, redis.SCard("test")); 34 | Assert.AreEqual("*2\r\n$5\r\nSCARD\r\n$4\r\ntest\r\n", mock.GetMessage()); 35 | } 36 | } 37 | 38 | [TestMethod, TestCategory("Sets")] 39 | public void TestSDiff() 40 | { 41 | using (var mock = new FakeRedisSocket("*2\r\n$5\r\ntest1\r\n$5\r\ntest2\r\n")) 42 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 43 | { 44 | var response = redis.SDiff("test", "another"); 45 | Assert.AreEqual(2, response.Length); 46 | Assert.AreEqual("test1", response[0]); 47 | Assert.AreEqual("test2", response[1]); 48 | Assert.AreEqual("*3\r\n$5\r\nSDIFF\r\n$4\r\ntest\r\n$7\r\nanother\r\n", mock.GetMessage()); 49 | } 50 | } 51 | 52 | [TestMethod, TestCategory("Sets")] 53 | public void TestSDiffStore() 54 | { 55 | using (var mock = new FakeRedisSocket(":3\r\n")) 56 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 57 | { 58 | Assert.AreEqual(3, redis.SDiffStore("destination", "key1", "key2")); 59 | Assert.AreEqual("*4\r\n$10\r\nSDIFFSTORE\r\n$11\r\ndestination\r\n$4\r\nkey1\r\n$4\r\nkey2\r\n", mock.GetMessage()); 60 | } 61 | } 62 | 63 | [TestMethod, TestCategory("Sets")] 64 | public void TestInter() 65 | { 66 | using (var mock = new FakeRedisSocket("*2\r\n$5\r\ntest1\r\n$5\r\ntest2\r\n")) 67 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 68 | { 69 | var response = redis.SInter("test", "another"); 70 | Assert.AreEqual(2, response.Length); 71 | Assert.AreEqual("test1", response[0]); 72 | Assert.AreEqual("test2", response[1]); 73 | Assert.AreEqual("*3\r\n$6\r\nSINTER\r\n$4\r\ntest\r\n$7\r\nanother\r\n", mock.GetMessage()); 74 | } 75 | } 76 | 77 | [TestMethod, TestCategory("Sets")] 78 | public void TestSInterStore() 79 | { 80 | using (var mock = new FakeRedisSocket(":3\r\n")) 81 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 82 | { 83 | Assert.AreEqual(3, redis.SInterStore("destination", "key1", "key2")); 84 | Assert.AreEqual("*4\r\n$11\r\nSINTERSTORE\r\n$11\r\ndestination\r\n$4\r\nkey1\r\n$4\r\nkey2\r\n", mock.GetMessage()); 85 | } 86 | } 87 | 88 | [TestMethod, TestCategory("Sets")] 89 | public void TestSIsMember() 90 | { 91 | using (var mock = new FakeRedisSocket(":1\r\n")) 92 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 93 | { 94 | Assert.IsTrue(redis.SIsMember("test", "test1")); 95 | Assert.AreEqual("*3\r\n$9\r\nSISMEMBER\r\n$4\r\ntest\r\n$5\r\ntest1\r\n", mock.GetMessage()); 96 | } 97 | } 98 | 99 | [TestMethod, TestCategory("Sets")] 100 | public void TestSMembers() 101 | { 102 | using (var mock = new FakeRedisSocket("*2\r\n$5\r\ntest1\r\n$5\r\ntest2\r\n")) 103 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 104 | { 105 | var response = redis.SMembers("test"); 106 | Assert.AreEqual(2, response.Length); 107 | Assert.AreEqual("test1", response[0]); 108 | Assert.AreEqual("test2", response[1]); 109 | Assert.AreEqual("*2\r\n$8\r\nSMEMBERS\r\n$4\r\ntest\r\n", mock.GetMessage()); 110 | } 111 | } 112 | 113 | [TestMethod, TestCategory("Sets")] 114 | public void TestSMove() 115 | { 116 | using (var mock = new FakeRedisSocket(":1\r\n")) 117 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 118 | { 119 | Assert.IsTrue(redis.SMove("test", "destination", "test1")); 120 | Assert.AreEqual("*4\r\n$5\r\nSMOVE\r\n$4\r\ntest\r\n$11\r\ndestination\r\n$5\r\ntest1\r\n", mock.GetMessage()); 121 | } 122 | } 123 | 124 | [TestMethod, TestCategory("Sets")] 125 | public void TestSPop() 126 | { 127 | using (var mock = new FakeRedisSocket("$5\r\ntest1\r\n")) 128 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 129 | { 130 | Assert.AreEqual("test1", redis.SPop("test")); 131 | Assert.AreEqual("*2\r\n$4\r\nSPOP\r\n$4\r\ntest\r\n", mock.GetMessage()); 132 | } 133 | } 134 | 135 | [TestMethod, TestCategory("Sets")] 136 | public void TestSRandMember() 137 | { 138 | using (var mock = new FakeRedisSocket("$5\r\ntest1\r\n", "*2\r\n$5\r\ntest1\r\n$5\r\ntest2\r\n")) 139 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 140 | { 141 | Assert.AreEqual("test1", redis.SRandMember("test")); 142 | Assert.AreEqual("*2\r\n$11\r\nSRANDMEMBER\r\n$4\r\ntest\r\n", mock.GetMessage()); 143 | 144 | var response = redis.SRandMember("test", 2); 145 | Assert.AreEqual(2, response.Length); 146 | Assert.AreEqual("test1", response[0]); 147 | Assert.AreEqual("test2", response[1]); 148 | Assert.AreEqual("*3\r\n$11\r\nSRANDMEMBER\r\n$4\r\ntest\r\n$1\r\n2\r\n", mock.GetMessage()); 149 | } 150 | } 151 | 152 | [TestMethod, TestCategory("Sets")] 153 | public void TestSRem() 154 | { 155 | using (var mock = new FakeRedisSocket(":2\r\n")) 156 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 157 | { 158 | Assert.AreEqual(2, redis.SRem("test", "test1", "test2")); 159 | Assert.AreEqual("*4\r\n$4\r\nSREM\r\n$4\r\ntest\r\n$5\r\ntest1\r\n$5\r\ntest2\r\n", mock.GetMessage()); 160 | } 161 | } 162 | 163 | [TestMethod, TestCategory("Sets")] 164 | public void TestSScan() 165 | { 166 | string reply = "*2\r\n$2\r\n23\r\n*2\r\n$5\r\ntest1\r\n$5\r\ntest2\r\n"; 167 | using (var mock = new FakeRedisSocket(reply, reply, reply, reply)) 168 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 169 | { 170 | var response1 = redis.SScan("test", 0); 171 | Assert.AreEqual(23, response1.Cursor); 172 | Assert.AreEqual(2, response1.Items.Length); 173 | Assert.AreEqual("test1", response1.Items[0]); 174 | Assert.AreEqual("test2", response1.Items[1]); 175 | Assert.AreEqual("*3\r\n$5\r\nSSCAN\r\n$4\r\ntest\r\n$1\r\n0\r\n", mock.GetMessage(), "Basic test"); 176 | 177 | var response2 = redis.SScan("test", 0, pattern: "*"); 178 | Assert.AreEqual("*5\r\n$5\r\nSSCAN\r\n$4\r\ntest\r\n$1\r\n0\r\n$5\r\nMATCH\r\n$1\r\n*\r\n", mock.GetMessage(), "Pattern test"); 179 | 180 | var response3 = redis.SScan("test", 0, count: 5); 181 | Assert.AreEqual("*5\r\n$5\r\nSSCAN\r\n$4\r\ntest\r\n$1\r\n0\r\n$5\r\nCOUNT\r\n$1\r\n5\r\n", mock.GetMessage(), "Count test"); 182 | 183 | var response4 = redis.SScan("test", 0, "*", 5); 184 | Assert.AreEqual("*7\r\n$5\r\nSSCAN\r\n$4\r\ntest\r\n$1\r\n0\r\n$5\r\nMATCH\r\n$1\r\n*\r\n$5\r\nCOUNT\r\n$1\r\n5\r\n", mock.GetMessage(), "Pattern + Count test"); 185 | } 186 | } 187 | 188 | [TestMethod, TestCategory("Sets")] 189 | public void TestSUnion() 190 | { 191 | using (var mock = new FakeRedisSocket("*2\r\n$5\r\ntest1\r\n$5\r\ntest2\r\n")) 192 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 193 | { 194 | var response = redis.SUnion("test", "another"); 195 | Assert.AreEqual(2, response.Length); 196 | Assert.AreEqual("test1", response[0]); 197 | Assert.AreEqual("test2", response[1]); 198 | Assert.AreEqual("*3\r\n$6\r\nSUNION\r\n$4\r\ntest\r\n$7\r\nanother\r\n", mock.GetMessage()); 199 | } 200 | } 201 | 202 | [TestMethod, TestCategory("Sets")] 203 | public void TestSUnionStore() 204 | { 205 | using (var mock = new FakeRedisSocket(":3\r\n")) 206 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 207 | { 208 | Assert.AreEqual(3, redis.SUnionStore("destination", "key1", "key2")); 209 | Assert.AreEqual("*4\r\n$11\r\nSUNIONSTORE\r\n$11\r\ndestination\r\n$4\r\nkey1\r\n$4\r\nkey2\r\n", mock.GetMessage()); 210 | } 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /CSRedis.Tests/TransactionTests.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal; 2 | using CSRedis.Internal.Fakes; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace CSRedis.Tests 12 | { 13 | [TestClass] 14 | public class TransactionTests 15 | { 16 | [TestMethod, TestCategory("Transactions")] 17 | public void DiscardTest() 18 | { 19 | using (var mock = new FakeRedisSocket("+OK\r\n")) 20 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 21 | { 22 | Assert.AreEqual("OK", redis.Discard()); 23 | Assert.AreEqual("*1\r\n$7\r\nDISCARD\r\n", mock.GetMessage()); 24 | } 25 | } 26 | 27 | [TestMethod, TestCategory("Transactions")] 28 | public void ExecTest() 29 | { 30 | using (var mock = new FakeRedisSocket("*1\r\n$2\r\nhi\r\n")) 31 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 32 | { 33 | var response = redis.Exec(); 34 | Assert.AreEqual(1, response.Length); 35 | Assert.AreEqual("hi", response[0]); 36 | Assert.AreEqual("*1\r\n$4\r\nEXEC\r\n", mock.GetMessage()); 37 | } 38 | } 39 | 40 | [TestMethod, TestCategory("Transactions")] 41 | public void MultiTest() 42 | { 43 | using (var mock = new FakeRedisSocket("+OK\r\n")) 44 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 45 | { 46 | Assert.AreEqual("OK", redis.Multi()); 47 | Assert.AreEqual("*1\r\n$5\r\nMULTI\r\n", mock.GetMessage()); 48 | } 49 | } 50 | 51 | [TestMethod, TestCategory("Transactions")] 52 | public void UnwatchTest() 53 | { 54 | using (var mock = new FakeRedisSocket("+OK\r\n")) 55 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 56 | { 57 | Assert.AreEqual("OK", redis.Unwatch()); 58 | Assert.AreEqual("*1\r\n$7\r\nUNWATCH\r\n", mock.GetMessage()); 59 | } 60 | } 61 | 62 | [TestMethod, TestCategory("Transactions")] 63 | public void WatchTest() 64 | { 65 | using (var mock = new FakeRedisSocket("+OK\r\n")) 66 | using (var redis = new RedisClient(mock, new DnsEndPoint("fakehost", 9999))) 67 | { 68 | Assert.AreEqual("OK", redis.Watch()); 69 | Assert.AreEqual("*1\r\n$5\r\nWATCH\r\n", mock.GetMessage()); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /CSRedis.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30501.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSRedis", "CSRedis\CSRedis.csproj", "{D35E185E-A7E1-41E1-846C-21944F56074F}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSRedis.Tests", "CSRedis.Tests\CSRedis.Tests.csproj", "{422A4579-8C59-4E92-8EB5-2A45C38D27FB}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {D35E185E-A7E1-41E1-846C-21944F56074F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {D35E185E-A7E1-41E1-846C-21944F56074F}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {D35E185E-A7E1-41E1-846C-21944F56074F}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {D35E185E-A7E1-41E1-846C-21944F56074F}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {422A4579-8C59-4E92-8EB5-2A45C38D27FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {422A4579-8C59-4E92-8EB5-2A45C38D27FB}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {422A4579-8C59-4E92-8EB5-2A45C38D27FB}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {422A4579-8C59-4E92-8EB5-2A45C38D27FB}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /CSRedis/CSRedis.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Release 6 | AnyCPU 7 | {D35E185E-A7E1-41E1-846C-21944F56074F} 8 | Library 9 | Properties 10 | CSRedis 11 | csredis 12 | v4.0 13 | 512 14 | 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | bin\Debug\csredis.XML 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | bin\Release\csredis.XML 36 | 37 | 38 | 39 | 40 | 41 | true 42 | $(StrongNameKeyPath) 43 | 44 | 45 | true 46 | true 47 | ..\csredis.pub.snk 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | RedisClient.cs 105 | 106 | 107 | RedisClient.cs 108 | 109 | 110 | 111 | RedisSentinelClient.cs 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | RedisSentinelClient.cs 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 134 | -------------------------------------------------------------------------------- /CSRedis/CSRedis.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | http://www.apache.org/licenses/LICENSE-2.0.html 10 | https://github.com/ctstone/csredis 11 | false 12 | $description$ 13 | https://github.com/ctstone/csredis/blob/master/CHANGELOG.md 14 | Copyright 2014 15 | Redis Sentinel Async NoSQL 16 | 17 | -------------------------------------------------------------------------------- /CSRedis/Events.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace CSRedis 7 | { 8 | /// 9 | /// Provides data for the event that is raised when a subscription message is received 10 | /// 11 | public class RedisSubscriptionReceivedEventArgs : EventArgs 12 | { 13 | /// 14 | /// The subscription message 15 | /// 16 | public RedisSubscriptionMessage Message { get; private set; } 17 | 18 | internal RedisSubscriptionReceivedEventArgs(RedisSubscriptionMessage message) 19 | { 20 | Message = message; 21 | } 22 | } 23 | 24 | /// 25 | /// Provides data for the event that is raised when a subscription channel is opened or closed 26 | /// 27 | public class RedisSubscriptionChangedEventArgs : EventArgs 28 | { 29 | /// 30 | /// The subscription response 31 | /// 32 | public RedisSubscriptionChannel Response { get; private set; } 33 | 34 | internal RedisSubscriptionChangedEventArgs(RedisSubscriptionChannel response) 35 | { 36 | Response = response; 37 | } 38 | } 39 | 40 | /// 41 | /// Provides data for the event that is raised when a transaction command has been processed by the server 42 | /// 43 | public class RedisTransactionQueuedEventArgs : EventArgs 44 | { 45 | /// 46 | /// The status code of the transaction command 47 | /// 48 | public string Status { get; private set; } 49 | 50 | /// 51 | /// The command that was queued 52 | /// 53 | public string Command { get; private set; } 54 | 55 | /// 56 | /// The arguments of the queued command 57 | /// 58 | public object[] Arguments { get; private set; } 59 | 60 | internal RedisTransactionQueuedEventArgs(string status, string command, object[] arguments) 61 | { 62 | Status = status; 63 | Command = command; 64 | Arguments = arguments; 65 | } 66 | } 67 | 68 | /// 69 | /// Provides data for the event that is raised when a Redis MONITOR message is received 70 | /// 71 | public class RedisMonitorEventArgs : EventArgs 72 | { 73 | /// 74 | /// Monitor output 75 | /// 76 | public object Message { get; private set; } 77 | 78 | internal RedisMonitorEventArgs(object message) 79 | { 80 | Message = message; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /CSRedis/Exceptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CSRedis 4 | { 5 | /// 6 | /// Represents a Redis server error reply 7 | /// 8 | public class RedisException : RedisClientException 9 | { 10 | /// 11 | /// Instantiate a new instance of the RedisException class 12 | /// 13 | /// Server response 14 | public RedisException(string message) 15 | : base(message) 16 | { } 17 | } 18 | 19 | /// 20 | /// The exception that is thrown when an unexpected value is found in a Redis request or response 21 | /// 22 | public class RedisProtocolException : RedisClientException 23 | { 24 | /// 25 | /// Instantiate a new instance of the RedisProtocolException class 26 | /// 27 | /// Protocol violoation message 28 | public RedisProtocolException(string message) 29 | : base(message) 30 | { } 31 | } 32 | 33 | /// 34 | /// Exception thrown by RedisClient 35 | /// 36 | public class RedisClientException : Exception 37 | { 38 | /// 39 | /// Instantiate a new instance of the RedisClientException class 40 | /// 41 | /// Exception message 42 | public RedisClientException(string message) 43 | : base(message) 44 | { } 45 | 46 | /// 47 | /// Instantiate a new instance of the RedisClientException class 48 | /// 49 | /// Exception message 50 | /// Inner exception 51 | public RedisClientException(string message, Exception inner) 52 | : base(message, inner) 53 | { } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /CSRedis/IRedisClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace CSRedis 7 | { 8 | /// 9 | /// Common properties of the RedisClient 10 | /// 11 | public interface IRedisClient :IDisposable 12 | { 13 | /// 14 | /// Occurs when a subscription message is received 15 | /// 16 | event EventHandler SubscriptionReceived; 17 | 18 | /// 19 | /// Occurs when a subscription channel is added or removed 20 | /// 21 | event EventHandler SubscriptionChanged; 22 | 23 | /// 24 | /// Occurs when a transaction command is acknowledged by the server 25 | /// 26 | event EventHandler TransactionQueued; 27 | 28 | /// 29 | /// Occurs when a monitor message is received 30 | /// 31 | event EventHandler MonitorReceived; 32 | 33 | /// 34 | /// Occurs when the connection has sucessfully reconnected 35 | /// 36 | event EventHandler Connected; 37 | 38 | 39 | /// 40 | /// Get the Redis server hostname 41 | /// 42 | string Host { get; } 43 | 44 | /// 45 | /// Get the Redis server port 46 | /// 47 | int Port { get; } 48 | 49 | /// 50 | /// Get a value indicating whether the Redis client is connected to the server 51 | /// 52 | bool IsConnected { get; } 53 | 54 | /// 55 | /// Get or set the string encoding used to communicate with the server 56 | /// 57 | Encoding Encoding { get; set; } 58 | 59 | /// 60 | /// Get or set the connection read timeout (milliseconds) 61 | /// 62 | int ReceiveTimeout { get; set; } 63 | 64 | /// 65 | /// Get or set the connection send timeout (milliseconds) 66 | /// 67 | int SendTimeout { get; set; } 68 | 69 | /// 70 | /// Get or set the number of times to attempt a reconnect after a connection fails 71 | /// 72 | int ReconnectAttempts { get; set; } 73 | 74 | /// 75 | /// Get or set the amount of time (milliseconds) to wait between reconnect attempts 76 | /// 77 | int ReconnectWait { get; set; } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /CSRedis/Internal/Commands/RedisArray.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.IO; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace CSRedis.Internal.Commands 8 | { 9 | class RedisArray : RedisCommand 10 | { 11 | readonly Queue> _parsers = new Queue>(); 12 | 13 | public RedisArray(string command, params object[] args) 14 | : base(command, args) 15 | { } 16 | 17 | public override object[] Parse(RedisReader reader) 18 | { 19 | if (_parsers.Count == 0) 20 | return reader.ReadMultiBulk(bulkAsString: true); 21 | 22 | reader.ExpectType(RedisMessage.MultiBulk); 23 | long count = reader.ReadInt(false); 24 | if (count != _parsers.Count) 25 | throw new RedisProtocolException(String.Format("Expecting {0} array items; got {1}", _parsers.Count, count)); 26 | 27 | object[] results = new object[_parsers.Count]; 28 | for (int i = 0; i < results.Length; i++) 29 | results[i] = _parsers.Dequeue()(reader); 30 | return results; 31 | } 32 | 33 | public void AddParser(Func parser) 34 | { 35 | _parsers.Enqueue(parser); 36 | } 37 | 38 | public class Generic : RedisCommand 39 | { 40 | readonly RedisCommand _memberCommand; 41 | 42 | protected RedisCommand MemberCommand { get { return _memberCommand; } } 43 | 44 | public Generic(RedisCommand memberCommand) 45 | : base(memberCommand.Command, memberCommand.Arguments) 46 | { 47 | _memberCommand = memberCommand; 48 | } 49 | 50 | public override T[] Parse(RedisReader reader) 51 | { 52 | reader.ExpectType(RedisMessage.MultiBulk); 53 | long count = reader.ReadInt(false); 54 | return Read(count, reader); 55 | } 56 | 57 | protected virtual T[] Read(long count, RedisReader reader) 58 | { 59 | T[] array = new T[count]; 60 | for (int i = 0; i < array.Length; i++) 61 | array[i] = _memberCommand.Parse(reader); 62 | return array; 63 | } 64 | } 65 | 66 | public class IndexOf : RedisCommand 67 | { 68 | readonly RedisCommand _command; 69 | readonly int _index; 70 | 71 | public IndexOf(RedisCommand command, int index) 72 | : base(command.Command, command.Arguments) 73 | { 74 | _command = command; 75 | _index = index; 76 | } 77 | 78 | public override T Parse(RedisReader reader) 79 | { 80 | reader.ExpectType(RedisMessage.MultiBulk); 81 | long count = reader.ReadInt(false); 82 | T[] array = new T[count]; 83 | for (int i = 0; i < array.Length; i++) 84 | array[i] = _command.Parse(reader); 85 | return array[_index]; 86 | } 87 | } 88 | 89 | public class Strings : Generic 90 | { 91 | public Strings(string command, params object[] args) 92 | : base(new RedisString(command, args)) 93 | { } 94 | } 95 | 96 | public class StrongPairs : Generic> 97 | { 98 | public StrongPairs(RedisCommand command1, RedisCommand command2, string command, params object[] args) 99 | : base(new RedisTuple.Generic.Repeating(command1, command2, command, args)) 100 | { } 101 | 102 | protected override Tuple[] Read(long count, RedisReader reader) 103 | { 104 | var array = new Tuple[count / 2]; 105 | for (int i = 0; i < count; i += 2) 106 | array[i / 2] = MemberCommand.Parse(reader); 107 | return array; 108 | } 109 | } 110 | 111 | public class WeakPairs : StrongPairs 112 | { 113 | public WeakPairs(string command, params object[] args) 114 | : base(new RedisString.Converter(null), new RedisString.Converter(null), command, args) 115 | { } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /CSRedis/Internal/Commands/RedisBool.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.IO; 2 | using System.IO; 3 | 4 | namespace CSRedis.Internal.Commands 5 | { 6 | class RedisBool : RedisCommand 7 | { 8 | public RedisBool(string command, params object[] args) 9 | : base(command, args) 10 | { } 11 | 12 | public override bool Parse(RedisReader reader) 13 | { 14 | return reader.ReadInt() == 1; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CSRedis/Internal/Commands/RedisBytes.cs: -------------------------------------------------------------------------------- 1 |  2 | using CSRedis.Internal.IO; 3 | namespace CSRedis.Internal.Commands 4 | { 5 | class RedisBytes : RedisCommand 6 | { 7 | public RedisBytes(string command, params object[] args) 8 | : base(command, args) 9 | { } 10 | 11 | public override byte[] Parse(RedisReader reader) 12 | { 13 | return reader.ReadBulkBytes(true); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CSRedis/Internal/Commands/RedisDate.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.IO; 2 | using System; 3 | using System.IO; 4 | 5 | namespace CSRedis.Internal.Commands 6 | { 7 | class RedisDate : RedisCommand 8 | { 9 | static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 10 | 11 | public RedisDate(string command, params object[] args) 12 | : base(command, args) 13 | { } 14 | 15 | public override DateTime Parse(RedisReader reader) 16 | { 17 | return FromTimestamp(reader.ReadInt()); 18 | } 19 | 20 | public class Micro : RedisCommand 21 | { 22 | public Micro(string command, params object[] args) 23 | : base(command, args) 24 | { } 25 | 26 | public override DateTime Parse(RedisReader reader) 27 | { 28 | reader.ExpectType(RedisMessage.MultiBulk); 29 | reader.ExpectSize(2); 30 | 31 | int timestamp = Int32.Parse(reader.ReadBulkString()); 32 | int microseconds = Int32.Parse(reader.ReadBulkString()); 33 | 34 | return FromTimestamp(timestamp, microseconds); 35 | } 36 | 37 | public static DateTime FromTimestamp(long timestamp, long microseconds) 38 | { 39 | return RedisDate.FromTimestamp(timestamp) + FromMicroseconds(microseconds); 40 | } 41 | 42 | 43 | public static TimeSpan FromMicroseconds(long microseconds) 44 | { 45 | return TimeSpan.FromTicks(microseconds * (TimeSpan.TicksPerMillisecond / 1000)); 46 | } 47 | 48 | public static long ToMicroseconds(TimeSpan span) 49 | { 50 | return span.Ticks / (TimeSpan.TicksPerMillisecond / 1000); 51 | } 52 | } 53 | 54 | public static DateTime FromTimestamp(long seconds) 55 | { 56 | return _epoch + TimeSpan.FromSeconds(seconds); 57 | } 58 | 59 | public static TimeSpan ToTimestamp(DateTime date) 60 | { 61 | return date - _epoch; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /CSRedis/Internal/Commands/RedisFloat.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.IO; 2 | using System; 3 | using System.Globalization; 4 | 5 | namespace CSRedis.Internal.Commands 6 | { 7 | class RedisFloat : RedisCommand 8 | { 9 | public RedisFloat(string command, params object[] args) 10 | : base(command, args) 11 | { } 12 | 13 | public override double Parse(RedisReader reader) 14 | { 15 | return FromString(reader.ReadBulkString()); 16 | } 17 | 18 | static double FromString(string input) 19 | { 20 | return Double.Parse(input, NumberStyles.Float, CultureInfo.InvariantCulture); 21 | } 22 | 23 | public class Nullable : RedisCommand 24 | { 25 | public Nullable(string command, params object[] args) 26 | : base(command, args) 27 | { } 28 | 29 | public override double? Parse(RedisReader reader) 30 | { 31 | string result = reader.ReadBulkString(); 32 | if (result == null) 33 | return null; 34 | return RedisFloat.FromString(result); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /CSRedis/Internal/Commands/RedisHash.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.IO; 2 | using CSRedis.Internal.Utilities; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.IO; 7 | using System.Reflection; 8 | 9 | namespace CSRedis.Internal.Commands 10 | { 11 | class RedisHash : RedisCommand> 12 | { 13 | public RedisHash(string command, params object[] args) 14 | : base(command, args) 15 | { } 16 | 17 | public override Dictionary Parse(RedisReader reader) 18 | { 19 | return ToDict(reader); 20 | } 21 | 22 | static Dictionary ToDict(RedisReader reader) 23 | { 24 | reader.ExpectType(RedisMessage.MultiBulk); 25 | long count = reader.ReadInt(false); 26 | var dict = new Dictionary(); 27 | string key = String.Empty; 28 | for (int i = 0; i < count; i++) 29 | { 30 | if (i % 2 == 0) 31 | key = reader.ReadBulkString(); 32 | else 33 | dict[key] = reader.ReadBulkString(); 34 | } 35 | return dict; 36 | } 37 | 38 | public class Generic : RedisCommand 39 | where T : class 40 | { 41 | public Generic(string command, params object[] args) 42 | : base(command, args) 43 | { } 44 | 45 | public override T Parse(RedisReader reader) 46 | { 47 | return Serializer.Deserialize(RedisHash.ToDict(reader)); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /CSRedis/Internal/Commands/RedisInt.cs: -------------------------------------------------------------------------------- 1 |  2 | using CSRedis.Internal.IO; 3 | namespace CSRedis.Internal.Commands 4 | { 5 | class RedisInt : RedisCommand 6 | { 7 | public RedisInt(string command, params object[] args) 8 | : base(command, args) 9 | { } 10 | 11 | public override long Parse(RedisReader reader) 12 | { 13 | return reader.ReadInt(); 14 | } 15 | 16 | public class Nullable : RedisCommand 17 | { 18 | public Nullable(string command, params object[] args) 19 | : base(command, args) 20 | { } 21 | 22 | 23 | public override long? Parse(RedisReader reader) 24 | { 25 | RedisMessage type = reader.ReadType(); 26 | if (type == RedisMessage.Int) 27 | return reader.ReadInt(false); 28 | reader.ReadBulkString(false); 29 | return null; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /CSRedis/Internal/Commands/RedisIsMasterDownByAddrCommand.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.IO; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace CSRedis.Internal.Commands 8 | { 9 | class RedisIsMasterDownByAddrCommand : RedisCommand 10 | { 11 | public RedisIsMasterDownByAddrCommand(string command, params object[] args) 12 | : base(command, args) 13 | { } 14 | 15 | public override RedisMasterState Parse(RedisReader reader) 16 | { 17 | reader.ExpectType(RedisMessage.MultiBulk); 18 | reader.ExpectSize(3); 19 | long down_state = reader.ReadInt(); 20 | string leader = reader.ReadBulkString(); 21 | long vote_epoch = reader.ReadInt(); 22 | return new RedisMasterState(down_state, leader, vote_epoch); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CSRedis/Internal/Commands/RedisObject.cs: -------------------------------------------------------------------------------- 1 |  2 | using CSRedis.Internal.IO; 3 | namespace CSRedis.Internal.Commands 4 | { 5 | class RedisObject : RedisCommand 6 | { 7 | public RedisObject(string command, params object[] args) 8 | : base(command, args) 9 | { } 10 | 11 | public override object Parse(RedisReader reader) 12 | { 13 | return reader.Read(); 14 | } 15 | 16 | public class Strings : RedisCommand 17 | { 18 | public Strings(string command, params object[] args) 19 | : base(command, args) 20 | { } 21 | 22 | public override object Parse(RedisReader reader) 23 | { 24 | return reader.Read(true); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CSRedis/Internal/Commands/RedisRoleCommand.cs: -------------------------------------------------------------------------------- 1 |  2 | using CSRedis.Internal.IO; 3 | using CSRedis.Internal.Utilities; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using System.Globalization; 8 | using System.IO; 9 | namespace CSRedis.Internal.Commands 10 | { 11 | class RedisRoleCommand : RedisCommand 12 | { 13 | public RedisRoleCommand(string command, params object[] args) 14 | : base(command, args) 15 | { } 16 | 17 | public override RedisRole Parse(RedisReader reader) 18 | { 19 | reader.ExpectType(RedisMessage.MultiBulk); 20 | int count = (int)reader.ReadInt(false); 21 | 22 | string role = reader.ReadBulkString(); 23 | switch (role) 24 | { 25 | case "master": 26 | return ParseMaster(count, role, reader); 27 | case "slave": 28 | return ParseSlave(count, role, reader); 29 | case "sentinel": 30 | return ParseSentinel(count, role, reader); 31 | default: 32 | throw new RedisProtocolException("Unexpected role: " + role); 33 | } 34 | } 35 | 36 | static RedisMasterRole ParseMaster(int num, string role, RedisReader reader) 37 | { 38 | reader.ExpectSize(3, num); 39 | long offset = reader.ReadInt(); 40 | reader.ExpectType(RedisMessage.MultiBulk); 41 | var slaves = new Tuple[reader.ReadInt(false)]; 42 | for (int i = 0; i < slaves.Length; i++) 43 | { 44 | reader.ExpectType(RedisMessage.MultiBulk); 45 | reader.ExpectSize(3); 46 | string ip = reader.ReadBulkString(); 47 | int port = Int32.Parse(reader.ReadBulkString()); 48 | int slave_offset = Int32.Parse(reader.ReadBulkString()); 49 | slaves[i] = new Tuple(ip, port, slave_offset); 50 | } 51 | return new RedisMasterRole(role, offset, slaves); 52 | } 53 | static RedisSlaveRole ParseSlave(int num, string role, RedisReader reader) 54 | { 55 | reader.ExpectSize(5, num); 56 | string master_ip = reader.ReadBulkString(); 57 | int port = (int)reader.ReadInt(); 58 | string state = reader.ReadBulkString(); 59 | long data = reader.ReadInt(); 60 | return new RedisSlaveRole(role, master_ip, port, state, data); 61 | } 62 | static RedisSentinelRole ParseSentinel(int num, string role, RedisReader reader) 63 | { 64 | reader.ExpectSize(2, num); 65 | reader.ExpectType(RedisMessage.MultiBulk); 66 | string[] masters = new string[reader.ReadInt(false)]; 67 | for (int i = 0; i < masters.Length; i++) 68 | masters[i] = reader.ReadBulkString(); 69 | return new RedisSentinelRole(role, masters); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /CSRedis/Internal/Commands/RedisScanCommand.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.IO; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace CSRedis.Internal.Commands 9 | { 10 | class RedisScanCommand: RedisCommand> 11 | { 12 | RedisCommand _command; 13 | 14 | public RedisScanCommand(RedisCommand command) 15 | : base(command.Command, command.Arguments) 16 | { 17 | _command = command; 18 | } 19 | 20 | public override RedisScan Parse(RedisReader reader) 21 | { 22 | reader.ExpectType(RedisMessage.MultiBulk); 23 | if (reader.ReadInt(false) != 2) 24 | throw new RedisProtocolException("Expected 2 items"); 25 | 26 | long cursor = Int64.Parse(reader.ReadBulkString()); 27 | T[] items = _command.Parse(reader); 28 | 29 | return new RedisScan(cursor, items); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CSRedis/Internal/Commands/RedisSlowLogCommand.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.IO; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace CSRedis.Internal.Commands 8 | { 9 | class RedisSlowLogCommand : RedisCommand 10 | { 11 | public RedisSlowLogCommand(string command, object[] args) 12 | : base(command, args) 13 | { } 14 | 15 | public override RedisSlowLogEntry Parse(RedisReader reader) 16 | { 17 | reader.ExpectType(RedisMessage.MultiBulk); 18 | reader.ExpectSize(4); 19 | long id = reader.ReadInt(); 20 | long timestamp = reader.ReadInt(); 21 | long microseconds = reader.ReadInt(); 22 | reader.ExpectType(RedisMessage.MultiBulk); 23 | string[] arguments = new string[reader.ReadInt(false)]; 24 | for (int i = 0; i < arguments.Length; i++) 25 | arguments[i] = reader.ReadBulkString(); 26 | 27 | return new RedisSlowLogEntry(id, RedisDate.FromTimestamp(timestamp), RedisDate.Micro.FromMicroseconds(microseconds), arguments); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CSRedis/Internal/Commands/RedisStatus.cs: -------------------------------------------------------------------------------- 1 |  2 | using CSRedis.Internal.IO; 3 | using CSRedis.Internal.Utilities; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using System.Globalization; 8 | using System.IO; 9 | namespace CSRedis.Internal.Commands 10 | { 11 | class RedisStatus : RedisCommand 12 | { 13 | public RedisStatus(string command, params object[] args) 14 | : base(command, args) 15 | { } 16 | 17 | public override string Parse(RedisReader reader) 18 | { 19 | return reader.ReadStatus(); 20 | } 21 | 22 | public class Empty : RedisCommand 23 | { 24 | public Empty(string command, params object[] args) 25 | : base(command, args) 26 | { } 27 | public override string Parse(RedisReader reader) 28 | { 29 | RedisMessage type = reader.ReadType(); 30 | if ((int)type == -1) 31 | return String.Empty; 32 | else if (type == RedisMessage.Error) 33 | throw new RedisException(reader.ReadStatus(false)); 34 | 35 | throw new RedisProtocolException("Unexpected type: " + type); 36 | } 37 | } 38 | 39 | public class Nullable : RedisCommand 40 | { 41 | public Nullable(string command, params object[] args) 42 | : base(command, args) 43 | { } 44 | 45 | public override string Parse(RedisReader reader) 46 | { 47 | RedisMessage type = reader.ReadType(); 48 | if (type == RedisMessage.Status) 49 | return reader.ReadStatus(false); 50 | 51 | object[] result = reader.ReadMultiBulk(false); 52 | if (result != null) 53 | throw new RedisProtocolException("Expecting null MULTI BULK response. Received: " + result.ToString()); 54 | 55 | return null; 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /CSRedis/Internal/Commands/RedisString.cs: -------------------------------------------------------------------------------- 1 |  2 | using CSRedis.Internal.IO; 3 | using System; 4 | using System.ComponentModel; 5 | namespace CSRedis.Internal.Commands 6 | { 7 | class RedisString : RedisCommand 8 | { 9 | public RedisString(string command, params object[] args) 10 | : base(command, args) 11 | { } 12 | 13 | public override string Parse(RedisReader reader) 14 | { 15 | return reader.ReadBulkString(); 16 | } 17 | 18 | public class Nullable : RedisString 19 | { 20 | public Nullable(string command, params object[] args) 21 | : base(command, args) 22 | { } 23 | 24 | public override string Parse(RedisReader reader) 25 | { 26 | RedisMessage type = reader.ReadType(); 27 | if (type == RedisMessage.Bulk) 28 | return reader.ReadBulkString(false); 29 | reader.ReadMultiBulk(false); 30 | return null; 31 | } 32 | } 33 | 34 | public class Integer : RedisCommand 35 | { 36 | public Integer(string command, params object[] args) 37 | : base(command, args) 38 | { } 39 | 40 | public override int Parse(RedisReader reader) 41 | { 42 | return Int32.Parse(reader.ReadBulkString()); 43 | } 44 | } 45 | 46 | public class Converter : RedisCommand 47 | { 48 | static Lazy converter 49 | = new Lazy(() => TypeDescriptor.GetConverter(typeof(T))); 50 | 51 | public Converter(string command, params object[] args) 52 | : base(command, args) 53 | { } 54 | 55 | public override T Parse(RedisReader reader) 56 | { 57 | return (T)converter.Value.ConvertFromInvariantString(reader.ReadBulkString()); 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /CSRedis/Internal/Commands/RedisSubscription.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.IO; 2 | using System.IO; 3 | 4 | namespace CSRedis.Internal.Commands 5 | { 6 | class RedisSubscription : RedisCommand 7 | { 8 | public RedisSubscription(string command, params object[] args) 9 | : base(command, args) 10 | { } 11 | 12 | public override RedisSubscriptionResponse Parse(RedisReader reader) 13 | { 14 | reader.ExpectType(RedisMessage.MultiBulk); 15 | long count = reader.ReadInt(false); 16 | string type = reader.ReadBulkString(); 17 | switch (type) 18 | { 19 | case "subscribe": 20 | case "unsubscribe": 21 | case "psubscribe": 22 | case "punsubscribe": 23 | return ParseChannel(type, reader); 24 | 25 | case "message": 26 | case "pmessage": 27 | return ParseMessage(type, reader); 28 | } 29 | 30 | throw new RedisProtocolException("Unexpected type " + type); 31 | } 32 | 33 | static RedisSubscriptionChannel ParseChannel(string type, RedisReader reader) 34 | { 35 | switch (type) 36 | { 37 | case "subscribe": 38 | case "unsubscribe": 39 | return new RedisSubscriptionChannel(type, reader.ReadBulkString(), null, reader.ReadInt()); 40 | 41 | case "psubscribe": 42 | case "punsubscribe": 43 | return new RedisSubscriptionChannel(type, null, reader.ReadBulkString(), reader.ReadInt()); 44 | } 45 | 46 | throw new RedisProtocolException("Unexpected type " + type); 47 | } 48 | 49 | static RedisSubscriptionMessage ParseMessage(string type, RedisReader reader) 50 | { 51 | if (type == "message") 52 | return new RedisSubscriptionMessage(type, reader.ReadBulkString(), reader.ReadBulkString()); 53 | 54 | else if (type == "pmessage") 55 | return new RedisSubscriptionMessage(type, reader.ReadBulkString(), reader.ReadBulkString(), reader.ReadBulkString()); 56 | 57 | throw new RedisProtocolException("Unexpected type " + type); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /CSRedis/Internal/Commands/RedisTuple.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.IO; 2 | using System; 3 | using System.ComponentModel; 4 | using System.IO; 5 | 6 | namespace CSRedis.Internal.Commands 7 | { 8 | class RedisTuple : RedisCommand> 9 | { 10 | public RedisTuple(string command, params object[] args) 11 | : base(command, args) 12 | { } 13 | 14 | public override Tuple Parse(RedisReader reader) 15 | { 16 | reader.ExpectType(RedisMessage.MultiBulk); 17 | reader.ExpectSize(2); 18 | return Tuple.Create(reader.ReadBulkString(), reader.ReadBulkString()); 19 | } 20 | 21 | public abstract class Generic : RedisCommand> 22 | { 23 | readonly RedisCommand _command1; 24 | readonly RedisCommand _command2; 25 | 26 | protected Generic(RedisCommand command1, RedisCommand command2, string command, params object[] args) 27 | : base(command, args) 28 | { 29 | _command1 = command1; 30 | _command2 = command2; 31 | } 32 | 33 | protected Tuple Create(RedisReader reader) 34 | { 35 | return Tuple.Create(_command1.Parse(reader), _command2.Parse(reader)); 36 | } 37 | 38 | public class Repeating : Generic 39 | { 40 | public Repeating(RedisCommand command1, RedisCommand command2, string command, params object[] args) 41 | : base(command1, command2, command, args) 42 | { } 43 | 44 | public override Tuple Parse(RedisReader reader) 45 | { 46 | return Create(reader); 47 | } 48 | } 49 | 50 | public class Single : Generic 51 | { 52 | public Single(RedisCommand command1, RedisCommand command2, string command, params object[] args) 53 | : base(command1, command2, command, args) 54 | { } 55 | 56 | public override Tuple Parse(RedisReader reader) 57 | { 58 | reader.ExpectMultiBulk(2); 59 | return Create(reader); 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /CSRedis/Internal/Fakes/FakeRedisSocket.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.IO; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | using System.Text; 9 | 10 | namespace CSRedis.Internal.Fakes 11 | { 12 | class FakeRedisSocket : IRedisSocket 13 | { 14 | bool _connected; 15 | readonly FakeStream _stream; 16 | 17 | public bool Connected { get { return _connected; } } 18 | 19 | public int ReceiveTimeout { get; set; } 20 | 21 | public int SendTimeout { get; set; } 22 | 23 | public FakeRedisSocket(params string[] responses) 24 | : this(Encoding.UTF8, responses) 25 | { } 26 | 27 | public FakeRedisSocket(Encoding encoding, params string[] responses) 28 | : this(ToBytes(encoding, responses)) 29 | { } 30 | 31 | public FakeRedisSocket(params byte[][] responses) 32 | { 33 | _stream = new FakeStream(); 34 | foreach (var response in responses) 35 | _stream.AddResponse(response); 36 | } 37 | 38 | public void Connect(EndPoint endpoint) 39 | { 40 | _connected = true; 41 | } 42 | 43 | public bool ConnectAsync(SocketAsyncEventArgs args) 44 | { 45 | return false; 46 | } 47 | 48 | public bool SendAsync(SocketAsyncEventArgs args) 49 | { 50 | _stream.Write(args.Buffer, args.Offset, args.Count); 51 | return false; 52 | } 53 | 54 | public Stream GetStream() 55 | { 56 | return _stream; 57 | } 58 | 59 | public void Dispose() 60 | { 61 | _stream.Dispose(); 62 | _connected = false; 63 | } 64 | 65 | public string GetMessage() 66 | { 67 | return GetMessage(Encoding.UTF8); 68 | } 69 | public string GetMessage(Encoding encoding) 70 | { 71 | return encoding.GetString(_stream.GetMessage()); 72 | } 73 | 74 | static byte[][] ToBytes(Encoding encoding, string[] strings) 75 | { 76 | byte[][] set = new byte[strings.Length][]; 77 | for (int i = 0; i < strings.Length; i++) 78 | set[i] = encoding.GetBytes(strings[i]); 79 | return set; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /CSRedis/Internal/Fakes/FakeStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace CSRedis.Internal.Fakes 8 | { 9 | class FakeStream : MemoryStream 10 | { 11 | readonly Queue _responses; 12 | readonly Queue _messages; 13 | 14 | public FakeStream() 15 | { 16 | _responses = new Queue(); 17 | _messages = new Queue(); 18 | } 19 | 20 | public void AddResponse(byte[] response) 21 | { 22 | _responses.Enqueue(response); 23 | } 24 | 25 | public byte[] GetMessage() 26 | { 27 | return _messages.Dequeue(); 28 | } 29 | 30 | public override void Write(byte[] buffer, int offset, int count) 31 | { 32 | lock (_responses) 33 | { 34 | byte[] message = new byte[count]; 35 | Buffer.BlockCopy(buffer, offset, message, 0, count); 36 | _messages.Enqueue(message); 37 | base.Write(buffer, offset, count); 38 | 39 | byte[] next = _responses.Dequeue(); 40 | base.Write(next, 0, next.Length); 41 | Position -= next.Length; 42 | } 43 | } 44 | 45 | 46 | 47 | // todo: use Monitor.Wait() to simulate blocking thread on Read() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /CSRedis/Internal/IO/AsyncConnector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace CSRedis.Internal.IO 11 | { 12 | class AsyncConnector : IDisposable 13 | { 14 | readonly SocketAsyncEventArgs _asyncConnectArgs; 15 | readonly SocketAsyncPool _asyncTransferPool; 16 | readonly ConcurrentQueue _asyncReadQueue; 17 | readonly ConcurrentQueue _asyncWriteQueue; 18 | readonly object _readLock; 19 | readonly object _writeLock; 20 | readonly int _concurrency; 21 | readonly int _bufferSize; 22 | readonly IRedisSocket _redisSocket; 23 | readonly RedisIO _io; 24 | 25 | bool _asyncConnectionStarted; 26 | TaskCompletionSource _connectionTaskSource; 27 | 28 | public event EventHandler Connected; 29 | 30 | 31 | public AsyncConnector(IRedisSocket socket, EndPoint endpoint, RedisIO io, int concurrency, int bufferSize) 32 | { 33 | _redisSocket = socket; 34 | _io = io; 35 | _concurrency = concurrency; 36 | _bufferSize = bufferSize; 37 | _asyncTransferPool = new SocketAsyncPool(concurrency, bufferSize); 38 | _asyncTransferPool.Completed += OnSocketCompleted; 39 | _asyncReadQueue = new ConcurrentQueue(); 40 | _asyncWriteQueue = new ConcurrentQueue(); 41 | _readLock = new object(); 42 | _writeLock = new object(); 43 | _asyncConnectArgs = new SocketAsyncEventArgs { RemoteEndPoint = endpoint }; 44 | _asyncConnectArgs.Completed += OnSocketCompleted; 45 | _connectionTaskSource = new TaskCompletionSource(); 46 | } 47 | 48 | public Task ConnectAsync() 49 | { 50 | if (_redisSocket.Connected) 51 | _connectionTaskSource.SetResult(true); 52 | 53 | if (!_asyncConnectionStarted && !_redisSocket.Connected) 54 | { 55 | lock (_asyncConnectArgs) 56 | { 57 | if (!_asyncConnectionStarted && !_redisSocket.Connected) 58 | { 59 | _asyncConnectionStarted = true; 60 | if (!_redisSocket.ConnectAsync(_asyncConnectArgs)) 61 | OnSocketConnected(_asyncConnectArgs); 62 | } 63 | } 64 | } 65 | 66 | return _connectionTaskSource.Task; 67 | } 68 | 69 | public Task CallAsync(RedisCommand command) 70 | { 71 | var token = new RedisAsyncCommandToken(command); 72 | _asyncWriteQueue.Enqueue(token); 73 | ConnectAsync().ContinueWith(CallAsyncDeferred); 74 | return token.TaskSource.Task; 75 | } 76 | 77 | void InitConnection() 78 | { 79 | if (_connectionTaskSource != null) 80 | _connectionTaskSource.TrySetResult(false); 81 | 82 | _connectionTaskSource = new TaskCompletionSource(); 83 | } 84 | 85 | void CallAsyncDeferred(Task t) 86 | { 87 | lock (_writeLock) 88 | { 89 | IRedisAsyncCommandToken token; 90 | if (!_asyncWriteQueue.TryDequeue(out token)) 91 | throw new Exception(); 92 | 93 | _asyncReadQueue.Enqueue(token); 94 | 95 | var args = _asyncTransferPool.Acquire(); 96 | int bytes; 97 | try 98 | { 99 | bytes = _io.Writer.Write(token.Command, args.Buffer, args.Offset); 100 | } 101 | catch (ArgumentException e) 102 | { 103 | throw new RedisClientException("Could not write command '" + token.Command.Command + "'. Argument size exceeds buffer allocation of " + args.Count + ".", e); 104 | } 105 | args.SetBuffer(args.Offset, bytes); 106 | 107 | if (!_redisSocket.SendAsync(args)) 108 | OnSocketSent(args); 109 | } 110 | } 111 | 112 | void OnSocketCompleted(object sender, SocketAsyncEventArgs e) 113 | { 114 | switch (e.LastOperation) 115 | { 116 | case SocketAsyncOperation.Connect: 117 | OnSocketConnected(e); 118 | break; 119 | case SocketAsyncOperation.Send: 120 | OnSocketSent(e); 121 | break; 122 | default: 123 | throw new InvalidOperationException(); 124 | } 125 | } 126 | 127 | void OnSocketConnected(SocketAsyncEventArgs args) 128 | { 129 | _connectionTaskSource.SetResult(_redisSocket.Connected); 130 | 131 | if (Connected != null) 132 | Connected(this, new EventArgs()); 133 | } 134 | 135 | void OnSocketSent(SocketAsyncEventArgs args) 136 | { 137 | _asyncTransferPool.Release(args); 138 | 139 | IRedisAsyncCommandToken token; 140 | lock (_readLock) 141 | { 142 | if (_asyncReadQueue.TryDequeue(out token)) 143 | { 144 | try 145 | { 146 | token.SetResult(_io.Reader); 147 | } 148 | /*catch (IOException) // TODO implement async retry 149 | { 150 | if (ReconnectAttempts == 0) 151 | throw; 152 | Reconnect(); 153 | _asyncWriteQueue.Enqueue(token); 154 | ConnectAsync().ContinueWith(CallAsyncDeferred); 155 | }*/ 156 | catch (Exception e) 157 | { 158 | token.SetException(e); 159 | } 160 | } 161 | } 162 | } 163 | 164 | public void Dispose() 165 | { 166 | _asyncTransferPool.Dispose(); 167 | _asyncConnectArgs.Dispose(); 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /CSRedis/Internal/IO/IRedisSocket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | 9 | namespace CSRedis.Internal.IO 10 | { 11 | interface IRedisSocket : IDisposable 12 | { 13 | bool Connected { get; } 14 | int ReceiveTimeout { get; set; } 15 | int SendTimeout { get; set; } 16 | void Connect(EndPoint endpoint); 17 | bool ConnectAsync(SocketAsyncEventArgs args); 18 | bool SendAsync(SocketAsyncEventArgs args); 19 | Stream GetStream(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CSRedis/Internal/IO/RedisAsyncCommandToken.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.IO; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace CSRedis.Internal.IO 9 | { 10 | interface IRedisAsyncCommandToken 11 | { 12 | Task Task { get; } 13 | RedisCommand Command { get; } 14 | void SetResult(RedisReader reader); 15 | void SetException(Exception e); 16 | } 17 | 18 | class RedisAsyncCommandToken : IRedisAsyncCommandToken 19 | { 20 | readonly TaskCompletionSource _tcs; 21 | readonly RedisCommand _command; 22 | 23 | public TaskCompletionSource TaskSource { get { return _tcs; } } 24 | public RedisCommand Command { get { return _command; } } 25 | public Task Task { get { return _tcs.Task; } } 26 | 27 | public RedisAsyncCommandToken(RedisCommand command) 28 | { 29 | _tcs = new TaskCompletionSource(); 30 | _command = command; 31 | } 32 | 33 | public void SetResult(RedisReader reader) 34 | { 35 | _tcs.SetResult(_command.Parse(reader)); 36 | } 37 | 38 | public void SetException(Exception e) 39 | { 40 | _tcs.SetException(e); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /CSRedis/Internal/IO/RedisIO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace CSRedis.Internal.IO 8 | { 9 | class RedisIO : IDisposable 10 | { 11 | readonly RedisWriter _writer; 12 | RedisReader _reader; 13 | RedisPipeline _pipeline; 14 | BufferedStream _stream; 15 | 16 | public RedisWriter Writer { get { return _writer; } } 17 | public RedisReader Reader { get { return GetOrThrow(_reader); } } 18 | public Encoding Encoding { get; set; } 19 | public RedisPipeline Pipeline { get { return GetOrThrow(_pipeline); } } 20 | public Stream Stream { get { return GetOrThrow(_stream); } } 21 | public bool IsPipelined { get { return Pipeline == null ? false : Pipeline.Active; } } 22 | 23 | public RedisIO() 24 | { 25 | _writer = new RedisWriter(this); 26 | Encoding = new UTF8Encoding(false); 27 | } 28 | 29 | public void SetStream(Stream stream) 30 | { 31 | if (_stream != null) 32 | _stream.Dispose(); 33 | 34 | _stream = new BufferedStream(stream); 35 | _reader = new RedisReader(this); 36 | _pipeline = new RedisPipeline(this); 37 | } 38 | 39 | public void Dispose() 40 | { 41 | if (_pipeline != null) 42 | _pipeline.Dispose(); 43 | if (_stream != null) 44 | _stream.Dispose(); 45 | } 46 | 47 | static T GetOrThrow(T obj) 48 | { 49 | if (obj == null) 50 | throw new RedisClientException("Connection was not opened"); 51 | return obj; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /CSRedis/Internal/IO/RedisPooledSocket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | 9 | namespace CSRedis.Internal.IO 10 | { 11 | class RedisPooledSocket : IRedisSocket 12 | { 13 | Socket _socket; 14 | readonly SocketPool _pool; 15 | 16 | public bool Connected { get { return _socket == null ? false : _socket.Connected; } } 17 | 18 | public int ReceiveTimeout 19 | { 20 | get { return _socket.ReceiveTimeout; } 21 | set { _socket.ReceiveTimeout = value; } 22 | } 23 | 24 | public int SendTimeout 25 | { 26 | get { return _socket.SendTimeout; } 27 | set { _socket.SendTimeout = value; } 28 | } 29 | 30 | public RedisPooledSocket(SocketPool pool) 31 | { 32 | _pool = pool; 33 | } 34 | 35 | public void Connect(EndPoint endpoint) 36 | { 37 | _socket = _pool.Connect(); 38 | System.Diagnostics.Debug.WriteLine("Got socket #{0}", _socket.Handle); 39 | } 40 | 41 | public bool ConnectAsync(SocketAsyncEventArgs args) 42 | { 43 | return _pool.ConnectAsync(args, out _socket); 44 | } 45 | 46 | public bool SendAsync(SocketAsyncEventArgs args) 47 | { 48 | return _socket.SendAsync(args); 49 | } 50 | 51 | public Stream GetStream() 52 | { 53 | return new NetworkStream(_socket); 54 | } 55 | 56 | public void Dispose() 57 | { 58 | _pool.Release(_socket); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /CSRedis/Internal/IO/RedisReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace CSRedis.Internal.IO 6 | { 7 | class RedisReader 8 | { 9 | readonly Stream _stream; 10 | readonly RedisIO _io; 11 | 12 | public RedisReader(RedisIO io) 13 | { 14 | _stream = io.Stream; 15 | _io = io; 16 | } 17 | 18 | public RedisMessage ReadType() 19 | { 20 | RedisMessage type = (RedisMessage)_stream.ReadByte(); 21 | if (type == RedisMessage.Error) 22 | throw new RedisException(ReadStatus(false)); 23 | return type; 24 | } 25 | 26 | public string ReadStatus(bool checkType = true) 27 | { 28 | if (checkType) 29 | ExpectType(RedisMessage.Status); 30 | return ReadLine(); 31 | } 32 | 33 | public long ReadInt(bool checkType = true) 34 | { 35 | if (checkType) 36 | ExpectType(RedisMessage.Int); 37 | 38 | string line = ReadLine(); 39 | return Int64.Parse(line.ToString()); 40 | } 41 | 42 | public object ReadBulk(bool checkType = true, bool asString = false) 43 | { 44 | if (asString) 45 | return ReadBulkString(checkType); 46 | else 47 | return ReadBulkBytes(checkType); 48 | } 49 | 50 | public byte[] ReadBulkBytes(bool checkType = true) 51 | { 52 | if (checkType) 53 | ExpectType(RedisMessage.Bulk); 54 | 55 | int size = (int)ReadInt(false); 56 | if (size == -1) 57 | return null; 58 | 59 | byte[] bulk = new byte[size]; 60 | int bytes_read = 0; 61 | int bytes_remaining = size; 62 | 63 | while (bytes_read < size) 64 | bytes_read += _stream.Read(bulk, bytes_read, size - bytes_read); 65 | 66 | ExpectBytesRead(size, bytes_read); 67 | ReadCRLF(); 68 | return bulk; 69 | } 70 | 71 | public void ReadBulkBytes(Stream destination, int bufferSize, bool checkType = true) 72 | { 73 | if (checkType) 74 | ExpectType(RedisMessage.Bulk); 75 | int size = (int)ReadInt(false); 76 | if (size == -1) 77 | return; 78 | 79 | byte[] buffer = new byte[bufferSize]; 80 | int position = 0; 81 | while (position < size) 82 | { 83 | int bytes_to_buffer = Math.Min(buffer.Length, size - position); 84 | int bytes_read = 0; 85 | while (bytes_read < bytes_to_buffer) 86 | { 87 | int bytes_to_read = Math.Min(bytes_to_buffer - bytes_read, size - position); 88 | bytes_read += _stream.Read(buffer, bytes_read, bytes_to_read); 89 | } 90 | position += bytes_read; 91 | destination.Write(buffer, 0, bytes_read); 92 | } 93 | ExpectBytesRead(size, position); 94 | ReadCRLF(); 95 | } 96 | 97 | public string ReadBulkString(bool checkType = true) 98 | { 99 | byte[] bulk = ReadBulkBytes(checkType); 100 | if (bulk == null) 101 | return null; 102 | return _io.Encoding.GetString(bulk); 103 | } 104 | 105 | public void ExpectType(RedisMessage expectedType) 106 | { 107 | RedisMessage type = ReadType(); 108 | if ((int)type == -1) 109 | throw new EndOfStreamException("Unexpected end of stream; expected type '" + expectedType + "'"); 110 | if (type != expectedType) 111 | throw new RedisProtocolException(String.Format("Unexpected response type: {0} (expecting {1})", type, expectedType)); 112 | } 113 | 114 | public void ExpectMultiBulk(long expectedSize) 115 | { 116 | ExpectType(RedisMessage.MultiBulk); 117 | ExpectSize(expectedSize); 118 | } 119 | 120 | public void ExpectSize(long expectedSize) 121 | { 122 | long size = ReadInt(false); 123 | ExpectSize(expectedSize, size); 124 | } 125 | 126 | public void ExpectSize(long expectedSize, long actualSize) 127 | { 128 | if (actualSize != expectedSize) 129 | throw new RedisProtocolException("Expected " + expectedSize + " elements; got " + actualSize); 130 | } 131 | 132 | public void ReadCRLF() // TODO: remove hardcoded 133 | { 134 | var r = _stream.ReadByte(); 135 | var n = _stream.ReadByte(); 136 | if (r != (byte)13 && n != (byte)10) 137 | throw new RedisProtocolException(String.Format("Expecting CRLF; got bytes: {0}, {1}", r, n)); 138 | } 139 | 140 | public object[] ReadMultiBulk(bool checkType = true, bool bulkAsString = false) 141 | { 142 | if (checkType) 143 | ExpectType(RedisMessage.MultiBulk); 144 | long count = ReadInt(false); 145 | if (count == -1) 146 | return null; 147 | 148 | object[] lines = new object[count]; 149 | for (int i = 0; i < count; i++) 150 | lines[i] = Read(bulkAsString); 151 | return lines; 152 | } 153 | 154 | public object Read(bool bulkAsString = false) 155 | { 156 | RedisMessage type = ReadType(); 157 | switch (type) 158 | { 159 | case RedisMessage.Bulk: 160 | return ReadBulk(false, bulkAsString); 161 | 162 | case RedisMessage.Int: 163 | return ReadInt(false); 164 | 165 | case RedisMessage.MultiBulk: 166 | return ReadMultiBulk(false, bulkAsString); 167 | 168 | case RedisMessage.Status: 169 | return ReadStatus(false); 170 | 171 | case RedisMessage.Error: 172 | throw new RedisException(ReadStatus(false)); 173 | 174 | default: 175 | throw new RedisProtocolException("Unsupported response type: " + type); 176 | } 177 | } 178 | 179 | string ReadLine() 180 | { 181 | StringBuilder sb = new StringBuilder(); 182 | char c; 183 | bool should_break = false; 184 | while (true) 185 | { 186 | c = (char)_stream.ReadByte(); 187 | if (c == '\r') // TODO: remove hardcoded 188 | should_break = true; 189 | else if (c == '\n' && should_break) 190 | break; 191 | else 192 | { 193 | sb.Append(c); 194 | should_break = false; 195 | } 196 | } 197 | return sb.ToString(); 198 | } 199 | 200 | void ExpectBytesRead(long expecting, long actual) 201 | { 202 | if (actual != expecting) 203 | throw new RedisProtocolException(String.Format("Expecting {0} bytes; got {1} bytes", expecting, actual)); 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /CSRedis/Internal/IO/RedisSocket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Security; 7 | using System.Net.Sockets; 8 | using System.Text; 9 | 10 | namespace CSRedis.Internal.IO 11 | { 12 | class RedisSocket : IRedisSocket 13 | { 14 | readonly bool _ssl; 15 | Socket _socket; 16 | EndPoint _remote; 17 | 18 | public bool Connected { get { return _socket == null ? false : _socket.Connected; } } 19 | 20 | public int ReceiveTimeout 21 | { 22 | get { return _socket.ReceiveTimeout; } 23 | set { _socket.ReceiveTimeout = value; } 24 | } 25 | 26 | public int SendTimeout 27 | { 28 | get { return _socket.SendTimeout; } 29 | set { _socket.SendTimeout = value; } 30 | } 31 | 32 | public RedisSocket(bool ssl) 33 | { 34 | _ssl = ssl; 35 | } 36 | 37 | public void Connect(EndPoint endpoint) 38 | { 39 | InitSocket(endpoint); 40 | _socket.Connect(endpoint); 41 | } 42 | 43 | public bool ConnectAsync(SocketAsyncEventArgs args) 44 | { 45 | InitSocket(args.RemoteEndPoint); 46 | return _socket.ConnectAsync(args); 47 | } 48 | 49 | public bool SendAsync(SocketAsyncEventArgs args) 50 | { 51 | return _socket.SendAsync(args); 52 | } 53 | 54 | public Stream GetStream() 55 | { 56 | Stream netStream = new NetworkStream(_socket); 57 | 58 | if (!_ssl) return netStream; 59 | 60 | var sslStream = new SslStream(netStream, true); 61 | sslStream.AuthenticateAsClient(GetHostForAuthentication()); 62 | return sslStream; 63 | } 64 | 65 | public void Dispose() 66 | { 67 | _socket.Dispose(); 68 | } 69 | 70 | void InitSocket(EndPoint endpoint) 71 | { 72 | if (_socket != null) 73 | _socket.Dispose(); 74 | 75 | _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 76 | _remote = endpoint; 77 | } 78 | 79 | string GetHostForAuthentication() 80 | { 81 | if (_remote == null) 82 | throw new ArgumentNullException("Remote endpoint is not set"); 83 | else if (_remote is DnsEndPoint) 84 | return (_remote as DnsEndPoint).Host; 85 | else if (_remote is IPEndPoint) 86 | return (_remote as IPEndPoint).Address.ToString(); 87 | 88 | throw new InvalidOperationException("Cannot get remote host"); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /CSRedis/Internal/IO/RedisWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace CSRedis.Internal.IO 9 | { 10 | class RedisWriter 11 | { 12 | const char Bulk = (char)RedisMessage.Bulk; 13 | const char MultiBulk = (char)RedisMessage.MultiBulk; 14 | const string EOL = "\r\n"; 15 | 16 | readonly RedisIO _io; 17 | 18 | public RedisWriter(RedisIO io) 19 | { 20 | _io = io; 21 | } 22 | 23 | public int Write(RedisCommand command, Stream stream) 24 | { 25 | string prepared = Prepare(command); 26 | byte[] data = _io.Encoding.GetBytes(prepared); 27 | stream.Write(data, 0, data.Length); 28 | return data.Length; 29 | } 30 | 31 | public int Write(RedisCommand command, byte[] buffer, int offset) 32 | { 33 | string prepared = Prepare(command); 34 | return _io.Encoding.GetBytes(prepared, 0, prepared.Length, buffer, offset); 35 | } 36 | 37 | string Prepare(RedisCommand command) 38 | { 39 | var parts = command.Command.Split(' '); 40 | int length = parts.Length + command.Arguments.Length; 41 | StringBuilder sb = new StringBuilder(); 42 | sb.Append(MultiBulk).Append(length).Append(EOL); 43 | 44 | foreach (var part in parts) 45 | sb.Append(Bulk).Append(_io.Encoding.GetByteCount(part)).Append(EOL).Append(part).Append(EOL); 46 | 47 | foreach (var arg in command.Arguments) 48 | { 49 | string str = String.Format(CultureInfo.InvariantCulture, "{0}", arg); 50 | sb.Append(Bulk).Append(_io.Encoding.GetByteCount(str)).Append(EOL).Append(str).Append(EOL); 51 | } 52 | 53 | return sb.ToString(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /CSRedis/Internal/IO/SocketAsyncPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Sockets; 5 | using System.Text; 6 | using System.Threading; 7 | 8 | namespace CSRedis.Internal.IO 9 | { 10 | class SocketAsyncPool : IDisposable 11 | { 12 | readonly byte[] _buffer; 13 | readonly Stack _pool; 14 | readonly int _bufferSize; 15 | readonly Semaphore _acquisitionGate; 16 | 17 | public event EventHandler Completed; 18 | 19 | public SocketAsyncPool(int concurrency, int bufferSize) 20 | { 21 | _pool = new Stack(); 22 | _bufferSize = bufferSize; 23 | _buffer = new byte[concurrency * bufferSize]; 24 | _acquisitionGate = new Semaphore(concurrency, concurrency); 25 | for (int i = 0; i < _buffer.Length; i += bufferSize) 26 | { 27 | SocketAsyncEventArgs args = new SocketAsyncEventArgs(); 28 | args.Completed += OnSocketCompleted; 29 | args.SetBuffer(_buffer, i, bufferSize); 30 | _pool.Push(args); 31 | } 32 | } 33 | 34 | public SocketAsyncEventArgs Acquire() 35 | { 36 | if (!_acquisitionGate.WaitOne()) 37 | throw new Exception(); 38 | 39 | lock (_pool) 40 | { 41 | return _pool.Pop(); 42 | } 43 | } 44 | 45 | public void Release(SocketAsyncEventArgs args) 46 | { 47 | lock (_pool) 48 | { 49 | if (args.Buffer.Equals(_buffer)) 50 | _pool.Push(args); 51 | else 52 | args.Dispose(); 53 | } 54 | _acquisitionGate.Release(); 55 | } 56 | 57 | public void Dispose() 58 | { 59 | Array.Clear(_buffer, 0, _buffer.Length); 60 | for (int i = 0; i < _pool.Count; i++) 61 | _pool.Pop().Dispose(); 62 | } 63 | 64 | void OnSocketCompleted(object sender, SocketAsyncEventArgs e) 65 | { 66 | if (Completed != null) 67 | Completed(sender, e); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /CSRedis/Internal/IO/SocketPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | 9 | namespace CSRedis.Internal.IO 10 | { 11 | class SocketPool : IDisposable 12 | { 13 | readonly EndPoint _endPoint; 14 | readonly ConcurrentStack _pool; 15 | readonly int _max; 16 | 17 | public SocketPool(EndPoint endPoint, int max) 18 | { 19 | _max = max; 20 | _endPoint = endPoint; 21 | _pool = new ConcurrentStack(); 22 | } 23 | 24 | public Socket Connect() 25 | { 26 | Socket socket = Acquire(); 27 | if (!socket.Connected) 28 | socket.Connect(_endPoint); 29 | return socket; 30 | } 31 | 32 | public bool ConnectAsync(SocketAsyncEventArgs connectArgs, out Socket socket) 33 | { 34 | socket = Acquire(); 35 | if (socket.Connected) 36 | return false; 37 | return socket.ConnectAsync(connectArgs); 38 | } 39 | 40 | public void Release(Socket socket) 41 | { 42 | _pool.Push(socket); 43 | } 44 | 45 | public void Dispose() 46 | { 47 | foreach (var socket in _pool) 48 | { 49 | System.Diagnostics.Debug.WriteLine("Disposing socket #{0}", socket.Handle); 50 | socket.Dispose(); 51 | } 52 | } 53 | 54 | Socket Acquire() 55 | { 56 | Socket socket; 57 | if (!_pool.TryPop(out socket)) 58 | { 59 | Add(); 60 | return Acquire(); 61 | } 62 | else if (socket.IsBound && !socket.Connected) 63 | { 64 | socket.Dispose(); 65 | return Acquire(); 66 | } 67 | else if (socket.Poll(1000, SelectMode.SelectRead)) 68 | { 69 | socket.Dispose(); 70 | return Acquire(); 71 | } 72 | return socket; 73 | } 74 | 75 | void Add() 76 | { 77 | if (_pool.Count > _max) 78 | throw new InvalidOperationException("Maximum sockets"); 79 | _pool.Push(SocketFactory()); 80 | } 81 | 82 | Socket SocketFactory() 83 | { 84 | System.Diagnostics.Debug.WriteLine("NEW SOCKET"); 85 | return new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /CSRedis/Internal/MonitorListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace CSRedis.Internal 7 | { 8 | class MonitorListener : RedisListner 9 | { 10 | public event EventHandler MonitorReceived; 11 | 12 | public MonitorListener(RedisConnector connection) 13 | : base(connection) 14 | { } 15 | 16 | public string Start() 17 | { 18 | string status = Call(RedisCommands.Monitor()); 19 | Listen(x => x.Read()); 20 | return status; 21 | } 22 | 23 | protected override void OnParsed(object value) 24 | { 25 | OnMonitorReceived(value); 26 | } 27 | 28 | protected override bool Continue() 29 | { 30 | return Connection.IsConnected; 31 | } 32 | 33 | void OnMonitorReceived(object message) 34 | { 35 | if (MonitorReceived != null) 36 | MonitorReceived(this, new RedisMonitorEventArgs(message)); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /CSRedis/Internal/RedisConnector.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.IO; 2 | using System; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Net.Sockets; 9 | using System.Text; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | namespace CSRedis.Internal 14 | { 15 | class RedisConnector 16 | { 17 | readonly int _concurrency; 18 | readonly int _bufferSize; 19 | readonly Lazy _asyncConnector; 20 | readonly IRedisSocket _redisSocket; 21 | readonly EndPoint _endPoint; 22 | readonly RedisIO _io; 23 | 24 | public event EventHandler Connected; 25 | 26 | public AsyncConnector Async { get { return _asyncConnector.Value; } } 27 | public bool IsConnected { get { return _redisSocket.Connected; } } 28 | public EndPoint EndPoint { get { return _endPoint; } } 29 | public bool IsPipelined { get { return _io.IsPipelined; } } 30 | public int ReconnectAttempts { get; set; } 31 | public int ReconnectWait { get; set; } 32 | public int ReceiveTimeout 33 | { 34 | get { return _redisSocket.ReceiveTimeout; } 35 | set { _redisSocket.ReceiveTimeout = value; } 36 | } 37 | public int SendTimeout 38 | { 39 | get { return _redisSocket.SendTimeout; } 40 | set { _redisSocket.SendTimeout = value; } 41 | } 42 | public Encoding Encoding 43 | { 44 | get { return _io.Encoding; } 45 | set { _io.Encoding = value; } 46 | } 47 | 48 | 49 | public RedisConnector(EndPoint endPoint, IRedisSocket socket, int concurrency, int bufferSize) 50 | { 51 | _concurrency = concurrency; 52 | _bufferSize = bufferSize; 53 | _endPoint = endPoint; 54 | _redisSocket = socket; 55 | _io = new RedisIO(); 56 | _asyncConnector = new Lazy(AsyncConnectorFactory); 57 | } 58 | 59 | public bool Connect() 60 | { 61 | _redisSocket.Connect(_endPoint); 62 | 63 | if (_redisSocket.Connected) 64 | OnConnected(); 65 | 66 | return _redisSocket.Connected; 67 | } 68 | 69 | public Task ConnectAsync() 70 | { 71 | return Async.ConnectAsync(); 72 | } 73 | 74 | public T Call(RedisCommand command) 75 | { 76 | ConnectIfNotConnected(); 77 | 78 | try 79 | { 80 | if (IsPipelined) 81 | return _io.Pipeline.Write(command); 82 | 83 | _io.Writer.Write(command, _io.Stream); 84 | return command.Parse(_io.Reader); 85 | } 86 | catch (IOException) 87 | { 88 | if (ReconnectAttempts == 0) 89 | throw; 90 | Reconnect(); 91 | return Call(command); 92 | } 93 | } 94 | 95 | public Task CallAsync(RedisCommand command) 96 | { 97 | return Async.CallAsync(command); 98 | } 99 | 100 | public void Write(RedisCommand command) 101 | { 102 | ConnectIfNotConnected(); 103 | 104 | try 105 | { 106 | _io.Writer.Write(command, _io.Stream); 107 | } 108 | catch (IOException) 109 | { 110 | if (ReconnectAttempts == 0) 111 | throw; 112 | Reconnect(); 113 | Write(command); 114 | } 115 | } 116 | 117 | public T Read(Func func) 118 | { 119 | ExpectConnected(); 120 | 121 | try 122 | { 123 | return func(_io.Reader); 124 | } 125 | catch (IOException) 126 | { 127 | if (ReconnectAttempts == 0) 128 | throw; 129 | Reconnect(); 130 | return Read(func); 131 | } 132 | } 133 | 134 | public void Read(Stream destination, int bufferSize) 135 | { 136 | ExpectConnected(); 137 | 138 | try 139 | { 140 | _io.Reader.ExpectType(RedisMessage.Bulk); 141 | _io.Reader.ReadBulkBytes(destination, bufferSize, false); 142 | } 143 | catch (IOException) 144 | { 145 | if (ReconnectAttempts == 0) 146 | throw; 147 | Reconnect(); 148 | Read(destination, bufferSize); 149 | } 150 | } 151 | 152 | public void BeginPipe() 153 | { 154 | ConnectIfNotConnected(); 155 | _io.Pipeline.Begin(); 156 | } 157 | 158 | public object[] EndPipe() 159 | { 160 | ExpectConnected(); 161 | 162 | try 163 | { 164 | return _io.Pipeline.Flush(); 165 | } 166 | catch (IOException) 167 | { 168 | if (ReconnectAttempts == 0) 169 | throw; 170 | Reconnect(); 171 | return EndPipe(); 172 | } 173 | } 174 | 175 | public void Dispose() 176 | { 177 | if (_asyncConnector.IsValueCreated) 178 | _asyncConnector.Value.Dispose(); 179 | 180 | _io.Dispose(); 181 | 182 | if (_redisSocket != null) 183 | _redisSocket.Dispose(); 184 | 185 | } 186 | 187 | void Reconnect() 188 | { 189 | int attempts = 0; 190 | while (attempts++ < ReconnectAttempts || ReconnectAttempts == -1) 191 | { 192 | if (Connect()) 193 | return; 194 | 195 | Thread.Sleep(TimeSpan.FromMilliseconds(ReconnectWait)); 196 | } 197 | 198 | throw new IOException("Could not reconnect after " + attempts + " attempts"); 199 | } 200 | 201 | void OnConnected() 202 | { 203 | _io.SetStream(_redisSocket.GetStream()); 204 | if (Connected != null) 205 | Connected(this, new EventArgs()); 206 | } 207 | 208 | void OnAsyncConnected(object sender, EventArgs args) 209 | { 210 | OnConnected(); 211 | } 212 | 213 | AsyncConnector AsyncConnectorFactory() 214 | { 215 | var connector = new AsyncConnector(_redisSocket, _endPoint, _io, _concurrency, _bufferSize); 216 | connector.Connected += OnAsyncConnected; 217 | return connector; 218 | } 219 | 220 | void ConnectIfNotConnected() 221 | { 222 | if (!IsConnected) 223 | Connect(); 224 | } 225 | 226 | void ExpectConnected() 227 | { 228 | if (!IsConnected) 229 | throw new RedisClientException("Client is not connected"); 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /CSRedis/Internal/RedisListener.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.IO; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | 9 | namespace CSRedis.Internal 10 | { 11 | abstract class RedisListner 12 | { 13 | readonly RedisConnector _connection; 14 | 15 | public bool Listening { get; private set; } 16 | protected RedisConnector Connection { get { return _connection; } } 17 | 18 | public RedisListner(RedisConnector connection) 19 | { 20 | _connection = connection; 21 | } 22 | 23 | protected void Listen(Func func) 24 | { 25 | Listening = true; 26 | do 27 | { 28 | try 29 | { 30 | TResponse value = _connection.Read(func); 31 | OnParsed(value); 32 | } 33 | catch (IOException) 34 | { 35 | if (_connection.IsConnected) 36 | throw; 37 | break; 38 | } 39 | } while (Continue()); 40 | Listening = false; 41 | } 42 | 43 | protected void Write(RedisCommand command) 44 | { 45 | _connection.Write(command); 46 | } 47 | 48 | protected T Call(RedisCommand command) 49 | { 50 | return _connection.Call(command); 51 | } 52 | 53 | protected abstract void OnParsed(TResponse value); 54 | protected abstract bool Continue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /CSRedis/Internal/RedisPipeline.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.IO; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace CSRedis.Internal 9 | { 10 | class RedisPipeline : IDisposable 11 | { 12 | readonly Stream _buffer; 13 | readonly Stream _destination; 14 | readonly RedisWriter _writer; 15 | readonly RedisReader _reader; 16 | readonly Queue> _parsers; 17 | 18 | public bool Active { get; private set; } 19 | 20 | internal RedisPipeline(RedisIO io) 21 | { 22 | _reader = io.Reader; 23 | _destination = io.Stream; 24 | _buffer = new MemoryStream(); 25 | _writer = new RedisWriter(io); 26 | _parsers = new Queue>(); 27 | } 28 | 29 | public T Write(RedisCommand command) 30 | { 31 | _writer.Write(command, _buffer); 32 | _parsers.Enqueue(() => command.Parse(_reader)); 33 | return default(T); 34 | } 35 | 36 | public void Begin() 37 | { 38 | Active = true; 39 | } 40 | 41 | public object[] Flush() 42 | { 43 | _buffer.Position = 0; 44 | _buffer.CopyTo(_destination); 45 | 46 | object[] results = new object[_parsers.Count]; 47 | for (int i = 0; i < results.Length; i++) 48 | results[i] = _parsers.Dequeue()(); 49 | _buffer.SetLength(0); 50 | Active = false; 51 | return results; 52 | } 53 | 54 | public void Dispose() 55 | { 56 | _buffer.Dispose(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /CSRedis/Internal/RedisTransaction.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.Commands; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace CSRedis.Internal 9 | { 10 | class RedisTransaction 11 | { 12 | readonly RedisConnector _connector; 13 | readonly RedisArray _execCommand; 14 | readonly List> _pipeCommands = new List>(); 15 | 16 | public event EventHandler TransactionQueued; 17 | 18 | bool _active; 19 | public bool Active { get { return _active; } } 20 | 21 | public RedisTransaction(RedisConnector connector) 22 | { 23 | _connector = connector; 24 | _execCommand = RedisCommands.Exec(); 25 | } 26 | 27 | public string Start() 28 | { 29 | _active = true; 30 | return _connector.Call(RedisCommands.Multi()); 31 | } 32 | 33 | public Task StartAsync() 34 | { 35 | _active = true; 36 | return _connector.CallAsync(RedisCommands.Multi()); 37 | } 38 | 39 | public T Write(RedisCommand command) 40 | { 41 | string response = _connector.Call(RedisCommands.AsTransaction(command)); 42 | OnTransactionQueued(command, response); 43 | 44 | _execCommand.AddParser(x => command.Parse(x)); 45 | return default(T); 46 | } 47 | 48 | public Task WriteAsync(RedisCommand command) 49 | { 50 | lock (_execCommand) 51 | { 52 | _execCommand.AddParser(x => command.Parse(x)); 53 | return _connector.CallAsync(RedisCommands.AsTransaction(command)) 54 | .ContinueWith(t => OnTransactionQueued(command, t.Result)) 55 | .ContinueWith(t => default(T)); 56 | } 57 | } 58 | 59 | public object[] Execute() 60 | { 61 | _active = false; 62 | 63 | if (_connector.IsConnected && _connector.IsPipelined) 64 | { 65 | _connector.Call(_execCommand); 66 | object[] response = _connector.EndPipe(); 67 | for (int i = 1; i < response.Length - 1; i++) 68 | OnTransactionQueued(_pipeCommands[i - 1].Item1, _pipeCommands[i - 1].Item2, response[i - 1].ToString()); 69 | 70 | object transaction_response = response[response.Length - 1]; 71 | if (!(transaction_response is object[])) 72 | throw new RedisProtocolException("Unexpected response"); 73 | 74 | return transaction_response as object[]; 75 | } 76 | 77 | return _connector.Call(_execCommand); 78 | } 79 | 80 | public Task ExecuteAsync() 81 | { 82 | _active = false; 83 | return _connector.CallAsync(_execCommand); 84 | } 85 | 86 | public string Abort() 87 | { 88 | _active = false; 89 | return _connector.Call(RedisCommands.Discard()); 90 | } 91 | 92 | public Task AbortAsync() 93 | { 94 | _active = false; 95 | return _connector.CallAsync(RedisCommands.Discard()); 96 | } 97 | 98 | void OnTransactionQueued(RedisCommand command, string response) 99 | { 100 | if (_connector.IsPipelined) 101 | _pipeCommands.Add(Tuple.Create(command.Command, command.Arguments)); 102 | else 103 | OnTransactionQueued(command.Command, command.Arguments, response); 104 | } 105 | 106 | void OnTransactionQueued(string command, object[] args, string response) 107 | { 108 | if (TransactionQueued != null) 109 | TransactionQueued(this, new RedisTransactionQueuedEventArgs(response, command, args)); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /CSRedis/Internal/SubscriptionListener.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.Commands; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace CSRedis.Internal 8 | { 9 | class SubscriptionListener : RedisListner 10 | { 11 | long _count; 12 | 13 | public event EventHandler MessageReceived; 14 | public event EventHandler Changed; 15 | 16 | public SubscriptionListener(RedisConnector connection) 17 | : base(connection) 18 | { } 19 | 20 | public void Send(RedisSubscription command) 21 | { 22 | Write(command); 23 | if (!Listening) 24 | Listen(command.Parse); 25 | } 26 | 27 | protected override void OnParsed(RedisSubscriptionResponse response) 28 | { 29 | if (response is RedisSubscriptionChannel) 30 | OnReceivedChannel(response as RedisSubscriptionChannel); 31 | else if (response is RedisSubscriptionMessage) 32 | OnReceivedMessage(response as RedisSubscriptionMessage); 33 | } 34 | 35 | protected override bool Continue() 36 | { 37 | return _count > 0; 38 | } 39 | 40 | void OnReceivedChannel(RedisSubscriptionChannel channel) 41 | { 42 | _count = channel.Count; 43 | if (Changed != null) 44 | Changed(this, new RedisSubscriptionChangedEventArgs(channel)); 45 | } 46 | 47 | void OnReceivedMessage(RedisSubscriptionMessage message) 48 | { 49 | if (MessageReceived != null) 50 | MessageReceived(this, new RedisSubscriptionReceivedEventArgs(message)); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /CSRedis/Internal/Utilities/RedisArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | 5 | namespace CSRedis.Internal.Utilities 6 | { 7 | static class RedisArgs 8 | { 9 | /// 10 | /// Join arrays 11 | /// 12 | /// Arrays to join 13 | /// Array of ToString() elements in each array 14 | public static string[] Concat(params object[][] arrays) 15 | { 16 | int count = 0; 17 | foreach (var ar in arrays) 18 | count += ar.Length; 19 | 20 | int pos = 0; 21 | string[] output = new string[count]; 22 | for (int i = 0; i < arrays.Length; i++) 23 | { 24 | for (int j = 0; j < arrays[i].Length; j++) 25 | { 26 | object obj = arrays[i][j]; 27 | output[pos++] = obj == null ? String.Empty : String.Format(CultureInfo.InvariantCulture, "{0}", obj); 28 | } 29 | } 30 | return output; 31 | } 32 | 33 | /// 34 | /// Joine string with arrays 35 | /// 36 | /// Leading string element 37 | /// Array to join 38 | /// Array of str and ToString() elements of arrays 39 | public static string[] Concat(string str, params object[] arrays) 40 | { 41 | return Concat(new[] { str }, arrays); 42 | } 43 | 44 | /// 45 | /// Convert array of two-element tuple into flat array arguments 46 | /// 47 | /// Type of first item 48 | /// Type of second item 49 | /// Array of tuple arguments 50 | /// Flattened array of arguments 51 | public static object[] GetTupleArgs(Tuple[] tuples) 52 | { 53 | List args = new List(); 54 | foreach (var kvp in tuples) 55 | args.AddRange(new object[] { kvp.Item1, kvp.Item2 }); 56 | 57 | return args.ToArray(); 58 | } 59 | 60 | /// 61 | /// Parse score for +/- infinity and inclusive/exclusive 62 | /// 63 | /// Numeric base score 64 | /// Score is exclusive, rather than inclusive 65 | /// String representing Redis score/range notation 66 | public static string GetScore(double score, bool isExclusive) 67 | { 68 | if (Double.IsNegativeInfinity(score) || score == Double.MinValue) 69 | return "-inf"; 70 | else if (Double.IsPositiveInfinity(score) || score == Double.MaxValue) 71 | return "+inf"; 72 | else if (isExclusive) 73 | return '(' + score.ToString(); 74 | else 75 | return score.ToString(); 76 | } 77 | 78 | public static object[] FromDict(Dictionary dict) 79 | { 80 | var array = new List(); 81 | foreach (var keyValue in dict) 82 | { 83 | if (keyValue.Key != null && keyValue.Value != null) 84 | array.AddRange(new[] { keyValue.Key, keyValue.Value }); 85 | } 86 | return array.ToArray(); 87 | } 88 | 89 | public static object[] FromObject(T obj) 90 | where T : class 91 | { 92 | var dict = Serializer.Serialize(obj); 93 | object[] array = new object[dict.Count * 2]; 94 | int i = 0; 95 | foreach (var item in dict) 96 | { 97 | array[i++] = item.Key; 98 | array[i++] = item.Value; 99 | } 100 | return array; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /CSRedis/Internal/Utilities/Serializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using System.Runtime.Serialization; 8 | 9 | namespace CSRedis.Internal.Utilities 10 | { 11 | internal static class Serializer 12 | where T : class 13 | { 14 | static readonly Lazy>> 15 | _propertySerializer; 16 | static readonly Lazy, T>> 17 | _propertyDeserializer; 18 | static readonly Lazy>> 19 | _serializer; 20 | static readonly Lazy, T>> 21 | _deserializer; 22 | 23 | static Serializer() 24 | { 25 | _propertySerializer 26 | = new Lazy>>(CompilePropertySerializer); 27 | _propertyDeserializer 28 | = new Lazy, T>>(CompilePropertyDeserializer); 29 | _serializer 30 | = new Lazy>>(CompileSerializer); 31 | _deserializer 32 | = new Lazy, T>>(CompileDeserializer); 33 | } 34 | 35 | public static Dictionary Serialize(T obj) 36 | { 37 | if (typeof(ISerializable).IsAssignableFrom(typeof(T))) 38 | return _serializer.Value(obj); 39 | else 40 | return _propertySerializer.Value(obj); 41 | } 42 | 43 | public static T Deserialize(Dictionary fields) 44 | { 45 | if (typeof(ISerializable).IsAssignableFrom(typeof(T))) 46 | return _deserializer.Value(fields); 47 | else 48 | return _propertyDeserializer.Value(fields); 49 | } 50 | 51 | 52 | static Func> CompilePropertySerializer() 53 | { 54 | var o_t = typeof(T); 55 | var o = Expression.Parameter(o_t, "o"); 56 | 57 | var d_t = typeof(Dictionary); 58 | var d = Expression.Variable(d_t, "d"); 59 | var d_init = Expression.MemberInit(Expression.New(d_t)); 60 | var d_add = d_t.GetMethod("Add"); 61 | var d_setters = o_t.GetProperties(BindingFlags.Public | BindingFlags.Instance) // build setters via Add(k,v) 62 | .Where(x => x.CanRead) 63 | .Select(x => 64 | { 65 | var prop = Expression.Property(o, x.Name); 66 | var prop_mi_to_string = x.PropertyType.GetMethod("ToString", new Type[0]); 67 | var add_to_dict = Expression.Call(d, d_add, Expression.Constant(x.Name), Expression.Call(prop, prop_mi_to_string)); 68 | 69 | if (x.PropertyType.IsValueType) 70 | return (Expression)add_to_dict; 71 | else 72 | return (Expression)Expression.IfThen( 73 | Expression.Not(Expression.Equal(prop, Expression.Constant(null))), 74 | add_to_dict); 75 | }); 76 | 77 | // run this 78 | var body = Expression.Block(new[] { d }, // scope variables 79 | Expression.Assign(d, d_init), // initialize 80 | Expression.Block(d_setters), // set 81 | d); // return 82 | 83 | return Expression.Lambda>>(body, o) 84 | .Compile(); 85 | } 86 | 87 | static Func, T> CompilePropertyDeserializer() 88 | { 89 | var o_t = typeof(T); 90 | var o = Expression.Variable(o_t, "o"); 91 | var o_new = Expression.New(typeof(T)); 92 | 93 | var d_t = typeof(Dictionary); 94 | var d = Expression.Parameter(d_t, "d"); 95 | var d_mi_try_get_value = d_t.GetMethod("TryGetValue"); 96 | 97 | var item_t = typeof(String); 98 | var item = Expression.Variable(item_t, "item"); 99 | 100 | var tc_t = typeof(TypeConverter); 101 | var tc = Expression.Variable(tc_t, "tc"); 102 | var tc_mi_can_convert_from = tc_t.GetMethod("CanConvertFrom", new[] { typeof(Type) }); 103 | var tc_mi_convert_from = tc_t.GetMethod("ConvertFrom", new[] { typeof(Object) }); 104 | 105 | var td_t = typeof(TypeDescriptor); 106 | var td_mi_get_converter = td_t.GetMethod("GetConverter", new[] { typeof(Type) }); 107 | 108 | var binds = o_t.GetProperties(BindingFlags.Public | BindingFlags.Instance) 109 | .Where(x => x.CanRead) 110 | .Select(x => 111 | { 112 | var value_t = x.PropertyType; 113 | var value = Expression.Variable(value_t, "value"); 114 | var target = Expression.Label(x.PropertyType); 115 | 116 | return Expression.Bind(x, Expression.Block(new[] { item, value }, 117 | Expression.Assign(tc, Expression.Call(null, td_mi_get_converter, Expression.Constant(x.PropertyType))), 118 | Expression.IfThen( 119 | Expression.Call(d, d_mi_try_get_value, Expression.Constant(x.Name), item), 120 | Expression.IfThen( 121 | Expression.NotEqual(item, Expression.Constant(null)), 122 | Expression.IfThen( 123 | Expression.Call(tc, tc_mi_can_convert_from, Expression.Constant(typeof(String))), 124 | Expression.Block( 125 | Expression.Assign(value, Expression.Convert(Expression.Call(tc, tc_mi_convert_from, item), x.PropertyType)), 126 | Expression.Return(target, value, x.PropertyType))))), 127 | Expression.Label(target, value) 128 | )); 129 | }).ToArray(); 130 | 131 | var body = Expression.Block(new[] { o, tc }, 132 | Expression.MemberInit(o_new, binds) 133 | ); 134 | 135 | return Expression.Lambda, T>>(body, d) 136 | .Compile(); 137 | } 138 | 139 | static Func> CompileSerializer() 140 | { 141 | var o_t = typeof(T); 142 | var o = Expression.Parameter(o_t, "original"); 143 | var o_get_object_data = o_t.GetMethod("GetObjectData"); 144 | 145 | var d_t = typeof(Dictionary); 146 | var d = Expression.Variable(d_t, "d"); // define object variable 147 | var d_init = Expression.MemberInit(Expression.New(d_t)); // object ctor 148 | var d_add = d_t.GetMethod("Add"); // add method 149 | 150 | var fc_t = typeof(FormatterConverter); 151 | var fc = Expression.Variable(fc_t, "fc"); 152 | var fc_init = Expression.MemberInit(Expression.New(fc_t)); 153 | 154 | var info_t = typeof(SerializationInfo); 155 | var info = Expression.Variable(info_t, "info"); 156 | var info_ctor = info_t.GetConstructor(new[] { typeof(Type), fc_t }); 157 | var info_init = Expression.MemberInit(Expression.New(info_ctor, Expression.Constant(o_t), fc)); 158 | var info_get_enumerator = info_t.GetMethod("GetEnumerator"); 159 | 160 | var ctx_t = typeof(StreamingContext); 161 | var ctx = Expression.Variable(ctx_t, "ctx"); 162 | var ctx_init = Expression.MemberInit(Expression.New(ctx_t)); 163 | 164 | var enumerator_t = typeof(SerializationInfoEnumerator); 165 | var enumerator = Expression.Variable(enumerator_t, "enumerator"); 166 | var enumerator_move_next = enumerator_t.GetMethod("MoveNext"); 167 | var enumerator_name = Expression.Property(enumerator, "Name"); 168 | var enumerator_value = Expression.Property(enumerator, "Value"); 169 | var mi_to_string = typeof(Object).GetMethod("ToString", new Type[0]); 170 | var exit_loop = Expression.Label("exit_loop"); 171 | var body = Expression.Block(new[] { d, fc, info, ctx }, 172 | Expression.Assign(d, d_init), 173 | Expression.Assign(fc, fc_init), 174 | Expression.Assign(info, info_init), 175 | Expression.Assign(ctx, ctx_init), 176 | Expression.Call(o, o_get_object_data, info, ctx), 177 | 178 | Expression.Block(new[] { enumerator }, 179 | Expression.Assign(enumerator, Expression.Call(info, info_get_enumerator)), 180 | Expression.Loop( 181 | Expression.IfThenElse( 182 | Expression.Call(enumerator, enumerator_move_next), // test 183 | Expression.IfThen( 184 | Expression.NotEqual(enumerator_value, Expression.Constant(null)), 185 | Expression.Call(d, d_add, enumerator_name, Expression.Call(enumerator_value, mi_to_string)) 186 | ), 187 | Expression.Break(exit_loop)), // if false 188 | exit_loop)), 189 | d); // return 190 | 191 | // compile 192 | return Expression.Lambda>>(body, o) 193 | .Compile(); 194 | } 195 | 196 | static Func, T> CompileDeserializer() 197 | { 198 | var o_t = typeof(T); 199 | var o_ctor = o_t.GetConstructor(new[] { typeof(SerializationInfo), typeof(StreamingContext) }); 200 | 201 | var d_t = typeof(Dictionary); 202 | var d = Expression.Parameter(d_t, "d"); 203 | var d_mi_get_enumerator = d_t.GetMethod("GetEnumerator"); 204 | 205 | var fc_t = typeof(FormatterConverter); 206 | var fc = Expression.Variable(fc_t, "fc"); 207 | var fc_init = Expression.MemberInit(Expression.New(fc_t)); 208 | 209 | var info_t = typeof(SerializationInfo); 210 | var info = Expression.Variable(info_t, "info"); 211 | var info_ctor = info_t.GetConstructor(new[] { typeof(Type), fc_t }); 212 | var info_init = Expression.MemberInit(Expression.New(info_ctor, Expression.Constant(o_t), fc)); 213 | var info_mi_add_value = info_t.GetMethod("AddValue", new[] { typeof(String), typeof(Object) }); 214 | 215 | var ctx_t = typeof(StreamingContext); 216 | var ctx = Expression.Variable(ctx_t, "ctx"); 217 | var ctx_init = Expression.MemberInit(Expression.New(ctx_t)); 218 | 219 | var enumerator_t = typeof(Dictionary.Enumerator); 220 | var enumerator = Expression.Variable(enumerator_t, "enumerator"); 221 | var enumerator_mi_move_next = enumerator_t.GetMethod("MoveNext"); 222 | var enumerator_current = Expression.Property(enumerator, "Current"); 223 | 224 | var kvp_t = typeof(KeyValuePair); 225 | var kvp_pi_key = kvp_t.GetProperty("Key"); 226 | var kvp_pi_value = kvp_t.GetProperty("Value"); 227 | 228 | var exit_loop = Expression.Label("exit_loop"); 229 | 230 | var body = Expression.Block(new[] { fc, info, ctx, enumerator }, 231 | Expression.Assign(fc, fc_init), 232 | Expression.Assign(info, info_init), 233 | Expression.Assign(ctx, ctx_init), 234 | Expression.Assign(enumerator, Expression.Call(d, d_mi_get_enumerator)), 235 | 236 | Expression.Loop( 237 | Expression.IfThenElse( 238 | Expression.Call(enumerator, enumerator_mi_move_next), 239 | Expression.Call(info, info_mi_add_value, Expression.Property(enumerator_current, kvp_pi_key), Expression.Property(enumerator_current, kvp_pi_value)), 240 | Expression.Break(exit_loop)), 241 | exit_loop), 242 | 243 | Expression.MemberInit(Expression.New(o_ctor, info, ctx)) 244 | ); 245 | 246 | return Expression.Lambda, T>>(body, d) 247 | .Compile(); 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /CSRedis/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("csredis")] 9 | [assembly: AssemblyDescription("CSRedis is a .NET client for Redis and Sentinel")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Chris Stone")] 12 | [assembly: AssemblyProduct("csredis")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("1e4063d6-d412-4c26-83c5-63a29ca84334")] 24 | 25 | [assembly: AssemblyVersion("3.1.0")] 26 | [assembly: AssemblyFileVersion("3.1.0")] 27 | [assembly: AssemblyInformationalVersion("3.1.0-rc1")] 28 | [assembly: InternalsVisibleTo("CSRedis.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b7e70b54a9e7596d195fd76f95b0fc80f53158f828c72a84b153168637072cb94f8f8d8f574948ad1c4267f1a2e7feb31df0adcb97f46156bcc32c022278a589f9d21b79d3a5cce62a68497fd4abc96daeb7f1d8dbf204d95b6dcccec52e2ed274d8b7bacd4db0a381f9301832fdedcf52eaa66ff2d7867f8fd7f5c932b055aa")] -------------------------------------------------------------------------------- /CSRedis/RedisConnectionPool.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.IO; 2 | using System; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Net.Sockets; 9 | using System.Text; 10 | 11 | namespace CSRedis 12 | { 13 | /// 14 | /// Represents a pooled collection of Redis connections 15 | /// 16 | public class RedisConnectionPool : IDisposable 17 | { 18 | readonly EndPoint _endPoint; 19 | readonly SocketPool _pool; 20 | 21 | /// 22 | /// Create a new connection pool 23 | /// 24 | /// Redis server host 25 | /// Redis server port 26 | /// Maximum simultaneous connections 27 | public RedisConnectionPool(string host, int port, int max) 28 | : this(new DnsEndPoint(host, port), max) 29 | { } 30 | 31 | /// 32 | /// Create a new connection pool 33 | /// 34 | /// Redis server 35 | /// Maximum simultaneous connections 36 | 37 | public RedisConnectionPool(EndPoint endPoint, int max) 38 | { 39 | _pool = new SocketPool(endPoint, max); 40 | _endPoint = endPoint; 41 | } 42 | 43 | /// 44 | /// Get a pooled Redis Client instance 45 | /// 46 | /// Max concurrent threads (default 1000) 47 | /// Async thread buffer size (default 10240 bytes) 48 | /// RedisClient instance from pool 49 | public RedisClient GetClient(int asyncConcurrency, int asyncBufferSize) 50 | { 51 | return new RedisClient(new RedisPooledSocket(_pool), _endPoint, asyncConcurrency, asyncBufferSize); 52 | } 53 | 54 | /// 55 | /// Get a pooled Redis Client instance 56 | /// 57 | /// RedisClient instance from pool 58 | public RedisClient GetClient() 59 | { 60 | return new RedisClient(new RedisPooledSocket(_pool), _endPoint); 61 | } 62 | 63 | /// 64 | /// Get a pooled Sentinel Client instance 65 | /// 66 | /// Max concurrent threads (default 1000) 67 | /// Async thread buffer size (default 10240 bytes) 68 | /// Sentinel Client from pool 69 | public RedisSentinelClient GetSentinelClient(int asyncConcurrency, int asyncBufferSize) 70 | { 71 | return new RedisSentinelClient(new RedisPooledSocket(_pool), _endPoint, asyncConcurrency, asyncBufferSize); 72 | } 73 | 74 | /// 75 | /// Get a pooled Sentinel Client instance 76 | /// 77 | /// Sentinel Client from pool 78 | public RedisSentinelClient GetSentinelClient() 79 | { 80 | return new RedisSentinelClient(new RedisPooledSocket(_pool), _endPoint); 81 | } 82 | 83 | /// 84 | /// Close all open pooled connections 85 | /// 86 | public void Dispose() 87 | { 88 | _pool.Dispose(); 89 | } 90 | } 91 | 92 | class RedisPooledSocket : IRedisSocket 93 | { 94 | Socket _socket; 95 | readonly SocketPool _pool; 96 | 97 | public bool Connected { get { return _socket == null ? false : _socket.Connected; } } 98 | 99 | public int ReceiveTimeout 100 | { 101 | get { return _socket.ReceiveTimeout; } 102 | set { _socket.ReceiveTimeout = value; } 103 | } 104 | 105 | public int SendTimeout 106 | { 107 | get { return _socket.SendTimeout; } 108 | set { _socket.SendTimeout = value; } 109 | } 110 | 111 | public RedisPooledSocket(SocketPool pool) 112 | { 113 | _pool = pool; 114 | } 115 | 116 | public void Connect(EndPoint endpoint) 117 | { 118 | _socket = _pool.Connect(); 119 | System.Diagnostics.Debug.WriteLine("Got socket #{0}", _socket.Handle); 120 | } 121 | 122 | public bool ConnectAsync(SocketAsyncEventArgs args) 123 | { 124 | return _pool.ConnectAsync(args, out _socket); 125 | } 126 | 127 | public bool SendAsync(SocketAsyncEventArgs args) 128 | { 129 | return _socket.SendAsync(args); 130 | } 131 | 132 | public Stream GetStream() 133 | { 134 | return new NetworkStream(_socket); 135 | } 136 | 137 | public void Dispose() 138 | { 139 | _pool.Release(_socket); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /CSRedis/RedisSentinelClient.Async.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.Commands; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace CSRedis 9 | { 10 | public partial class RedisSentinelClient 11 | { 12 | /// 13 | /// Connect to the remote host 14 | /// 15 | /// True if connected 16 | public Task ConnectAsync() 17 | { 18 | return _connector.ConnectAsync(); 19 | } 20 | 21 | /// 22 | /// Call arbitrary Sentinel command (e.g. for a command not yet implemented in this library) 23 | /// 24 | /// The name of the command 25 | /// Array of arguments to the command 26 | /// Redis unified response 27 | public Task CallAsync(string command, params string[] args) 28 | { 29 | return WriteAsync(new RedisObject(command, args)); 30 | } 31 | 32 | Task WriteAsync(RedisCommand command) 33 | { 34 | return _connector.CallAsync(command); 35 | } 36 | 37 | #region sentinel 38 | /// 39 | /// Ping the Sentinel server 40 | /// 41 | /// Status code 42 | public Task PingAsync() 43 | { 44 | return WriteAsync(RedisCommands.Ping()); 45 | } 46 | 47 | /// 48 | /// Get a list of monitored Redis masters 49 | /// 50 | /// Redis master info 51 | public Task MastersAsync() 52 | { 53 | return WriteAsync(RedisCommands.Sentinel.Masters()); 54 | } 55 | 56 | /// 57 | /// Get information on the specified Redis master 58 | /// 59 | /// Name of the Redis master 60 | /// Master information 61 | public Task MasterAsync(string masterName) 62 | { 63 | return WriteAsync(RedisCommands.Sentinel.Master(masterName)); 64 | } 65 | 66 | /// 67 | /// Get a list of other Sentinels known to the current Sentinel 68 | /// 69 | /// Name of monitored master 70 | /// Sentinel hosts and ports 71 | public Task SentinelsAsync(string masterName) 72 | { 73 | return WriteAsync(RedisCommands.Sentinel.Sentinels(masterName)); 74 | } 75 | 76 | 77 | /// 78 | /// Get a list of monitored Redis slaves to the given master 79 | /// 80 | /// Name of monitored master 81 | /// Redis slave info 82 | public Task SlavesAsync(string masterName) 83 | { 84 | return WriteAsync(RedisCommands.Sentinel.Slaves(masterName)); 85 | } 86 | 87 | /// 88 | /// Get the IP and port of the current master Redis server 89 | /// 90 | /// Name of monitored master 91 | /// IP and port of master Redis server 92 | public Task> GetMasterAddrByNameAsync(string masterName) 93 | { 94 | return WriteAsync(RedisCommands.Sentinel.GetMasterAddrByName(masterName)); 95 | } 96 | 97 | /// 98 | /// Get master state information 99 | /// 100 | /// Host IP 101 | /// Host port 102 | /// Current epoch 103 | /// Run ID 104 | /// Master state 105 | public Task IsMasterDownByAddrAsync(string ip, int port, long currentEpoch, string runId) 106 | { 107 | return WriteAsync(RedisCommands.Sentinel.IsMasterDownByAddr(ip, port, currentEpoch, runId)); 108 | } 109 | 110 | /// 111 | /// Clear state in all masters with matching name 112 | /// 113 | /// Master name pattern 114 | /// Number of masters that were reset 115 | public Task ResetAsync(string pattern) 116 | { 117 | return WriteAsync(RedisCommands.Sentinel.Reset(pattern)); 118 | } 119 | 120 | /// 121 | /// Force a failover as if the master was not reachable, and without asking for agreement from other sentinels 122 | /// 123 | /// Master name 124 | /// Status code 125 | public Task FailoverAsync(string masterName) 126 | { 127 | return WriteAsync(RedisCommands.Sentinel.Failover(masterName)); 128 | } 129 | 130 | /// 131 | /// Start monitoring a new master 132 | /// 133 | /// Master name 134 | /// Master port 135 | /// Quorum count 136 | /// Status code 137 | public Task MonitorAsync(string name, int port, int quorum) 138 | { 139 | return WriteAsync(RedisCommands.Sentinel.Monitor(name, port, quorum)); 140 | } 141 | 142 | /// 143 | /// Remove the specified master 144 | /// 145 | /// Master name 146 | /// Status code 147 | public Task RemoveAsync(string name) 148 | { 149 | return WriteAsync(RedisCommands.Sentinel.Remove(name)); 150 | } 151 | 152 | /// 153 | /// Change configuration parameters of a specific master 154 | /// 155 | /// Master name 156 | /// Config option name 157 | /// Config option value 158 | /// Status code 159 | public Task SetAsync(string masterName, string option, string value) 160 | { 161 | return WriteAsync(RedisCommands.Sentinel.Set(masterName, option, value)); 162 | } 163 | #endregion 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /CSRedis/RedisSentinelClient.Sync.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal.Commands; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace CSRedis 8 | { 9 | public partial class RedisSentinelClient 10 | { 11 | /// 12 | /// Connect to the remote host 13 | /// 14 | /// Connection timeout in milliseconds 15 | /// True if connected 16 | public bool Connect(int timeout) 17 | { 18 | return _connector.Connect(); // TODO: timeout 19 | } 20 | 21 | /// 22 | /// Call arbitrary Sentinel command (e.g. for a command not yet implemented in this library) 23 | /// 24 | /// The name of the command 25 | /// Array of arguments to the command 26 | /// Redis unified response 27 | public object Call(string command, params string[] args) 28 | { 29 | return Write(RedisCommands.Call(command, args)); 30 | } 31 | 32 | T Write(RedisCommand command) 33 | { 34 | return _connector.Call(command); 35 | } 36 | 37 | #region sentinel 38 | /// 39 | /// Ping the Sentinel server 40 | /// 41 | /// Status code 42 | public string Ping() 43 | { 44 | return Write(RedisCommands.Ping()); 45 | } 46 | 47 | /// 48 | /// Get a list of monitored Redis masters 49 | /// 50 | /// Redis master info 51 | public RedisMasterInfo[] Masters() 52 | { 53 | return Write(RedisCommands.Sentinel.Masters()); 54 | } 55 | 56 | /// 57 | /// Get information on the specified Redis master 58 | /// 59 | /// Name of the Redis master 60 | /// Master information 61 | public RedisMasterInfo Master(string masterName) 62 | { 63 | return Write(RedisCommands.Sentinel.Master(masterName)); 64 | } 65 | 66 | /// 67 | /// Get a list of other Sentinels known to the current Sentinel 68 | /// 69 | /// Name of monitored master 70 | /// Sentinel hosts and ports 71 | public RedisSentinelInfo[] Sentinels(string masterName) 72 | { 73 | return Write(RedisCommands.Sentinel.Sentinels(masterName)); 74 | } 75 | 76 | /// 77 | /// Get a list of monitored Redis slaves to the given master 78 | /// 79 | /// Name of monitored master 80 | /// Redis slave info 81 | public RedisSlaveInfo[] Slaves(string masterName) 82 | { 83 | return Write(RedisCommands.Sentinel.Slaves(masterName)); 84 | } 85 | 86 | /// 87 | /// Get the IP and port of the current master Redis server 88 | /// 89 | /// Name of monitored master 90 | /// IP and port of master Redis server 91 | public Tuple GetMasterAddrByName(string masterName) 92 | { 93 | return Write(RedisCommands.Sentinel.GetMasterAddrByName(masterName)); 94 | } 95 | 96 | /// 97 | /// Open one or more subscription channels to Redis Sentinel server 98 | /// 99 | /// Name of channels to open (refer to http://redis.io/ for channel names) 100 | public void Subscribe(params string[] channels) 101 | { 102 | _subscription.Send(RedisCommands.Subscribe(channels)); 103 | } 104 | 105 | /// 106 | /// Close one or more subscription channels to Redis Sentinel server 107 | /// 108 | /// Name of channels to close 109 | public void Unsubscribe(params string[] channels) 110 | { 111 | _subscription.Send(RedisCommands.Unsubscribe(channels)); 112 | } 113 | 114 | /// 115 | /// Open one or more subscription channels to Redis Sentinel server 116 | /// 117 | /// Pattern of channels to open (refer to http://redis.io/ for channel names) 118 | public void PSubscribe(params string[] channelPatterns) 119 | { 120 | _subscription.Send(RedisCommands.PSubscribe(channelPatterns)); 121 | } 122 | 123 | /// 124 | /// Close one or more subscription channels to Redis Sentinel server 125 | /// 126 | /// Pattern of channels to close 127 | public void PUnsubscribe(params string[] channelPatterns) 128 | { 129 | _subscription.Send(RedisCommands.PUnsubscribe(channelPatterns)); 130 | } 131 | 132 | /// 133 | /// Get master state information 134 | /// 135 | /// Host IP 136 | /// Host port 137 | /// Current epoch 138 | /// Run ID 139 | /// Master state 140 | public RedisMasterState IsMasterDownByAddr(string ip, int port, long currentEpoch, string runId) 141 | { 142 | return Write(RedisCommands.Sentinel.IsMasterDownByAddr(ip, port, currentEpoch, runId)); 143 | } 144 | 145 | /// 146 | /// Clear state in all masters with matching name 147 | /// 148 | /// Master name pattern 149 | /// Number of masters that were reset 150 | public long Reset(string pattern) 151 | { 152 | return Write(RedisCommands.Sentinel.Reset(pattern)); 153 | } 154 | 155 | /// 156 | /// Force a failover as if the master was not reachable, and without asking for agreement from other sentinels 157 | /// 158 | /// Master name 159 | /// Status code 160 | public string Failover(string masterName) 161 | { 162 | return Write(RedisCommands.Sentinel.Failover(masterName)); 163 | } 164 | 165 | /// 166 | /// Start monitoring a new master 167 | /// 168 | /// Master name 169 | /// Master port 170 | /// Quorum count 171 | /// Status code 172 | public string Monitor(string name, int port, int quorum) 173 | { 174 | return Write(RedisCommands.Sentinel.Monitor(name, port, quorum)); 175 | } 176 | 177 | /// 178 | /// Remove the specified master 179 | /// 180 | /// Master name 181 | /// Status code 182 | public string Remove(string name) 183 | { 184 | return Write(RedisCommands.Sentinel.Remove(name)); 185 | } 186 | 187 | /// 188 | /// Change configuration parameters of a specific master 189 | /// 190 | /// Master name 191 | /// Config option name 192 | /// Config option value 193 | /// Status code 194 | public string Set(string masterName, string option, string value) 195 | { 196 | return Write(RedisCommands.Sentinel.Set(masterName, option, value)); 197 | } 198 | #endregion 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /CSRedis/RedisSentinelClient.cs: -------------------------------------------------------------------------------- 1 | using CSRedis.Internal; 2 | using CSRedis.Internal.Commands; 3 | using CSRedis.Internal.IO; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Net; 7 | using System.Runtime.Serialization; 8 | using System.Text; 9 | 10 | namespace CSRedis 11 | { 12 | /// 13 | /// Represents a client connection to a Redis sentinel instance 14 | /// 15 | public partial class RedisSentinelClient : IDisposable 16 | { 17 | const int DefaultPort = 26379; 18 | const bool DefaultSSL = false; 19 | const int DefaultConcurrency = 1000; 20 | const int DefaultBufferSize = 1024; 21 | readonly RedisConnector _connector; 22 | readonly SubscriptionListener _subscription; 23 | 24 | /// 25 | /// Occurs when a subscription message is received 26 | /// 27 | public event EventHandler SubscriptionReceived; 28 | 29 | /// 30 | /// Occurs when a subscription channel is added or removed 31 | /// 32 | public event EventHandler SubscriptionChanged; 33 | 34 | /// 35 | /// Occurs when the connection has sucessfully reconnected 36 | /// 37 | public event EventHandler Reconnected; 38 | 39 | /// 40 | /// Get the Redis sentinel hostname 41 | /// 42 | public string Host { get { return GetHost(); } } 43 | 44 | /// 45 | /// Get the Redis sentinel port 46 | /// 47 | public int Port { get { return GetPort(); } } 48 | 49 | /// 50 | /// Get a value indicating whether the Redis sentinel client is connected to the server 51 | /// 52 | public bool Connected { get { return _connector.IsConnected; } } 53 | 54 | /// 55 | /// Get the string encoding used to communicate with the server 56 | /// 57 | public Encoding Encoding { get { return _connector.Encoding; } } 58 | 59 | /// 60 | /// Get or set the connection read timeout (milliseconds) 61 | /// 62 | public int ReceiveTimeout 63 | { 64 | get { return _connector.ReceiveTimeout; } 65 | set { _connector.ReceiveTimeout = value; } 66 | } 67 | 68 | /// 69 | /// Get or set the connection send timeout (milliseconds) 70 | /// 71 | public int SendTimeout 72 | { 73 | get { return _connector.SendTimeout; } 74 | set { _connector.SendTimeout = value; } 75 | } 76 | 77 | /// 78 | /// Get or set the number of times to attempt a reconnect after a connection fails 79 | /// 80 | public int ReconnectAttempts 81 | { 82 | get { return _connector.ReconnectAttempts; } 83 | set { _connector.ReconnectAttempts = value; } 84 | } 85 | 86 | /// 87 | /// Get or set the amount of time to wait between reconnect attempts 88 | /// 89 | public int ReconnectWait 90 | { 91 | get { return _connector.ReconnectWait; } 92 | set { _connector.ReconnectWait = value; } 93 | } 94 | 95 | /// 96 | /// Create a new RedisSentinelClient using default port and encoding 97 | /// 98 | /// Redis sentinel hostname 99 | public RedisSentinelClient(string host) 100 | : this(host, DefaultPort) 101 | { } 102 | 103 | /// 104 | /// Create a new RedisSentinelClient using default encoding 105 | /// 106 | /// Redis sentinel hostname 107 | /// Redis sentinel port 108 | public RedisSentinelClient(string host, int port) 109 | : this(host, port, DefaultSSL) 110 | { } 111 | 112 | /// 113 | /// Create a new RedisSentinelClient using default encoding 114 | /// 115 | /// Redis sentinel hostname 116 | /// Redis sentinel port 117 | /// Set to true if remote Redis server expects SSL 118 | public RedisSentinelClient(string host, int port, bool ssl) 119 | : this(new RedisSocket(ssl), new DnsEndPoint(host, port), DefaultConcurrency, DefaultBufferSize) 120 | { } 121 | 122 | internal RedisSentinelClient(IRedisSocket socket, EndPoint endpoint) 123 | : this(socket, endpoint, DefaultConcurrency, DefaultBufferSize) 124 | { } 125 | 126 | internal RedisSentinelClient(IRedisSocket socket, EndPoint endpoint, int concurrency, int bufferSize) 127 | { 128 | _connector = new RedisConnector(endpoint, socket, concurrency, bufferSize); 129 | _subscription = new SubscriptionListener(_connector); 130 | 131 | _subscription.MessageReceived += OnSubscriptionReceived; 132 | _subscription.Changed += OnSubscriptionChanged; 133 | _connector.Connected += OnConnectionReconnected; 134 | } 135 | 136 | /// 137 | /// Release resoures used by the current RedisSentinelClient 138 | /// 139 | public void Dispose() 140 | { 141 | if (_connector != null) 142 | _connector.Dispose(); 143 | } 144 | 145 | void OnSubscriptionReceived(object sender, RedisSubscriptionReceivedEventArgs args) 146 | { 147 | if (SubscriptionReceived != null) 148 | SubscriptionReceived(this, args); 149 | } 150 | 151 | void OnSubscriptionChanged(object sender, RedisSubscriptionChangedEventArgs args) 152 | { 153 | if (SubscriptionChanged != null) 154 | SubscriptionChanged(this, args); 155 | } 156 | 157 | void OnConnectionReconnected(object sender, EventArgs args) 158 | { 159 | if (Reconnected != null) 160 | Reconnected(this, args); 161 | } 162 | 163 | string GetHost() 164 | { 165 | if (_connector.EndPoint is IPEndPoint) 166 | return (_connector.EndPoint as IPEndPoint).Address.ToString(); 167 | else if (_connector.EndPoint is DnsEndPoint) 168 | return (_connector.EndPoint as DnsEndPoint).Host; 169 | else 170 | return null; 171 | } 172 | 173 | int GetPort() 174 | { 175 | if (_connector.EndPoint is IPEndPoint) 176 | return (_connector.EndPoint as IPEndPoint).Port; 177 | else if (_connector.EndPoint is DnsEndPoint) 178 | return (_connector.EndPoint as DnsEndPoint).Port; 179 | else 180 | return -1; 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /CSRedis/RedisSentinelManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | 6 | // http://redis.io/topics/sentinel-clients 7 | 8 | namespace CSRedis 9 | { 10 | /// 11 | /// Represents a managed connection to a Redis master instance via a set of Redis sentinel nodes 12 | /// 13 | public class RedisSentinelManager : IDisposable 14 | { 15 | const int DefaultPort = 26379; 16 | readonly LinkedList> _sentinels; 17 | string _masterName; 18 | int _connectTimeout; 19 | RedisClient _redisClient; 20 | 21 | /// 22 | /// Occurs when the master connection has sucessfully connected 23 | /// 24 | public event EventHandler Connected; 25 | 26 | /// 27 | /// Create a new RedisSentinenlManager 28 | /// 29 | /// Sentinel addresses (host:ip) 30 | public RedisSentinelManager(params string[] sentinels) 31 | { 32 | _sentinels = new LinkedList>(); 33 | foreach (var host in sentinels) 34 | { 35 | string[] parts = host.Split(':'); 36 | string hostname = parts[0].Trim(); 37 | int port = Int32.Parse(parts[1]); 38 | Add(hostname, port); 39 | } 40 | } 41 | 42 | /// 43 | /// Add a new sentinel host using default port 44 | /// 45 | /// Sentinel hostname 46 | public void Add(string host) 47 | { 48 | Add(host, DefaultPort); 49 | } 50 | 51 | /// 52 | /// Add a new sentinel host 53 | /// 54 | /// Sentinel hostname 55 | /// Sentinel port 56 | public void Add(string host, int port) 57 | { 58 | foreach (var sentinel in _sentinels) 59 | { 60 | if (sentinel.Item1 == host && sentinel.Item2 == port) 61 | return; 62 | } 63 | _sentinels.AddLast(Tuple.Create(host, port)); 64 | } 65 | 66 | /// 67 | /// Obtain connection to the specified master node 68 | /// 69 | /// Name of Redis master 70 | /// Connection timeout (milliseconds) 71 | /// host:port of Sentinel server that responded 72 | public string Connect(string masterName, int timeout = 200) 73 | { 74 | _masterName = masterName; 75 | _connectTimeout = timeout; 76 | 77 | string sentinel = SetMaster(masterName, timeout); 78 | if (sentinel == null) 79 | throw new IOException("Could not connect to sentinel or master"); 80 | 81 | _redisClient.ReconnectAttempts = 0; 82 | return sentinel; 83 | } 84 | 85 | /// 86 | /// Execute command against the master, reconnecting if necessary 87 | /// 88 | /// Command return type 89 | /// Command to execute 90 | /// Command result 91 | public T Call(Func redisAction) 92 | { 93 | if (_masterName == null) 94 | throw new InvalidOperationException("Master not set"); 95 | 96 | try 97 | { 98 | return redisAction(_redisClient); 99 | } 100 | catch (IOException) 101 | { 102 | Next(); 103 | Connect(_masterName, _connectTimeout); 104 | return Call(redisAction); 105 | } 106 | } 107 | 108 | /// 109 | /// Release resources held by the current RedisSentinelManager 110 | /// 111 | public void Dispose() 112 | { 113 | if (_redisClient != null) 114 | _redisClient.Dispose(); 115 | } 116 | 117 | string SetMaster(string name, int timeout) 118 | { 119 | for (int i = 0; i < _sentinels.Count; i++) 120 | { 121 | if (i > 0) 122 | Next(); 123 | 124 | using (var sentinel = Current()) 125 | { 126 | try 127 | { 128 | if (!sentinel.Connect(timeout)) 129 | continue; 130 | } 131 | catch (Exception) 132 | { 133 | continue; 134 | } 135 | 136 | var master = sentinel.GetMasterAddrByName(name); 137 | if (master == null) 138 | continue; 139 | 140 | _redisClient = new RedisClient(master.Item1, master.Item2); 141 | _redisClient.Connected += OnConnectionConnected; 142 | if (!_redisClient.Connect(timeout)) 143 | continue; 144 | 145 | var role = _redisClient.Role(); 146 | if (role.RoleName != "master") 147 | continue; 148 | 149 | foreach (var remoteSentinel in sentinel.Sentinels(name)) 150 | Add(remoteSentinel.Ip, remoteSentinel.Port); 151 | 152 | return sentinel.Host + ':' + sentinel.Port; 153 | } 154 | 155 | } 156 | return null; 157 | } 158 | 159 | RedisSentinelClient Current() 160 | { 161 | return new RedisSentinelClient(_sentinels.First.Value.Item1, _sentinels.First.Value.Item2); 162 | } 163 | 164 | void Next() 165 | { 166 | var first = _sentinels.First; 167 | _sentinels.RemoveFirst(); 168 | _sentinels.AddLast(first.Value); 169 | } 170 | 171 | void OnConnectionConnected(object sender, EventArgs args) 172 | { 173 | if (Connected != null) 174 | Connected(this, new EventArgs()); 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | http://www.apache.org/licenses/LICENSE-2.0.html -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # csredis [![Build status](https://ci.appveyor.com/api/projects/status/cfhtnvf9vuyf5797)](https://ci.appveyor.com/project/ctstone/csredis-675) 2 | 3 | CSRedis is a .NET client for Redis and Redis Sentinel (2.8.12). Includes both synchronous and asynchronous implementations. 4 | 5 | The easiest way to install CSRedis is from [NuGet](https://nuget.org/packages/csredis) via the [Package Manager Console](http://docs.nuget.org/docs/start-here/using-the-package-manager-console): 6 | 7 | **PM> Install-Package csredis** 8 | 9 | 10 | ## Basic usage 11 | Whenever possible, server responses are mapped to the appropriate CLR type. 12 | ```csharp 13 | using (var redis = new RedisClient("yourhost")) 14 | { 15 | string ping = redis.Ping(); 16 | string echo = redis.Echo("hello world"); 17 | DateTime time = redis.Time(); 18 | } 19 | ``` 20 | 21 | **Asynchronous** commands are also available. 22 | ```csharp 23 | using (var redis = new RedisClient("localhost")) 24 | { 25 | // fire-and-forget: results are not captured 26 | for (int i = 0; i < 5000; i++) 27 | { 28 | redis.IncrAsync("test1"); 29 | } 30 | 31 | // callback via ContinueWith: Ping is executed asyncronously, and when a result is ready, the response is printed to screen. 32 | redis.TimeAsync().ContinueWith(t => Console.WriteLine(t.Result)); 33 | 34 | // blocking call 35 | string result = redis.GetAsync("test1").Result; 36 | } 37 | ``` 38 | 39 | Use the IRedisClient or IRedisClientAsync interfaces to use synconous or asyncronous methods exclusively. 40 | ```csharp 41 | using (IRedisClient csredis = new RedisClient(Host)) 42 | { 43 | // only syncronous methods exposed 44 | } 45 | 46 | using (IRedisClientAsync csredis = new RedisClient(Host)) 47 | { 48 | // only asyncronous methods exposed 49 | } 50 | ``` 51 | 52 | ## Pipelining 53 | CSRedis supports pipelining commands to lessen the effects of network overhead on sequential server calls. To enable pipelining, wrap a group of commands between **StartPipe()** and **EndPipe()**. Note that redis-server currently has a 1GB limit on client buffers but CSRedis does not enforce this. Similar performance gains may be obtained by using the deferred Task/Asyncronous methods. 54 | ```csharp 55 | using (var redis = new RedisClient("localhost")) 56 | { 57 | redis.StartPipe(); 58 | var empty1 = redis.Echo("hello"); // returns immediately with default(string) 59 | var empty2 = redis.Time(); // returns immediately with default(DateTime) 60 | object[] result = redis.EndPipe(); // all commands sent to the server at once 61 | var item1 = (string)result[0]; // cast result objects to appropriate types 62 | var item2 = (DateTime)result[1]; 63 | 64 | // automatic MULTI/EXEC pipeline: start a pipe that is also a MULTI/EXEC transaction 65 | redis.StartPipeTransaction(); 66 | redis.Set("key", "value"); 67 | redis.Set("key2", "value2"); 68 | object[] result2 = redis.EndPipe(); // transaction is EXEC'd automatically if DISCARD was not called first 69 | 70 | // DISCARD pipelined transaction 71 | redis.StartPipeTransaction(); 72 | redis.Set("key", 123); 73 | redis.Set("key2", "abc"); 74 | redis.Discard(); 75 | } 76 | ``` 77 | 78 | 79 | ## Why csredis? 80 | There are a handful of .NET redis clients in active development, but none quite suited my needs: clean interface of the native Redis API; Sentinel support; easy-to-use pipelining/async. If there are gaps between CSRedis and another implementation please open an Issue or Pull Request. 81 | 82 | 83 | ## Authentication 84 | Password authentication is handled according to the native API (i.e. not in the connection constructor): 85 | ```csharp 86 | redis.Auth("mystrongpasword"); 87 | ``` 88 | 89 | ## Reconnecting 90 | CSRedis supports a simple reconnect option to handle dropped connections to the same Redis host. See **RedisSentinelManager** for a fuller implementation between multiple masters. 91 | ```csharp 92 | using (var redis = new RedisClient("localhost")) 93 | { 94 | redis.Connected += (s, e) => redis.Auth(Password); // set AUTH, CLIENT NAME, etc 95 | redis.ReconnectAttempts = 3; 96 | redis.ReconnectWait = 200; 97 | // connection will retry 3 times with 200ms in between before throwing an Exception 98 | } 99 | ``` 100 | 101 | ## Flexible hash mapping 102 | Pass any POCO or anonymous object to the generic hash methods: 103 | ```chsarp 104 | redis.HMSet("myhash", new 105 | { 106 | Field1 = "string", 107 | Field2 = true, 108 | Field3 = DateTime.Now, 109 | }); 110 | 111 | MyPOCO hash = redis.HGetAll("my-hash-key"); 112 | ``` 113 | 114 | Or use a string Dictionary: 115 | ```chsarp 116 | redis.HMSet("mydict", new Dictionary 117 | { 118 | { "F1", "string" }, 119 | { "F2", "true" }, 120 | { "F3", DateTime.Now.ToString() }, 121 | }); 122 | 123 | Dictionary mydict = redis.HGetAll("my-hash-key"); 124 | ``` 125 | 126 | Or use the native API: 127 | ```csharp 128 | redis.HMSet("myhash", new[] { "F1", "string", "F2", "true", "F3", DateTime.Now.ToString() }); 129 | ``` 130 | 131 | 132 | ## Transactions 133 | Synchronous transactions are handled using the API calls MULTI/EXEC/DISCARD. Attach an event handler to **RedisClient.TransactionQueued** event to observe server queue replies (typically 'OK'). When inside of a transaction, command return values will be default(T). 134 | ```csharp 135 | redis.TransactionQueued += (s, e) => 136 | { 137 | Console.WriteLine("Transaction queued: {0}({1}) = {2}", e.Command, String.Join(", ", e.Arguments), e.Status); 138 | }; 139 | redis.Multi(); 140 | var empty1 = redis.Set("test1", "hello"); // returns default(String) 141 | var empty2 = redis.Set("test2", "world"); // returns default(String) 142 | var empty3 = redis.Time(); // returns default(DateTime) 143 | object[] result = redis.Exec(); 144 | var item1 = (string)result[0]; 145 | var item2 = (string)result[1]; 146 | var item3 = (DateTime)result[2]; 147 | ``` 148 | 149 | 150 | ## Subscription model 151 | The subscription model is event based. Attach a handler to one or both of SubscriptionChanged/SubscriptionReceived to receive callbacks on subscription events. Opening the first subscription channel blocks the main thread, so unsubscription (and new subscriptions) must be handled by a background thread/task. 152 | 153 | **SubscriptionChanged**: Occurs when a subscription channel is opened or closed 154 | **RedisSubscriptionReceived**: Occurs when a subscription message has been received 155 | 156 | Example: 157 | ```csharp 158 | redis.SubscriptionChanged += (s, e) => 159 | { 160 | Console.WriteLine("There are now {0} open channels", e.Response.Count); 161 | }; 162 | redis.SubscriptionReceived += (s, e) => 163 | { 164 | Console.WriteLine("Message received: {0}", e.Message.Body); 165 | }; 166 | redis.PSubscribe("*"); 167 | ``` 168 | 169 | ## Future-proof 170 | CSRedis exposes a basic **Call()** method that sends arbitrary commands to the Redis server. Use this command to easily implement future Redis commands before they are included in CSRedis. This can also be used to work with "bare-metal" server responses or if a command has been renamed in redis.conf. 171 | ```csharp 172 | object resp = redis.Call("ANYTHING", "arg1", "arg2", "arg3"); 173 | ``` 174 | Note that the response object will need to be cast according to the Redis unified protocol: status (System.String), integer (System.Int64), bulk (System.String), multi-bulk (System.Object[]). 175 | 176 | 177 | ## Streaming responses 178 | For large result sizes, it may be preferred to stream the raw bytes from the server rather than allocating large chunks of memory in place. This can be achieved with **RedisClient.StreamTo()**. Note that this only applies to BULK responses (e.g. GET, HGET, LINDEX, etc). Attempting to stream any other response will result in an InvalidOperationException. Here is an example that stores the response in a MemoryStream 64 bytes at a time. A more useful example might use a FileStream and a larger buffer size. 179 | ```csharp 180 | redis.Set("test", new string('x', 1048576)); // 1MB string 181 | using (var ms = new MemoryStream()) 182 | { 183 | redis.StreamTo(ms, 64, r => r.Get("test")); // read in small 64 byte blocks 184 | byte[] bytes = ms.ToArray(); // optional: get the bytes if needed 185 | } 186 | ``` 187 | 188 | ## Tracing 189 | Use [.NET tracing](http://msdn.microsoft.com/en-us/library/ms733025(v=vs.110).aspx) to expose low level TCP messages 190 | 191 | 192 | ## Sentinel 193 | **RedisSentinelManager** is a managed connection that will automatically obtain a connection to a Redis master node based on information from one or more Redis Sentinel nodes. Async methods coming soon 194 | ```csharp 195 | using (var sentinel = new RedisSentinelManager("host1:123", "host2:456")) 196 | { 197 | sentinel.Add(Host); // add host using default port 198 | sentinel.Add(Host, 36379); // add host using specific port 199 | sentinel.Connected += (s, e) => sentinel.Call(x => x.Auth(Password)); // this will be called each time a master connects 200 | sentinel.Connect("mymaster"); // open connection 201 | var test2 = sentinel.Call(x => x.Time()); // use the Call() lambda to access the current master connection 202 | } 203 | ``` 204 | -------------------------------------------------------------------------------- /csredis.pub.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctstone/csredis/d3a47f25f320df9b29f2a0161393bfa4fc423c01/csredis.pub.snk --------------------------------------------------------------------------------