{"id":15039733,"url":"https://github.com/mhallan/xeeny","last_synced_at":"2025-04-10T00:07:16.591Z","repository":{"id":143585906,"uuid":"120233315","full_name":"MhAllan/Xeeny","owner":"MhAllan","description":"Framework For Building And Consuming Services In .Net Standard","archived":false,"fork":false,"pushed_at":"2018-11-12T11:22:33.000Z","size":230,"stargazers_count":29,"open_issues_count":8,"forks_count":6,"subscribers_count":4,"default_branch":"develop","last_synced_at":"2025-04-10T00:07:03.386Z","etag":null,"topics":["csharp","dot-net","dotnet","dotnetstandard","duplex","json","messagepack","services","tcp","udp","websockets","xamarin"],"latest_commit_sha":null,"homepage":null,"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/MhAllan.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,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-02-04T23:30:45.000Z","updated_at":"2022-12-03T20:39:37.000Z","dependencies_parsed_at":"2023-06-09T00:45:19.284Z","dependency_job_id":null,"html_url":"https://github.com/MhAllan/Xeeny","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MhAllan%2FXeeny","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MhAllan%2FXeeny/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MhAllan%2FXeeny/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MhAllan%2FXeeny/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MhAllan","download_url":"https://codeload.github.com/MhAllan/Xeeny/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248131317,"owners_count":21052819,"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","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":["csharp","dot-net","dotnet","dotnetstandard","duplex","json","messagepack","services","tcp","udp","websockets","xamarin"],"created_at":"2024-09-24T20:43:45.221Z","updated_at":"2025-04-10T00:07:16.572Z","avatar_url":"https://github.com/MhAllan.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"![](http://www.imgim.com/xeeny.png)\n\n\nFramework For Building And Consuming Cross Platform Services In .Net Standard. \n\nCross Platform, Duplex, Scalable, Configurable, and Extendable\n\n## Table Of Content\n* [What Is Xeeny](#what-is-xeeny)\n* [Nuget](#nuget)\n* [Features](#features)\n* [Terminology](#terminology)\n* [Get Started](#get-started)\n    * [Server Side](#server-side)\n    * [Client Side](#client-side)\n* [Duplex and Callback](#duplex-and-callback)\n    * [Duplex Server](#duplex-service)\n    * [Duplex Client](#duplex-client)\n* [Instance Lifetime](#instance-lifetime)\n    * [Service Instance Lifetime](#service-instance-lifetime)\n    * [Callback Instance Lifetime](#callback-instance-lifetime)\n    * [Initializing Created Instances](#initializing-created-instances)\n* [Operations](#operations)\n* [Managing The Connection](#managing-the-connection)\n\t* [Access Connection From The Service](#access-connection-from-the-service)\n   \t* [Access Connection From The Client](#access-connection-from-the-client)\n   \t* [Connection Options](#connection-options)\n\t* [Connection Timeout](#connection-timeout)\n\t* [Connection Buffers](#connection-buffers)\n   \t* [Keep Alive Management](#keep-alive-management)\n* [Serialization](#serialization)\n* [Security](#security)\n* [Logging](#logging)\n* [Transports](#transports)\n* [Performance](#performance)\n* [Extend Xeeny](#extend-xeeny)\n\t* [Custom Transport](#custom-transport)\n\t* [Custom Protocol](#custom-protocol)\n* [Samples](#samples)\n\n## What is Xeeny\nXeeny is framework for building and consuming services on devices and servers that support .net standard.\n\nWith Xeeny you can host and consume services anywhere .net standard is able to work (e.g Xamarin android, Windows Server, ...).\nIt is Cross Platform, Duplex, Multiple Transports, Asynchronous, Typed Proxies, Configurable, and Extendable\n\n## Nuget\n```\nInstall-Package Xeeny\n\nFor extensions:\nInstall-Package Xeeny.Http\nInstall-Package Xeeny.Extentions.Loggers\nInstall-Package Xeeny.Serialization.JsonSerializer\nInstall-Package Xeeny.Serialization.ProtobufSerializer\n```\n\n## Features\nCurrent Features:\n* Building and consuming services in .Net Standard\n* Multiple Transports, You can mix transports\n* Duplex connections\n* Typed proxies\n* Asynchronous, Scalable, and Lightweight\n* Extendable, you can have your custom transport (e.g Behind bluetooth), custom protocol, and custom serialization\n\nComming:\n* Integrating AspNetCore (Logger is done, next is DI, Kestrel, and Middleware)\n* Streaming\n* UDP transport\n* P2P Framework\n\n## Terminology\n* **Contract**: A shared interface between the Service and Client\n* **Service Contract**: The interface that is referenced by the client to call remotely, and actually implemented on the server\n* **Callback Contract**: The interface that is referenced by the service to call remotely, and actually implemented on the client\n* **Operation**: Any method in a Contract is called Operation\n\n## Get Started\n* Define service contract (an interface) shared between your client and server\n```csharp\npublic interface IService\n{\n    Task\u003cstring\u003e Echo(string message);\n}\n```\n### Server Side\n* Create the service that implements the contract, services without interfaces won't expose methods to clients\n```csharp\npublic class Service : IService\n{\n    public Task\u003cstring\u003e Echo(string message)\n    {\n        return Task.FromResult(message);\n    }\n}\n```\n* Create `ServiceHost` using `ServiceHostBuilder\u003cTService\u003e` where \u003cTService\u003e is the service implementation\n* Add Servers to the host using `AddXXXServer` methods\n* Open the host\n\t\n```csharp\nvar tcpAddress = \"tcp://myhost:9999/myservice\";\nvar httpAddress = \"http://myhost/myservice\";\nvar host = new ServiceHostBuilder\u003cService\u003e(InstanceMode.PerCall)\n\t\t.AddTcpServer(tcpAddress)\n\t\t.AddWebSocketServer(httpAddress);\n\t\nawait host.Open();\n```\n\n### Client Side\n* Create client connection connection using `ConnctionBuilder\u003cT\u003e`\n\n```csharp\nvar tcpAddress = \"tcp://myhost/myservice\";\nvar client = await new ConnectionBuilder\u003cIService\u003e()\n\t\t\t.WithTcpTransport(tcpAddress)\n\t\t\t.CreateConnection();\n```\n\n* Once the connection is created it is connected and now you can call the service remotely\n\n```csharp\nvar msg = await client.Echo(\"Hellow World!\");\n```\n\n## Duplex and Callback\n* First define a callback contract (an interface) shared between your service and client to handle the callback\n\n```csharp\npublic interface ICallback\n{\n    Task OnCallback(string serverMessage);\n}\n```\n* **Callback contract methods must return void or Task** as the server always invoke them as OneWay operations\n\n### Duplex Service\n* In your service, optain the typed callback channel using `OperationContext.Current.GetCallback\u003cT\u003e`\n\n```csharp\npublic Service : IService\n{\n    public Task\u003cstring\u003e Join(string name)\n    {\n        CallBackAfter(TimeSpan.FromSeconds(3));\n        return Task.FromResult(\"You joined\");\n    }\n    \n    async void CallBackAfter(TimeSpan delay)\n    {\n        var client = OperationContext.Current.GetCallback\u003cICallback\u003e();\n        await Task.Delay((int)delay.TotalMilliseconds);\n        await client.OnCallBack(\"This is a server callback\");\n    }\n}\n```\n\n* Call `WithCallback\u003cT\u003e` on the builder\n\n```csharp\nvar host = new ServiceHostBuilder\u003cService\u003e(InstanceMode.Single)\n                .WithCallback\u003cICallback\u003e()\n                .AddTcpServer(address)\n                .CreateHost();\nawait host.Open();\n```\n* *For Duplex connections you will usually have your services PerConnection or Singleton* *\n\n### Duplex Client\n* Implement your callback contract in the client side\n\n```csharp\npublic class Callback : ICallback\n{\n    public void OnServerUpdates(string msg)\n    {\n        Console.WriteLine($\"Received callback msg: {msg}\");\n    }\n}\n```\n* Use `DuplexConnectionBuilder` to create the duplex client, note that it is generic class, the first generic argument is the service contract, while the other one is the callback implementation not the contract interface, so the builder knows what type to instantiate when the callback request is received.\n\n```csharp\nvar address = \"tcp://myhost/myservice\";\nvar client = await new DuplexConnectionBuilder\u003cIService, Callback\u003e(InstanceMode.Single)\n                        .WithTcpTransport(address)\n                        .CreateConnection();\nawait client.Join(\"My Name\");\n```\n\n## Instance Lifetime\n### Service Instance Lifetime\nXeeny defines three modes for creating service instances\n* **PerCall**: A PerCall mode instructs the framework to create one instance of the service object for every client request, That is anytime any client invokes a method on the service a new instance is going to handle that request. Once the method is execute the instance is going to be collect by GC like any other out of scope objects.\n* **PerConnection**: A PerConnection mode istructs the framework to create one instance of the service object for each client. That is every time a proxy is connected there is one instance for that client is created, This instance will last as long as the connection between the client and server is open. Once the connection is closed (Or dropped by the network) the instance is removed and becomes available for GC to collect.\n* **Single**: A Singleton instance mode means that there is one instance of the service handles all requests from all clients, This is the typical chat room example. The instance will be removed when the host is closed.\n\nYou define the service instance mode using the `InstanceMode` enum when creating the ServiceHost\n\n```csharp\nvar host = new ServiceHostBuilder\u003cService\u003e(InstanceMode.PerCall)\n\t\t...\n\t\t.CreateHost();\nawait host.Open();\n```\n### Callback Instance Lifetime\nWhen you create duplex connection you pass the callback type and InstanceMode to the `DuplexConnectionBuilder`. The `InstanceMode` acts the same way it does for the service when creating ServiceHost\n* **PerCall**: Every callback from the service will be handled in new instance, the instance is subject to collection by GC once the callback method is done.\n* **PerConnection**: All callbacks from the service to a connection created by that builder will have one instance to handle them, all callbacks to another connection created by that builder will have another callback instance, these instances are subject to collection by GC when the connection is closed or dropped by the network\n* **Single**: All callbacks from the service to any connection created by that builder will be handeled by one instance.\n\n### Initializing Created Instances\n* `ServiceHostBuilder` constructor has one overload that takes instance of the service type, This allows you to create the instance and pass it to the builder, the result is `InstanceMode.Single` using the object you passed\n* Similar to `ServiceHostBuilder`, the `DuplextConnectionBuilder` takes an instance of the callback type allowing you to create the singleton yourself\n* Instances that are `PerCall` and `PerConnection` are created by the framework, you still can initialize them after being constructed and before executing any method by listening to the events: `ServiceHost\u003cTService\u003e.ServiceInstanceCreated` event and `DuplextConnectionBuilder\u003cTContract, TCallback\u003e.CallbackInstanceCreated`\n* Once AspNetCore Dependency System (IServiceCollection and IServiceResolver) are used you will be able to have easy DI.\n\n```csharp\nhost.ServiceInstanceCreated += service =\u003e\n{\n    service.MyProperty  = \"Something\";\n}\n...\nvar builder = new DuplexConnectionBuilder\u003cIService, Callback\u003e(InstanceMode.PerConnection)\n                    .WithTcpTransport(tcpAddress);\n                    \nbuilder.CallbackInstanceCreated += callback =\u003e\n{\n    callback...\n}\nvar client = builder.CreateConnection();\n```\n\n## Operations\n* **Two Way Operations**: Methods by default are two way, even methods that return **void** or **Task** are two way methods. A two way method means that the method waits the service to finish executing the operation before it returns.\n* **One Way Operations**: One way operations return when the server receives the messages (before the service invokes it), These are fire and forget methods. To define a method as one way operation you have to attribute it using `Operation` attribute passing **IsOneWay = true** in the contract (The interface)\n\n```csharp\npublic interface IService\n{\n    [Operation(IsOneWay = true)]\n    void FireAndForget(string message);\n}\n```\n\n### Resolving Operation Conflicts With Names\nWhen you have methods overload in one interface (or a similar method signature in a parent interface) you have to tell them apart using `Operation` attribute by setting `Name` property. This applies for both Service and Callback contracts.\n\n```csharp\npublic interface IOtherService\n{\n    [Operation(Name = \"AnotherEcho\")]\n    Task\u003cstring\u003e Echo(string message);\n}\npublic interface IService : IOhterService\n{\n    Task\u003cstring\u003e Echo(string message);\n}\nclass Service : IService, IOtherService\n{\n    public Task\u003cstring\u003e Echo(string message)\n    {\n      return Task.FromResult($\"Echo: {message}\");\n    }\n        \n    Task\u003cstring\u003e IOtherService.Echo(string message)\n    {\n      return Task.FromResult($\"This is the other Echo: {message}\");\n    }\n}\n```\n## Managing The Connection\nYou will want to access the underlying connection to manage it, like monitoring it's status, listen to events, or manage it manually (close or open it). The connection is exposed through `IConnection` interface which provides these funtionalities:\n* `State`: The connection state: `Connecting`, `Connected`, `Closing`, `Closed`\n* `StateChanged`: Event fired whenever the connection state changes\n* `Connect()`: Connects to the remote address\n* `Close()`: Closes the connection\n* `SessionEnded`: Event fired when the connection is closing (`State` changed to `Closing`)\n* `Dispose()`: Disposes the connection\n* `ConnectionId`: Guid identifies each connection (for now the Id on the server and client don't match)\n* `ConnectionName`: Friendly connection name for easier debugging and logs analystics\n\n### Access Connection From The Service\n* If your service host doesn't define a callback you get the connection using `OperationContext.Current.GetConnection()` at the beginning of your method and before the service method spawn any new thread.\n* If it is duplex, you get the connection by calling `OperationContext.Current.GetConnection()`, but most likely by calling `OperationContext.Current.GetCallback\u003cTCallback\u003e`. The returned instance is an instance that is emitted at runtime and implements your callback contract (defined in the generic parameter `TCallback`). This auto-generated type implements `IConnection` as well, so anytime you want to access connection functions of the challback channel just cast it to `IConnection`\n\n```csharp\npublic class ChatService : IChatService\n{\n   ConcurrentDictionary\u003cstring, ICallback\u003e _clients = new ConcurrentDictionary\u003cstring, ICallback\u003e();\n   \n   ICallback GetCaller() =\u003e OperationContext.Current.GetCallback\u003cICallback\u003e();\n\n   public void Join(string id)\n   {\n      var caller = GetCaller();\n      _clients.AddOrUpdate(id, caller, (k, v) =\u003e caller);\n      ((IConnection)caller).SessionEnded += s =\u003e\n      {\n         _clients.TryRemove(id, out ICallback _);\n      };\n   }\n}\n```\n### Access Connection From The Client\nClients are instances of auto-generated types that are emitted at runtime and implement your service contract interface. Together with the contract the emitted type implements `IConnection` which means you can cast any client (Duplex or not) to `IConnection`\n\n```csharp\nvar client = await new ConnectionBuilder\u003cIService\u003e()\n                  \t.WithTcpTransport(address)\n                  \t.CreateConnection();\nvar connection = (IConnection)client;\nconnection.StateChanged += c =\u003e Console.WriteLine(c.State);\nconnection.Close()\n```\n* The `CreateConnection` method takes one optional parameter of type boolean which is `true` by default. This flag indicates if the generated connection will connect to the server or not. by default anytime `CreateConnection` is called the generated connection will connect automatically. Sometimes you want to create connections and want to connect them later, to do that you pass `false` to the `CreateConnection` method then open your connection manually when you want\n\n```csharp\nvar client = await new ConnectionBuilder\u003cIService\u003e()\n\t\t\t.WithTcpTransport(address)\n\t\t\t.CreateConnection(false);\nvar connection = (IConnection)client;\n...\nawait connection.Connect();\n```\n\n### Connection Options\nAll builders expose connection options when you add Server or Transport. the options are:\n* `Timeout`: Sets the connection timeout (_default 30 seconds_)\n* `ReceiveTiemout`: Is the Idle remote timeout (_server default: 10 minutes, client default: Infinity_)\n* `KeepAliveInterval`: Keep alive pinging interval (_default 30 seconds_)\n* `KeepAliveRetries`: Number of retries before deciding the connection is off (_default 10 retries_)\n* `SendBufferSize`: Sending buffer size (_default 4096 byte = 4 KB_)\n* `ReceiveBufferSize` : Receiving buffer size (_default 4096 byte = 4 KB_)\n* `MaxMessageSize`: Maximum size of messages (_default 1000000 byte = 1 MB_)\n* `ConnectionNameFormatter`: Delegate to set or format `ConnectionName` (_default is null_). (see [Logging](#logging))\n* `SecuritySettings`: SSL settings (_default is null_) (see [Security](#security))\n\nYou get these options configuration action on the server when you call AddXXXServer:\n\n```csharp\nvar host = new ServiceHostBuilder\u003cChatService\u003e(InstanceMode.Single)\n\t\t.WithCallback\u003cICallback\u003e()\n\t\t.AddTcpServer(address, options =\u003e\n\t\t{\n\t\t\toptions.Timeout = TimeSpan.FromSeconds(10);\n\t\t})\n\t\t.WithConsoleLogger()\n\t\t.CreateHost();\n\nawait host.Open();\n```\nOn the client side you get it when calling WithXXXTransport\n\n```csharp\nvar client = await new DuplexConnectionBuilder\u003cIChatService, MyCallback\u003e(new MyCallback())\n\t\t.WithTcpTransport(address, options =\u003e\n\t\t{\n\t\t\toptions.KeepAliveInterval = TimeSpan.FromSeconds(5);\n\t\t})\n\t\t.WithConsoleLogger()\n\t\t.CreateConnection();\n```\n### Connection Timeout\nWhen you set `Timeout` and the request doesn't complete during that time the connection will be closed and you have to create new clien. If the `Timeout` is set on the server side that will define the callback timeout and the connection will be closed when the callback isn't complete during that time. Remember that callaback is one way operation and all one way operations complete when the other side receives the message and before the remote method is executed.\n\nThe `ReceiveTimeout` is the \"_Idle Remote Timeout_\" If you set it on the server it will define the timeout for the server to close inactive clients who are the clients that are not sending any request or KeepAlive message during that time.\n\nThe `ReceiveTimeout` on the client is set to _Infinity_ by default, if you set it on the duplex client you are instructing the client to ignore callbacks that don't come during that time which is a weird scenario but still possible if you chose to do so.\n\n### Connection Buffers\n`ReceiveBufferSize` is the size of the receiving buffer. Setting it to small values won't affect the ability of receiving big messages, buf if that size is significantly small comparing to messages to receive then introduce more IO operations. You better leave the default value at the beginning then if needed do your load testing and analysing to find the size that performs good and occupies \n\n`SendBufferSize` is the size of the sending buffer. Setting it to small values won't affect the ability of sending big messages, buf if that size is significantly small comparing to messages to send then introduce more IO operations. You better leave the default value at the beginning then if needed do your load testing and analysing to find the size that performs good and occupies less memory.\n\nA receiver's `ReceiveBufferSize` should equal the sender's  `SendBufferSize` because some transports like UDP won't work well if these two size are not equal. For now Xeeny doesn't check buffer sizes but in the future I am modifying the protocol to include this check during the Connect processing.\n\n`MaxMessageSize` is the maximum allowed number of bytes to receive. This value has nothing to do with buffers so it doesn't affect the memory or the performance. This value is important though for validating your clients and preventing huge messages from clients, Xeeny uses size-prefix protocol so when a message arrives it will be bufferd on a buffer of size `ReceiveBufferSize` which must be ways smaller than `MaxMessageSize`, After the message arrives the size header is read, if the size is bigger than `MaxMessageSize` the message is rejected and the connection is closed.\n\n### Keep Alive Management\nXeeny uses it's own keep-alive messages because not all kind of transports has built-in keep-alive mechanism. These messages are 5 bytes flow from the client to the server only. The interval `KeepAliveInterval` is 30 seconds by default, when you set it on the client the client will send a ping message if it didn't successfully send anything during the last `KeepAliveInterval`.\n\nYou have to set `KeepAliveInterval` to be less than the server's `ReceiveTimeout`, at least 1/2 or 1/3 of server's `ReceiveTimeout` because the server will timeout and closes the connection if it didn't receive anything during it's `ReceiveTimeout` \n\n`KeepAliveRetries` is the number of failing keep-alive messages, once reached the client decides that the connection is broken and closes.\n\nSetting `KeepAliveInterval` or `KeepAliveRetries` on the server has no effect.\n\n## Serialization\nFor Xeeny to be able to marshal method parameters and return types on the wire it needs to serialize them. There are three serializers already supported in the framework\n* `MessagePackSerializer`: Is the MessagePack serialization implemented by [MsgPack.Cli](https://github.com/msgpack/msgpack-cli), It is the Default serializer as the serialized data is small and the implementation for .net in the given library is fast.\n* `JsonSerializer`: Json serializer implemented by [Newtonsoft](https://github.com/JamesNK/Newtonsoft.Json)\n* `ProtobufSerializer`: Google's ProtoBuffers serializer implemented by [Protobuf-net](https://github.com/mgravell/protobuf-net)\n\nYou can chose the serializer using the builders by calling `WithXXXSerializer`, just make sure your types are serializable using the selected serializer.\n\n```csharp\nvar host = new ServiceHostBuilder\u003cChatService\u003e(InstanceMode.Single)\n\t\t.WithCallback\u003cICallback\u003e()\n\t\t.WithProtobufSerializer()\n\t\t.CreateHost();\n\nawait host.Open();\n```\n\n* You can also use your own serializer by implementing ISerializer and then calling `WithSerializer(ISerializer serializer)`\n\n## Security\nXeeny uses TLS 1.2 (over TCP only for now), you need to add `X509Certificate` to the server\n```csharp\n var host = new ServiceHostBuilder\u003cService\u003e(...)\n \t\t.AddTcpServer(tcpAddress, options =\u003e\n\t\t{\n\t\t\toptions.SecuritySettings = SecuritySettings.CreateForServer(x509Certificate2);\n\t\t})\n\t\t...\n```\nAnd on the client you need to pass the `Certificate Name`:\n```csharp\nawait new ConnectionBuilder\u003cIService\u003e()\n\t\t.WithTcpTransport(tcpAddress, options =\u003e\n\t\t{\n\t\t\toptions.SecuritySettings = SecuritySettings.CreateForClient(certificateName);\n\t\t})\n\t\t...\n```\nIf you want to validate remote certificate you can pass the `RemoteCertificateValidationCallback` optional delegate to `SecuritySettings.CreateForClient`\n\n## Logging\nXeeny uses same logging system found in Asp.Net Core\n* Console Logger\n* Debug Logger\n* Custome Logger\n\nTo use loggers add the nuget package of the logger, then call `WithXXXLogger` where you can pass the `LogLevel`\n\nYou may like to name connections so they are easy to spot when debugging or analysing logs, you can do that by setting `ConnectionNameFormatter` function delegate in the options which is passed `IConnection.ConnectionId` as parameter and the return will be assigned to `IConnection.ConnectionName`.\n\n```csharp\nvar client1 = await new DuplexConnectionBuilder\u003cIChatService, Callback\u003e(callback1)\n                        .WithTcpTransport(address, options =\u003e\n\t\t\t{\n\t\t\t\toptions.ConnectionNameFormatter = id =\u003e $\"First-Connection ({id})\";\n\t\t\t})\n\t\t\t.WithConsoleLogger(LogLevel.Trace)\n\t\t\t.CreateConnection();\n```\n\n## Transports \n* TCP: Sockets\n* UDP: Sockets\n* WebSockets (To be discontinued or implemented by [vtortola WebSocketListener](https://github.com/vtortola/WebSocketListener))\n\n\n## Performance\n* Xeeny is built to be high performance and async, having async contracts allows the framework to be fully async. Try always to have your operations to return `Task` or `Task\u003cT\u003e` instead of `void` or `T`. This will save that one extra thread that will be waiting the underlying async socket to complete in case your operations aren't async.\n\n* The overhead in Xeeny is when it needs to emit _\"New\"_ types at runtime. It does that when you create `ServiceHost\u003cTService\u003e` (calling `ServiceHostBuilder\u003cTService\u003e.CreateHost()`) but that happens once per type, so once xeeny emitted the first host of the given type creating more hosts of that type has no performance issues. anyway this is usually your application start.\n\n* Another place where emitting types happen is when you create the first client of a given contract or callback type (calling `CreateConnection`). once the first type of that proxy is emitter next clients will be created without overhead. (note that you are still creating a new socket and new connection unless you pass `false` to `CreateConnection`).\n\n* Calling `OperationContext.Current.GetCallback\u003cT\u003e` also emits runtime type, like all other emissions above the emitted type is cached and the overhead happens only at the first call. you can call this method as many as you like but you better cache the return.\n\n## Extend Xeeny\n#### Custom Transport\nYou can get all Xeeny framwork features above to work with your custom transport (Say you want it behind device Blueetooth).\n##### On the server:\n* Implement `XeenyListener` abstract class\n* Pass it to `ServiceHostBuilder\u003cT\u003e.AddCustomServer()`\n##### On the client:\n* Implement `IXeenyTransportFactory`\n* Pass it to `ConnectionBuilder\u003cT\u003e.WithCustomTransport()`\n\n#### Custom Protocol\nIf you want to have your own protocol from scratch, you need to implement your own connectivity, message framing, concurrency, buffering, timeout, keep-alive, ...etc.\n##### On the server:\n* Implement `IListener`\n* Pass it to `ServiceHostBuilder\u003cT\u003e.AddCustomServer()`\n##### On the client\n* Implement `ITransportFactory`\n* Pass it to `ConnectionBuilder\u003cT\u003e.WithCustomTransport()`\n\n## Samples\n* [Complete Chat Example](https://github.com/MhAllan/Xeeny/tree/develop/Src/Xeeny.ChatExample)\n* [Multi-Transports Example](https://github.com/MhAllan/Xeeny/tree/develop/Src/Xeeny.ConsoleTest)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmhallan%2Fxeeny","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmhallan%2Fxeeny","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmhallan%2Fxeeny/lists"}