Xeeny:以.NET标准构建和使用服务的框架
Xeeny:以.NET标准构建和使用服务的框架
Framework For Building And Consuming Cross Platform Services In .Net Standard.
Cross Platform, Duplex, Scalable, Configurable, and Extendable
Table Of Content
What Is XeenyNugetFeaturesTerminologyGet StartedServer SideClient Side Duplex and CallbackDuplex ServerDuplex Client Instance LifetimeService Instance LifetimeCallback Instance LifetimeInitializing Created Instances OperationsManaging The ConnectionAccess Connection From The ServiceAccess Connection From The ClientConnection OptionsConnection TimeoutConnection BuffersKeep Alive Management SerializationSecurityLoggingTransportsPerformanceExtend XeenyCustom TransportCustom Protocol Samples
What is Xeeny
Xeeny is framework for building and consuming services on devices and servers that support - standard.
With Xeeny you can host and consume services anywhere - standard is able to work (e.g Xamarin android, Windows Server, ...). It is Cross Platform, Duplex, Multiple Transports, Asynchronous, Typed Proxies, Configurable, and Extendable
Nuget
Install-Package XeenyFor extensions:Install-Package Xeeny.HttpInstall-Package Xeeny.Extentions.LoggersInstall-Package Xeeny.Serialization.JsonSerializerInstall-Package Xeeny.Serialization.ProtobufSerializer
Features
Current Features:
Building and consuming services in .Net StandardMultiple Transports, You can mix transportsDuplex connectionsTyped proxiesAsynchronous, Scalable, and LightweightExtendable, you can have your custom transport (e.g Behind bluetooth), custom protocol, and custom serialization
Comming:
Integrating AspNetCore (Logger is done, next is DI, Kestrel, and Middleware)StreamingUDP transportP2P Framework
Terminology
Contract: A shared interface between the Service and ClientService Contract: The interface that is referenced by the client to call remotely, and actually implemented on the serverCallback Contract: The interface that is referenced by the service to call remotely, and actually implemented on the clientOperation: Any method in a Contract is called Operation
Get Started
Define service contract (an interface) shared between your client and server
public interface IService{ Task
Server Side
Create the service that implements the contract, services without interfaces won't expose methods to clients
public class Service : IService{ public Task
Create ServiceHost using ServiceHostBuilder
var tcpAddress = "tcp://myhost:9999/myservice";var httpAddress = "http://myhost/myservice";var host = new ServiceHostBuilder
Client Side
Create client connection connection using ConnctionBuilder
var tcpAddress = "tcp://myhost/myservice";var client = await new ConnectionBuilder
Once the connection is created it is connected and now you can call the service remotely
var msg = await client.Echo("Hellow World!");
Duplex and Callback
First define a callback contract (an interface) shared between your service and client to handle the callback
public interface ICallback{ Task OnCallback(string serverMessage);}
Callback contract methods must return void or Task as the server always invoke them as OneWay operations
Duplex Service
In your service, optain the typed callback channel using OperationContext.Current.GetCallback
public Service : IService{ public Task
Call WithCallback
var host = new ServiceHostBuilder
For Duplex connections you will usually have your services PerConnection or Singleton *
Duplex Client
Implement your callback contract in the client side
public class Callback : ICallback{ public void OnServerUpdates(string msg) { Console.WriteLine($"Received callback msg: {msg}"); }}
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.
var address = "tcp://myhost/myservice";var client = await new DuplexConnectionBuilder
Instance Lifetime
Service Instance Lifetime
Xeeny defines three modes for creating service instances
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.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.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.
You define the service instance mode using the InstanceMode enum when creating the ServiceHost
var host = new ServiceHostBuilder
Callback Instance Lifetime
When 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
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.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 networkSingle: All callbacks from the service to any connection created by that builder will be handeled by one instance.
Initializing Created Instances
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 passedSimilar to ServiceHostBuilder, the DuplextConnectionBuilder takes an instance of the callback type allowing you to create the singleton yourselfInstances 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
host.ServiceInstanceCreated += service =>{ service.MyProperty = "Something";}...var builder = new DuplexConnectionBuilder
Operations
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.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)
public interface IService{ [Operation(IsOneWay = true)] void FireAndForget(string message);}
Resolving Operation Conflicts With Names
When 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.
public interface IOtherService{ [Operation(Name = "AnotherEcho")] Task
Managing The Connection
You 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:
State: The connection state: Connecting, Connected, Closing, ClosedStateChanged: Event fired whenever the connection state changesConnect(): Connects to the remote addressClose(): Closes the connectionSessionEnded: Event fired when the connection is closing (State changed to Closing)Dispose(): Disposes the connectionConnectionId: Guid identifies each connection (for now the Id on the server and client don't match)ConnectionName: Friendly connection name for easier debugging and logs analystics
Access Connection From The Service
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.If it is duplex, you get the connection by calling OperationContext.Current.GetConnection(), but most likely by calling OperationContext.Current.GetCallback
public class ChatService : IChatService{ ConcurrentDictionary
Access Connection From The Client
Clients 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
var client = await new ConnectionBuilder
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
var client = await new ConnectionBuilder
Connection Options
All builders expose connection options when you add Server or Transport. the options are:
Timeout: Sets the connection timeout (default 30 seconds)ReceiveTiemout: Is the Idle remote timeout (server default: 10 minutes, client default: Infinity)KeepAliveInterval: Keep alive pinging interval (default 30 seconds)KeepAliveRetries: Number of retries before deciding the connection is off (default 10 retries)SendBufferSize: Sending buffer size (default 4096 byte = 4 KB)ReceiveBufferSize : Receiving buffer size (default 4096 byte = 4 KB)MaxMessageSize: Maximum size of messages (default 1000000 byte = 1 MB)ConnectionNameFormatter: Delegate to set or format ConnectionName (default is null). (see Logging)SecuritySettings: SSL settings (default is null) (see Security)
You get these options configuration action on the server when you call AddXXXServer:
var host = new ServiceHostBuilder
On the client side you get it when calling WithXXXTransport
var client = await new DuplexConnectionBuilder
Connection Timeout
When 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.
The 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.
The 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.
Connection Buffers
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
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.
A 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.
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.
Keep Alive Management
Xeeny 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.
You 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
KeepAliveRetries is the number of failing keep-alive messages, once reached the client decides that the connection is broken and closes.
Setting KeepAliveInterval or KeepAliveRetries on the server has no effect.
Serialization
For 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
MessagePackSerializer: Is the MessagePack serialization implemented by MsgPack.Cli, It is the Default serializer as the serialized data is small and the implementation for - in the given library is fast.JsonSerializer: Json serializer implemented by NewtonsoftProtobufSerializer: Google's ProtoBuffers serializer implemented by Protobuf-net
You can chose the serializer using the builders by calling WithXXXSerializer, just make sure your types are serializable using the selected serializer.
var host = new ServiceHostBuilder
You can also use your own serializer by implementing ISerializer and then calling WithSerializer(ISerializer serializer)
Security
Xeeny uses TLS 1.2 (over TCP only for now), you need to add X509Certificate to the server
var host = new ServiceHostBuilder
And on the client you need to pass the Certificate Name:
await new ConnectionBuilder
If you want to validate remote certificate you can pass the RemoteCertificateValidationCallback optional delegate to SecuritySettings.CreateForClient
Logging
Xeeny uses same logging system found in Asp.Net Core
Console LoggerDebug LoggerCustome Logger
To use loggers add the nuget package of the logger, then call WithXXXLogger where you can pass the LogLevel
You 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.
var client1 = await new DuplexConnectionBuilder
Transports
TCP: SocketsUDP: SocketsWebSockets (To be discontinued or implemented by vtortola WebSocketListener)
Performance
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
Extend Xeeny
Custom Transport
You can get all Xeeny framwork features above to work with your custom transport (Say you want it behind device Blueetooth).
On the server:
Implement XeenyListener abstract classPass it to ServiceHostBuilder
On the client:
Implement IXeenyTransportFactoryPass it to ConnectionBuilder
Custom Protocol
If you want to have your own protocol from scratch, you need to implement your own connectivity, message framing, concurrency, buffering, timeout, keep-alive, ...etc.
On the server:
Implement IListenerPass it to ServiceHostBuilder
On the client
Implement ITransportFactoryPass it to ConnectionBuilder
Samples
Complete Chat ExampleMulti-Transports Example
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~