.Net Standard でクロスプラットフォーム サービスを構築および利用するためのフレームワーク。
クロスプラットフォーム、デュプレックス、スケーラブル、構成可能、拡張可能
Xeeny は、.net 標準をサポートするデバイスおよびサーバー上でサービスを構築および利用するためのフレームワークです。
Xeeny を使用すると、.net 標準が動作できる場所 (Xamarin Android、Windows Server など) であればどこでもサービスをホストし、利用できます。クロスプラットフォーム、デュプレックス、複数のトランスポート、非同期、型付きプロキシ、構成可能、拡張可能です。
Install-Package Xeeny
For extensions:
Install-Package Xeeny.Http
Install-Package Xeeny.Extentions.Loggers
Install-Package Xeeny.Serialization.JsonSerializer
Install-Package Xeeny.Serialization.ProtobufSerializer
現在の機能:
来る:
public interface IService
{
Task < string > Echo ( string message ) ;
} public class Service : IService
{
public Task < string > Echo ( string message )
{
return Task . FromResult ( message ) ;
}
}ServiceHostBuilder<TService>を使用してServiceHostを作成します。 はサービス実装です。AddXXXServerメソッドを使用してサーバーをホストに追加する var tcpAddress = "tcp://myhost:9999/myservice" ;
var httpAddress = "http://myhost/myservice" ;
var host = new ServiceHostBuilder < Service > ( InstanceMode . PerCall )
. AddTcpServer ( tcpAddress )
. AddWebSocketServer ( httpAddress ) ;
await host . Open ( ) ;ConnctionBuilder<T>使用してクライアント接続接続を作成する var tcpAddress = "tcp://myhost/myservice" ;
var client = await new ConnectionBuilder < IService > ( )
. WithTcpTransport ( tcpAddress )
. CreateConnection ( ) ; var msg = await client . Echo ( "Hellow World!" ) ; public interface ICallback
{
Task OnCallback ( string serverMessage ) ;
}OperationContext.Current.GetCallback<T>使用して型指定されたコールバック チャネルを選択します。 public Service : IService
{
public Task < string > Join ( string name )
{
CallBackAfter ( TimeSpan . FromSeconds ( 3 ) ) ;
return Task . FromResult ( "You joined" ) ;
}
async void CallBackAfter ( TimeSpan delay )
{
var client = OperationContext . Current . GetCallback < ICallback > ( ) ;
await Task . Delay ( ( int ) delay . TotalMilliseconds ) ;
await client . OnCallBack ( "This is a server callback" ) ;
}
}WithCallback<T>を呼び出す var host = new ServiceHostBuilder < Service > ( InstanceMode . Single )
. WithCallback < ICallback > ( )
. AddTcpServer ( address )
. CreateHost ( ) ;
await host . Open ( ) ; public class Callback : ICallback
{
public void OnServerUpdates ( string msg )
{
Console . WriteLine ( $ "Received callback msg: { msg } " ) ;
}
}DuplexConnectionBuilder使用して二重クライアントを作成します。これはジェネリック クラスであることに注意してください。最初のジェネリック引数はサービス コントラクトであり、もう 1 つはコントラクト インターフェイスではなくコールバック実装であるため、ビルダーはコールバック要求が発生したときにインスタンス化する型を認識します。受け取った。 var address = "tcp://myhost/myservice" ;
var client = await new DuplexConnectionBuilder < IService , Callback > ( InstanceMode . Single )
. WithTcpTransport ( address )
. CreateConnection ( ) ;
await client . Join ( "My Name" ) ; Xeeny はサービス インスタンスを作成するための 3 つのモードを定義します
ServiceHost を作成するときに、 InstanceMode列挙型を使用してサービス インスタンス モードを定義します。
var host = new ServiceHostBuilder < Service > ( InstanceMode . PerCall )
.. .
. CreateHost ( ) ;
await host . Open ( ) ;二重接続を作成するときは、コールバック タイプと InstanceMode をDuplexConnectionBuilderに渡します。 InstanceMode ServiceHost を作成するときにサービスに対して行うのと同じように動作します。
ServiceHostBuilderコンストラクターには、サービス タイプのインスタンスを取得する 1 つのオーバーロードがあります。これにより、インスタンスを作成してビルダーに渡すことができます。結果は、渡したオブジェクトを使用してInstanceMode.Singleになります。ServiceHostBuilderと同様に、 DuplextConnectionBuilderコールバック タイプのインスタンスを受け取り、シングルトンを自分で作成できるようにします。PerCallおよびPerConnectionのインスタンスはフレームワークによって作成されますが、構築後、メソッドを実行する前に、イベントServiceHost<TService>.ServiceInstanceCreatedイベントおよびDuplextConnectionBuilder<TContract, TCallback>.CallbackInstanceCreatedをリッスンすることで初期化できます。 host . ServiceInstanceCreated += service =>
{
service . MyProperty = "Something" ;
}
.. .
var builder = new DuplexConnectionBuilder < IService , Callback > ( InstanceMode . PerConnection )
. WithTcpTransport ( tcpAddress ) ;
builder . CallbackInstanceCreated += callback =>
{
callback .. .
}
var client = builder . CreateConnection ( ) ; Operation属性を使用して属性を付ける必要があります。 public interface IService
{
[ Operation ( IsOneWay = true ) ]
void FireAndForget ( string message ) ;
}1 つのインターフェイス (または親インターフェイスの同様のメソッド シグネチャ) でメソッドをオーバーロードする場合は、 Nameプロパティを設定してOperation属性を使用してそれらを区別する必要があります。これは、サービス コントラクトとコールバック コントラクトの両方に適用されます。
public interface IOtherService
{
[ Operation ( Name = "AnotherEcho" ) ]
Task < string > Echo ( string message ) ;
}
public interface IService : IOhterService
{
Task < string > Echo ( string message ) ;
}
class Service : IService , IOtherService
{
public Task < string > Echo ( string message )
{
return Task . FromResult ( $ "Echo: { message } " ) ;
}
Task < string > IOtherService . Echo ( string message )
{
return Task . FromResult ( $ "This is the other Echo: { message } " ) ;
}
} 基礎となる接続にアクセスして、ステータスの監視、イベントの監視、手動での管理 (閉じるまたは開く) を行うことができます。接続は、次の機能を提供するIConnectionインターフェイスを通じて公開されます。
State : 接続状態: Connecting 、 Connected 、 Closing 、 ClosedStateChanged : 接続状態が変化するたびに発生するイベントConnect() : リモートアドレスに接続しますClose() : 接続を閉じますSessionEnded : 接続が閉じられるときに発生するイベント ( State Closingに変更される)Dispose() : 接続を破棄しますConnectionId : GUID は各接続を識別します (現時点ではサーバーとクライアントの ID は一致しません)ConnectionName : デバッグとログ分析を容易にするためのわかりやすい接続名OperationContext.Current.GetConnection()使用して接続を取得します。OperationContext.Current.GetConnection()呼び出すことによって接続を取得しますが、通常はOperationContext.Current.GetCallback<TCallback>を呼び出すことによって接続を取得します。返されるインスタンスは、実行時に発行され、コールバック コントラクト (汎用パラメーターTCallbackで定義) を実装するインスタンスです。この自動生成型はIConnectionも実装しているため、コールバック チャネルの接続関数にアクセスしたいときは、それをIConnectionにキャストするだけです。 public class ChatService : IChatService
{
ConcurrentDictionary < string , ICallback > _clients = new ConcurrentDictionary < string , ICallback > ( ) ;
ICallback GetCaller ( ) => OperationContext . Current . GetCallback < ICallback > ( ) ;
public void Join ( string id )
{
var caller = GetCaller ( ) ;
_clients . AddOrUpdate ( id , caller , ( k , v ) => caller ) ;
( ( IConnection ) caller ) . SessionEnded += s =>
{
_clients . TryRemove ( id , out ICallback _ ) ;
} ;
}
}クライアントは、実行時に発行され、サービス コントラクト インターフェイスを実装する自動生成型のインスタンスです。コントラクトとともに、発行された型はIConnectionを実装します。これは、任意のクライアント (二重かどうかに関係なく) をIConnectionにキャストできることを意味します。
var client = await new ConnectionBuilder < IService > ( )
. WithTcpTransport ( address )
. CreateConnection ( ) ;
var connection = ( IConnection ) client ;
connection . StateChanged += c => Console . WriteLine ( c . State ) ;
connection . Close ( )CreateConnectionメソッドは、デフォルトでtrueであるブール型のオプションのパラメーターを 1 つ受け取ります。このフラグは、生成された接続がサーバーに接続するかどうかを示します。デフォルトでは、 CreateConnectionが呼び出されるたびに、生成された接続が自動的に接続されます。接続を作成し、後で接続したい場合があります。そのためには、 CreateConnectionメソッドにfalse渡し、必要なときに接続を手動で開きます。 var client = await new ConnectionBuilder < IService > ( )
. WithTcpTransport ( address )
. CreateConnection ( false ) ;
var connection = ( IConnection ) client ;
.. .
await connection . Connect ( ) ;すべてのビルダーは、サーバーまたはトランスポートを追加するときに接続オプションを公開します。オプションは次のとおりです。
Timeout : 接続タイムアウトを設定します (デフォルトは 30 秒)ReceiveTiemout : アイドル状態のリモート タイムアウトです (サーバーのデフォルト: 10 分、クライアントのデフォルト: 無限)KeepAliveInterval : キープアライブ ping 間隔 (デフォルトは 30 秒)KeepAliveRetries : 接続がオフであると判断するまでの再試行回数 (デフォルトは 10 回の再試行)SendBufferSize : 送信バッファ サイズ (デフォルトは 4096 バイト = 4 KB )ReceiveBufferSize : 受信バッファサイズ (デフォルトは 4096 バイト = 4 KB )MaxMessageSize : メッセージの最大サイズ (デフォルトは 1000000 バイト = 1 MB )ConnectionNameFormatter : ConnectionName設定またはフォーマットするためのデリゲート (デフォルトは null )。 (ロギングを参照)SecuritySettings : SSL 設定 (デフォルトは null ) (「セキュリティ」を参照)AddXXXSServer を呼び出すと、サーバー上で次のオプション構成アクションが取得されます。
var host = new ServiceHostBuilder < ChatService > ( InstanceMode . Single )
. WithCallback < ICallback > ( )
. AddTcpServer ( address , options =>
{
options . Timeout = TimeSpan . FromSeconds ( 10 ) ;
} )
. WithConsoleLogger ( )
. CreateHost ( ) ;
await host . Open ( ) ;クライアント側では、WithXXXTransport を呼び出すときに取得します。
var client = await new DuplexConnectionBuilder < IChatService , MyCallback > ( new MyCallback ( ) )
. WithTcpTransport ( address , options =>
{
options . KeepAliveInterval = TimeSpan . FromSeconds ( 5 ) ;
} )
. WithConsoleLogger ( )
. CreateConnection ( ) ;Timeoutを設定し、その時間内にリクエストが完了しない場合、接続は閉じられるため、新しいクライアントを作成する必要があります。 Timeoutがサーバー側で設定されている場合、コールバック タイムアウトが定義され、その時間内にコールバックが完了しない場合、接続は閉じられます。コールバックは一方向の操作であり、すべての一方向の操作は相手側がメッセージを受信したとき、リモート メソッドが実行される前に完了することに注意してください。
ReceiveTimeoutは「アイドル リモート タイムアウト」です。これをサーバーに設定すると、サーバーが非アクティブなクライアント (その間リクエストや KeepAlive メッセージを送信していないクライアント) を閉じるためのタイムアウトが定義されます。
クライアントのReceiveTimeoutはデフォルトでInfinityに設定されます。これを二重クライアントに設定すると、その時間内に来ないコールバックを無視するようにクライアントに指示することになります。これは奇妙なシナリオですが、そうすることを選択した場合は可能です。 。
ReceiveBufferSize受信バッファのサイズです。小さな値に設定しても大きなメッセージを受信する能力には影響しませんが、そのサイズが受信するメッセージと比較して著しく小さい場合は、より多くの IO 操作が導入されます。最初はデフォルト値のままにし、必要に応じて負荷テストと分析を行って、パフォーマンスが良く占有できるサイズを見つけることをお勧めします。
SendBufferSize送信バッファのサイズです。小さな値に設定しても、大きなメッセージの送信機能には影響しません。ただし、そのサイズが送信するメッセージと比較して大幅に小さい場合は、より多くの IO 操作が導入されます。最初はデフォルト値のままにし、必要に応じて負荷テストと分析を行って、パフォーマンスが良く、メモリ占有量が少ないサイズを見つけることをお勧めします。
これら 2 つのサイズが等しくない場合、UDP などの一部のトランスポートは適切に機能しないため、受信者のReceiveBufferSize送信者のSendBufferSizeと等しくなければなりません。今のところ、Xeeny はバッファ サイズをチェックしませんが、将来的には、接続処理中にこのチェックを含めるようにプロトコルを変更する予定です。
MaxMessageSize受信できる最大バイト数です。この値はバッファとは関係がないため、メモリやパフォーマンスには影響しません。この値は、クライアントを検証し、クライアントからの巨大なメッセージを防ぐために重要ですが、Xeeny はサイズ プレフィックス プロトコルを使用するため、メッセージが到着すると、 MaxMessageSizeよりもはるかに小さいサイズのReceiveBufferSizeのバッファーにバッファリングされます。サイズ ヘッダーが読み取られ、サイズがMaxMessageSizeより大きい場合、メッセージは拒否され、接続が閉じられます。
すべての種類のトランスポートにキープアライブ メカニズムが組み込まれているわけではないため、Xeeny は独自のキープアライブ メッセージを使用します。これらのメッセージはクライアントからサーバーへのみ 5 バイト フローします。 KeepAliveIntervalの間隔はデフォルトで 30 秒です。これをクライアントに設定すると、クライアントは、最後のKeepAliveInterval中に何も正常に送信できなかった場合に ping メッセージを送信します。
KeepAliveIntervalサーバーのReceiveTimeoutより小さく、サーバーのReceiveTimeoutの少なくとも 1/2 または 1/3 に設定する必要があります。これは、サーバーがReceiveTimeoutの間に何も受信しなかった場合、タイムアウトして接続が閉じられるためです。
KeepAliveRetriesは、失敗したキープアライブ メッセージの数です。このメッセージに到達すると、クライアントは接続が切断されていると判断して閉じます。
サーバー上でKeepAliveIntervalまたはKeepAliveRetriesを設定しても効果はありません。
Xeeny がメソッドのパラメータと戻り値の型をネットワーク上でマーシャリングできるようにするには、それらをシリアル化する必要があります。フレームワークではすでに 3 つのシリアライザーがサポートされています
MessagePackSerializer : MsgPack.Cli によって実装された MessagePack シリアル化です。シリアル化されたデータが小さく、指定されたライブラリでの .net の実装が高速であるため、これはデフォルトのシリアライザーです。JsonSerializer : Newtonsoft によって実装された Json シリアライザーProtobufSerializer : Protobuf-net によって実装された Google の ProtoBuffers シリアライザーWithXXXSerializer呼び出すことで、ビルダーを使用してシリアライザーを選択できます。選択したシリアライザーを使用して型がシリアル化可能であることを確認してください。
var host = new ServiceHostBuilder < ChatService > ( InstanceMode . Single )
. WithCallback < ICallback > ( )
. WithProtobufSerializer ( )
. CreateHost ( ) ;
await host . Open ( ) ;WithSerializer(ISerializer serializer)呼び出すことで、独自のシリアライザーを使用することもできます。 Xeeny は TLS 1.2 (現時点では TCP 経由のみ) を使用するため、 X509Certificateサーバーに追加する必要があります
var host = new ServiceHostBuilder < Service > ( .. . )
. AddTcpServer ( tcpAddress , options =>
{
options . SecuritySettings = SecuritySettings . CreateForServer ( x509Certificate2 ) ;
} )
.. .そして、クライアントではCertificate Nameを渡す必要があります。
await new ConnectionBuilder < IService > ( )
. WithTcpTransport ( tcpAddress , options =>
{
options . SecuritySettings = SecuritySettings . CreateForClient ( certificateName ) ;
} )
.. .リモート証明書を検証する場合は、 RemoteCertificateValidationCallbackのオプションのデリゲートをSecuritySettings.CreateForClientに渡すことができます。
Xeeny は Asp.Net Core と同じログ システムを使用します
ロガーを使用するには、ロガーの nuget パッケージを追加し、 LogLevel渡すことができるWithXXXLogger呼び出します。
デバッグやログの分析時に見つけやすいように接続に名前を付けたい場合があります。これを行うには、パラメータとしてIConnection.ConnectionIdが渡され、戻り値がIConnection.ConnectionNameに割り当てられるオプションでConnectionNameFormatter関数デリゲートを設定します。
var client1 = await new DuplexConnectionBuilder < IChatService , Callback > ( callback1 )
. WithTcpTransport ( address , options =>
{
options . ConnectionNameFormatter = id => $ "First-Connection ( { id } )" ;
} )
. WithConsoleLogger ( LogLevel . Trace )
. CreateConnection ( ) ; Xeeny は高性能かつ非同期になるように構築されており、非同期コントラクトによりフレームワークを完全に非同期にすることができます。常に、操作でvoidやT代わりにTaskまたはTask<T>返すようにしてください。これにより、操作が非同期でない場合に備えて、基になる非同期ソケットの完了を待機する余分なスレッドが 1 つ節約されます。
Xeeny のオーバーヘッドは、実行時に「新しい」型を発行する必要がある場合に発生します。これはServiceHost<TService>作成するとき ( ServiceHostBuilder<TService>.CreateHost()を呼び出す) に行われますが、これは型ごとに 1 回行われるため、xeeny が指定された型の最初のホストを発行した後、その型のホストをさらに作成してもパフォーマンスの問題は発生しません。とにかく、通常はこれがアプリケーションの起動です。
型の発行が発生するもう 1 つの場所は、特定のコントラクトまたはコールバック型の最初のクライアントを作成するとき ( CreateConnection呼び出すとき) です。そのプロキシの最初のタイプがエミッタになると、次のクライアントはオーバーヘッドなしで作成されます。 ( CreateConnectionにfalse渡さない限り、新しいソケットと新しい接続を作成していることに注意してください)。
OperationContext.Current.GetCallback<T>を呼び出すと、ランタイム型も出力されます。これは、出力された型を超える他のすべての出力がキャッシュされ、オーバーヘッドが最初の呼び出し時にのみ発生するのと同様です。このメソッドは好きなだけ呼び出すことができますが、戻り値をキャッシュすることをお勧めします。
上記のすべての Xeeny フレームワーク機能をカスタム トランスポートで動作させることができます (たとえば、デバイスの Blueetooth の背後で機能したいとします)。
XeenyListener抽象クラスの実装ServiceHostBuilder<T>.AddCustomServer()に渡します。 IXeenyTransportFactoryの実装ConnectionBuilder<T>.WithCustomTransport()に渡します。 独自のプロトコルを最初から作成したい場合は、独自の接続、メッセージ フレーミング、同時実行性、バッファリング、タイムアウト、キープアライブなどを実装する必要があります。
IListenerの実装ServiceHostBuilder<T>.AddCustomServer()に渡します。 ITransportFactory実装するConnectionBuilder<T>.WithCustomTransport()に渡します。