在 .Net 標準中建構和使用跨平台服務的框架。
跨平台、雙工、可擴展、可配置和可擴展
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
建立雙工客戶端,注意它是泛型類,第一個泛型參數是服務契約,而另一個是回調實作而不是契約接口,因此建構器知道當回呼請求時要實例化什麼型別已收到。 var address = "tcp://myhost/myservice" ;
var client = await new DuplexConnectionBuilder < IService , Callback > ( InstanceMode . Single )
. WithTcpTransport ( address )
. CreateConnection ( ) ;
await client . Join ( "My Name" ) ;
Xeeny定義了三種建立服務實例的模式
建立 ServiceHost 時使用InstanceMode
枚舉定義服務實例模式
var host = new ServiceHostBuilder < Service > ( InstanceMode . PerCall )
.. .
. CreateHost ( ) ;
await host . Open ( ) ;
建立雙工連線時,您將回呼類型和 InstanceMode 傳遞給DuplexConnectionBuilder
。 InstanceMode
行為方式與建立 ServiceHost 時服務的行為方式相同
ServiceHostBuilder
建構函式有一個重載,它採用服務類型的實例,這允許您建立實例並將其傳遞給建構器,結果是使用您傳遞的物件的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
屬性在契約中傳遞IsOneWay = true (介面)來將其屬性化 public interface IService
{
[ Operation ( IsOneWay = true ) ]
void FireAndForget ( string message ) ;
}
當一個介面(或父介面中類似的方法簽章)中存在方法重載時,您必須透過設定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
。此標誌指示產生的連線是否將連接到伺服器。預設情況下,每當呼叫CreateConnection
時,產生的連線都會自動連線。有時您想要建立連接並希望稍後連接它們,為此,您可以將false
傳遞給CreateConnection
方法,然後在需要時手動開啟連接 var client = await new ConnectionBuilder < IService > ( )
. WithTcpTransport ( address )
. CreateConnection ( false ) ;
var connection = ( IConnection ) client ;
.. .
await connection . Connect ( ) ;
當您新增伺服器或傳輸時,所有建置器都會公開連線選項。選項有:
Timeout
:設定連線逾時(預設30秒)ReceiveTiemout
:是空閒遠端逾時(伺服器預設:10分鐘,客戶端預設:Infinity )KeepAliveInterval
:保持活動 ping 間隔(預設 30 秒)KeepAliveRetries
:決定連線關閉之前的重試次數(預設重試 10 次)SendBufferSize
:傳送緩衝區大小(預設4096位元組= 4 KB )ReceiveBufferSize
:接收緩衝區大小(預設4096位元組= 4 KB )MaxMessageSize
:訊息的最大大小(預設 1000000 位元組 = 1 MB )ConnectionNameFormatter
:委託設定或格式化ConnectionName
(預設為 null )。 (請參閱日誌記錄)SecuritySettings
:SSL 設定(預設為 null )(請參閱安全性)當您呼叫 AddXXXServer 時,您將在伺服器上取得這些選項配置操作:
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 操作。您最好在開始時保留預設值,然後根據需要進行負載測試和分析,以找到性能良好且佔用記憶體較少的大小。
接收方的ReceiveBufferSize
應等於發送方的SendBufferSize
,因為如果這兩個大小不相等,則某些傳輸(如 UDP)將無法正常運作。目前 Xeeny 不檢查緩衝區大小,但將來我將修改協定以在連接處理期間包含此檢查。
MaxMessageSize
是允許接收的最大位元組數。該值與緩衝區無關,因此不會影響記憶體或效能。這個值對於驗證客戶端和防止來自客戶端的巨大訊息很重要,Xeeny 使用大小前綴協議,因此當訊息到達時,它將緩衝在大小為ReceiveBufferSize
的緩衝區中,該緩衝區必須小於MaxMessageSize
,訊息到達後讀取size 標頭,如果大小大於MaxMessageSize
則訊息將被拒絕並關閉連線。
Xeeny 使用它自己的保持活動訊息,因為並非所有類型的傳輸都具有內建的保持活動機制。這些訊息僅從客戶端到伺服器的 5 位元組流。 KeepAliveInterval
間隔預設為 30 秒,當您在用戶端上設定它時,如果用戶端在最後一個KeepAliveInterval
期間沒有成功發送任何內容,則會發送 ping 訊息。
您必須將KeepAliveInterval
設定為小於伺服器的ReceiveTimeout
,至少是伺服器的ReceiveTimeout
的 1/2 或 1/3,因為如果伺服器在ReceiveTimeout
期間沒有收到任何內容,它將逾時並關閉連接
KeepAliveRetries
是失敗的保持活動訊息的數量,一旦到達,客戶端就會決定連線中斷並關閉。
在伺服器上設定KeepAliveInterval
或KeepAliveRetries
無效。
為了使 Xeeny 能夠在線路上封送方法參數和返回類型,它需要將它們序列化。框架已經支援三個序列化器
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包,然後呼叫WithXXXLogger
您可以在其中傳遞LogLevel
您可能想要命名連接,以便在偵錯或分析日誌時輕鬆發現它們,您可以透過在選項中設定ConnectionNameFormatter
函數委託來實現此目的,該委託將作為參數傳遞給IConnection.ConnectionId
,並且返回值將指派給IConnection.ConnectionName
。
var client1 = await new DuplexConnectionBuilder < IChatService , Callback > ( callback1 )
. WithTcpTransport ( address , options =>
{
options . ConnectionNameFormatter = id => $ "First-Connection ( { id } )" ;
} )
. WithConsoleLogger ( LogLevel . Trace )
. CreateConnection ( ) ;
Xeeny 旨在實現高效能和非同步,擁有非同步合約允許框架完全非同步。嘗試始終讓您的操作返回Task
或Task<T>
而不是void
或T
。這將節省一個額外的線程,該線程將等待底層非同步套接字完成,以防您的操作不是非同步的。
Xeeny 的開銷是當它需要在運行時發出「新」類型時。當您建立ServiceHost<TService>
(呼叫ServiceHostBuilder<TService>.CreateHost()
)時,它會執行此操作,但每個類型都會發生一次,因此一旦xeeny 發出給定類型的第一個主機,建立更多該類型的主機就不會出現效能問題。無論如何,這通常是您的應用程式的開始。
發出類型的另一個地方是當您建立給定合約或回呼類型的第一個客戶端時(呼叫CreateConnection
)。一旦該代理的第一種類型是發射器,下一個客戶端將被建立而無需任何開銷。 (請注意,除非您將false
傳遞給CreateConnection
否則您仍在建立新套接字和新連線)。
呼叫OperationContext.Current.GetCallback<T>
也會發出運行時類型,就像發出類型之上的所有其他發出一樣,都會被緩存,並且開銷僅在第一次調用時發生。您可以根據需要多次呼叫此方法,但最好是快取返回結果。
您可以獲得上述所有 Xeeny 框架功能,以與您的自訂傳輸搭配使用(假設您希望它位於裝置藍牙後面)。
XeenyListener
抽象類ServiceHostBuilder<T>.AddCustomServer()
IXeenyTransportFactory
ConnectionBuilder<T>.WithCustomTransport()
如果您想從頭開始擁有自己的協議,則需要實現自己的連線、訊息幀、並發、緩衝、逾時、保持活動等。
IListener
ServiceHostBuilder<T>.AddCustomServer()
ITransportFactory
ConnectionBuilder<T>.WithCustomTransport()