{"id":29765552,"url":"https://github.com/drenalol/tcpclientio","last_synced_at":"2025-07-27T00:14:57.348Z","repository":{"id":37853233,"uuid":"259093284","full_name":"Drenalol/TcpClientIo","owner":"Drenalol","description":"TcpClientIo Wrapper of TcpClient what help focus on WHAT you transfer over TCP, not HOW","archived":false,"fork":false,"pushed_at":"2024-08-05T16:33:24.000Z","size":579,"stargazers_count":2,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-18T04:47:08.945Z","etag":null,"topics":["async","c-sharp","csharp","dotnet","pipeline","serialization","tcp","tcp-client","threadsafe"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Drenalol.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2020-04-26T17:36:29.000Z","updated_at":"2024-08-05T16:33:28.000Z","dependencies_parsed_at":"2022-06-22T21:22:03.048Z","dependency_job_id":"be1bdd38-3773-4952-9405-cb5821258706","html_url":"https://github.com/Drenalol/TcpClientIo","commit_stats":{"total_commits":140,"total_committers":2,"mean_commits":70.0,"dds":"0.26428571428571423","last_synced_commit":"73b9583a4c645db1863bc7170393605a74b9abb2"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/Drenalol/TcpClientIo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Drenalol%2FTcpClientIo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Drenalol%2FTcpClientIo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Drenalol%2FTcpClientIo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Drenalol%2FTcpClientIo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Drenalol","download_url":"https://codeload.github.com/Drenalol/TcpClientIo/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Drenalol%2FTcpClientIo/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267274179,"owners_count":24062705,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-26T02:00:08.937Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","c-sharp","csharp","dotnet","pipeline","serialization","tcp","tcp-client","threadsafe"],"created_at":"2025-07-27T00:14:52.667Z","updated_at":"2025-07-27T00:14:57.337Z","avatar_url":"https://github.com/Drenalol.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TcpClientIo\nWrapper of [TcpClient](https://github.com/dotnet/runtime/blob/c7a246c000747ec728ac862b7a503348b103df0e/src/libraries/System.Net.Sockets/src/System/Net/Sockets/TCPClient.cs \"Source Code dotnet/corefx/TCPClient.cs\") what help focus on **WHAT** (Declarative) you transfer over TCP not **HOW** (Imperative)\n\n- Thread-safe\n- Serialization with attribute schema\n- Big/Little endian\n- Async\n- Cancellation support\n\n[![NuGet Pre Release](https://img.shields.io/nuget/vpre/TcpClientIo.svg?style=for-the-badge\u0026logo=appveyor)](https://www.nuget.org/packages/TcpClientIo/)\n[![netstandard 2.1](https://img.shields.io/badge/netstandard-2.1-brightgreen.svg?style=for-the-badge\u0026logo=appveyor)](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) \n## Documentation\n#### Prerequisites\nYour TCP Server accepts and send messages with application-level header (id, length, etc)\n\n##### Example byte array\n| byte[] | 7B | 00 | 00 | 00 | 06 | 00 | 00 | 00 | 00 | D0 | 08 | A7 | 79 | 28 | B7 | 08 | A3 | 0B | 59 | 13 | 49 | 27 | 37 | 46 | B6 | D0 | 75 | A2 | EF | 07 | FA | 1F | 48 | 65 | 6C | 6C | 6F | 21 |\n|--------|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|\n##### Serialization process\n| Property name | Index | Length | Bytes                                                            | Value                                          | Reverse  | Custom converter |\n|---------------|--------|--------|------------------------------------------------------------------|------------------------------------------------|---------|------------------|\n| Id            | 0      | 4      | [7B, 00, 00, 00]                                                 | 123                                            | false   | false            |\n| * BodyLength  | 4      | 4      | [06, 00, 00, 00]                                                 | 6                                              | false   | false            |\n| DateTime      | 8      | 8      | [00, D0, 08, A7, 79, 28, B7, 08]                                 | \"1991-02-07 10:00:00\" as DateTime              | false   | true             |\n| Guid          | 16     | 16     | [A3, 0B, 59, 13, 49, 27, 37, 46, B6, D0, 75, A2, EF, 07, FA, 1F] | \"13590ba3-2749-4637-b6d0-75a2ef07fa1f\" as Guid | false   | true             |\n| * Body        | 32     | 6      | [48, 65, 6C, 6C, 6F, 21]                                         | \"Hello!\" as string                             | false   | true             |\n`* Mandatory if at least one is set.`\n#### Examples\n###### Example #1. Sending and receiving.\n```c#\n// Creating TcpClientIo instance with schema of request/response and uint ID type\nvar tcpClient = new TcpClientIo\u003cuint, Request, Response\u003e(IPAddress.Any, 10000, TcpClientIoOptions.Default);\n// Or without ID type (if your transport does'nt have id, only Body and Length)\nvar tcpClient = new TcpClientIo\u003cRequest, Response\u003e(IPAddress.Any, 10000, TcpClientIoOptions.Default);\n\n// Creating request\nRequest request = new Request\n{\n    // Serialized to [7B, 00, 00, 00], but if we set force reverse = true,\n    // it will serialized to [00, 00, 00, 7B] (My arch is Little-Endian)\n    Id = 123U, \n    \n    // The serializer will take the length of the TcpTypeBody and overwrite the value.\n    // Serialized to [06, 00, 00, 00] because the Data property has a length = 6\n    Length = 0,\n\n    // Will be used custom converter DateTime (about converters, read below)\n    // Serialized to [00, D0, 08, A7, 79, 28, B7, 08]\n    DateTime = DateTime.Parse(\"1991-02-07 10:00:00\"),\n\n    // Will be used custom converter Guid\n    // Serialized to [A3, 0B, 59, 13, 49, 27, 37, 46, B6, D0, 75, A2, EF, 07, FA, 1F]\n    Guid = Guid.Parse(\"13590ba3-2749-4637-b6d0-75a2ef07fa1f\"),\n    \n    // Will be used custom converter string\n    // Serialized to [48, 65, 6C, 6C, 6F, 21]\n    Data = \"Hello!\"\n};\n\n// Send request asynchronously\nawait tcpClient.SendAsync(request, CancellationToken.None);\n\n// Receive response in overtype ITcpBatch\u003cResponse\u003e by identifier asynchronously.\n// Identifier is strongly-typed, you must use the type specified in the request.\nITcpBatch\u003cResponse\u003e resultBatch = await tcpClient.ReceiveAsync(123U, CancellationToken.None);\n\n// Or if schema does not have TcpDataType.Id (Available from 1.0.9)\nITcpBatch\u003cResponse\u003e resultBatch = await tcpClient.ReceiveAsync(CancellationToken.None);\n\n// Batch support iteration\nforeach (var response in resultBatch)\n{\n    // Hello!\n    Console.WriteLine(response.Data);\n}\n// and LINQ queries\nvar response = resultBatch.First();\n// Hello!\nConsole.WriteLine(response.Data);\n\n// Check result\nAssert.AreEqual(request.Id, response.Id);\nAssert.AreEqual(request.BodyLength, response.BodyLength);\nAssert.AreEqual(request.Data, response.Data);\n\n//Stop \u0026 Cleanup\nawait tcpClient.DisposeAsync();\n```\n###### Example #2. Consuming asynchronously. (Available from 1.0.9, netstandard2.1 only)\n```c#\n// GetConsumingAsyncEnumerable works like a stream and will be stopped by cancellation when necessary.\nawait foreach (ITcpBatch\u003cResponse\u003e batch in tcpClient.GetConsumingAsyncEnumerable(CancellationToken.None))\n{\n    // manual iterate batch\n    foreach (var response in batch)\n    {\n        // work with response\n    }\n}\n// or we can use Expandable method (will iterate batch for us inside)\nawait foreach (Response response in tcpClient.GetExpandableConsumingAsyncEnumerable(CancellationToken.None))\n{\n    // work with response\n}\n```\n###### Example #3. TcpClient from TcpListener. (Available from 1.0.9, netstandard2.1 only)\n```c#\n// Suppose you have a listener.\nvar listener = TcpListener.Create(10000);\nlistener.Start();\n\n// Get TcpClient instance\nvar tcpClient = await _listener.AcceptTcpClientAsync();\n\n// Create TcpClientIo and pass it TcpClient\nvar tcpClientIo = new TcpClientIo\u003cuint, Request, Response\u003e(tcpClient, TcpClientIoOptions.Default);\n\n//Start consuming from TcpClientIo\nawait foreach (ITcpBatch\u003cuint, Request, Response\u003e batch in tcpClientIo.GetConsumingAsyncEnumerable(CancellationToken.None))\n{\n    foreach (var response in batch)\n    {\n        // work\n    }\n}\n```\n###### Example #4. Compose properties. (Available from 1.4.0)\n```c#\n// RecursiveMock\npublic class RecursiveMock\u003cT\u003e\n{\n    [TcpData(0, 4, TcpDataType.Length)]  \n    public int Length { get; set; }\n\n    [TcpData(4, TcpDataType = TcpDataType.Compose)]\n    public T Data { get; set; }\n}\n\n// Creating TcpClientIo instance with schema with generics\nvar tcpClient = new TcpClientIo\u003cRecursiveMock\u003cRecursiveMock\u003cRecursiveMock\u003clong\u003e\u003e\u003e, RecursiveMock\u003cRecursiveMock\u003cRecursiveMock\u003clong\u003e\u003e\u003e\u003e(IPAddress.Any, 10000, TcpClientIoOptions.Default);\n\n// Compose RecursiveMock\nvar request = new RecursiveMock\u003cRecursiveMock\u003cRecursiveMock\u003clong\u003e\u003e\u003e();\n\n// Send request asynchronously\nawait tcpClient.SendAsync(request, CancellationToken.None);\n\n// Receive response\nvar response = await tcpClient.ReceiveAsync(CancellationToken.None).Single();\n\n// Check data\nAssert.NotNull(response); // check RecursiveMock\nAssert.NotNull(response.Data); // check RecursiveMock.RecursiveMock\nAssert.NotNull(response.Data.Data); // check RecursiveMock.RecursiveMock.RecursiveMock\nAssert.IsInstanceOf\u003clong\u003e(response.Data.Data.Data); // check RecursiveMock.RecursiveMock.RecursiveMock.long\n```\n#### Attribute schema\nProperties\n\n`Index` Property position in Byte Array.\n\n`Length` Property length in bytes. (If TcpDataType set to TcpDataType.Body or TcpDataType.Compose, is ignored and will be overwritten by the serializer.)\n\n`TcpDataType` Sets the serialization rule for this property. Available: `MetaData` (default), `Id`, `Length`, `Body`, `Compose`.\n\n`Reverse` Reverses the sequence of the bytes from serialized property (used for cases where the receiving side uses a different endianness.)\n##### Example: Without generics\nRequest with first 32 bytes header, and body\n```c#\npublic class Request\n{\n    // TcpDataType.Id not mandatory from 1.0.9\n    [TcpData(0, 4, TcpDataType.Id)]\n    public uint Id { get; set; }\n\n    // TcpDataType.Length mandatory if TcpDataType.Body set\n    [TcpData(4, 4, TcpDataType.Length)]\n    public uint BodyLength { get; set; }\n\n    [TcpData(8, 8)]\n    public DateTime DateTime { get; set; }\n\n    [TcpData(16, 16)]\n    public Guid Guid { get; set; }\n\n    // TcpDataType.Body mandatory if TcpDataType.Length set\n    [TcpData(32, TcpDataType = TcpDataType.Body)]\n    public string Data { get; set; }\n}\n```\n##### Example: With generics (Available from 1.4.0)\n```c#\npublic class RecursiveMock\u003cT\u003e\n{\n    // TcpDataType.Length mandatory if TcpDataType.Compose set\n    [TcpData(0, 4, TcpDataType.Length)]  \n    public int Length { get; set; }\n    \n    // TcpDataType.Compose mandatory if TcpDataType.Length set\n    // Supports: Class, Sctruct, Primitive Types\n    [TcpData(4, TcpDataType = TcpDataType.Compose)]\n    public T Data { get; set; }\n}\n```\n#### Built-in converters\nSerializer use stock BitConverter and it support 10 types\n```c#\ntypeof(bool)\ntypeof(char)\ntypeof(double)\ntypeof(short)\ntypeof(int)\ntypeof(long)\ntypeof(float)\ntypeof(ushort)\ntypeof(uint)\ntypeof(ulong)\n```\n#### Custom converters\nFor specific types you must create custom converter and pass it to TcpClientIoOptions\n\nConverters below already included in package, but not added in list of converters when creating TcpClientIo\n```c#\npublic class TcpDateTimeConverter : TcpConverter\u003cDateTime\u003e\n{\n    public override byte[] Convert(DateTime input) =\u003e BitConverter.GetBytes(input.ToBinary());\n    public override DateTime ConvertBack(ReadOnlySpan\u003cbyte\u003e input) =\u003e DateTime.FromBinary(BitConverter.ToInt64(input));\n}\n\npublic class TcpGuidConverter : TcpConverter\u003cGuid\u003e\n{\n    public override byte[] Convert(Guid input) =\u003e input.ToByteArray();\n    public override Guid ConvertBack(ReadOnlySpan\u003cbyte\u003e input) =\u003e new Guid(input);\n}\n\npublic class TcpUtf8StringConverter : TcpConverter\u003cstring\u003e\n{\n    public override byte[] Convert(string input) =\u003e Encoding.UTF8.GetBytes(input);\n    public override string ConvertBack(ReadOnlySpan\u003cbyte\u003e input) =\u003e Encoding.UTF8.GetString(input);\n}\n```\n```c#\nvar options = new TcpClientIoOptions\n{\n    Converters = new List\u003cTcpConverter\u003e\n    {\n        new TcpDateTimeConverter(),\n        new TcpGuidConverter(),\n        new TcpUtf8StringConverter()\n    };\n}\n\nvar tcpClient = new TcpClientIo\u003cRequest, Response\u003e(IPAddress.Any, 10000, options);\n```\n## Dependencies\n* [AsyncEx](https://github.com/StephenCleary/AsyncEx)\n* [WaitingDictionary](https://github.com/Drenalol/WaitingDictionary)\n* [Microsoft.Extensions.Logging.Abstractions](https://github.com/dotnet/extensions#package-list)\n* [System.IO.Pipelines](https://github.com/dotnet/runtime/tree/master/src/libraries/System.IO.Pipelines)\n* [System.Threading.Tasks.Dataflow](https://github.com/dotnet/runtime/tree/master/src/libraries/System.Threading.Tasks.Dataflow)\n* [System.Collections.Immutable](https://github.com/dotnet/runtime/tree/master/src/libraries/System.Collections.Immutable)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrenalol%2Ftcpclientio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdrenalol%2Ftcpclientio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrenalol%2Ftcpclientio/lists"}