.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
、 Closed
StateChanged
: 接続状態が変化するたびに発生するイベント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()
に渡します。