Kerangka Kerja Untuk Membangun Dan Menggunakan Layanan Lintas Platform Dalam Standar .Net.
Lintas Platform, Dupleks, Dapat Diskalakan, Dapat Dikonfigurasi, dan Dapat Diperpanjang
Xeeny adalah kerangka kerja untuk membangun dan menggunakan layanan pada perangkat dan server yang mendukung standar .net.
Dengan Xeeny Anda dapat meng-host dan menggunakan layanan di mana saja standar .net dapat bekerja (misalnya Xamarin android, Windows Server, ...). Ini adalah Cross Platform, Duplex, Multiple Transports, Asynchronous, Typed Proxy, Dapat Dikonfigurasi, dan Dapat Diperpanjang
Install-Package Xeeny
For extensions:
Install-Package Xeeny.Http
Install-Package Xeeny.Extentions.Loggers
Install-Package Xeeny.Serialization.JsonSerializer
Install-Package Xeeny.Serialization.ProtobufSerializer
Fitur Saat Ini:
Datang:
public interface IService
{
Task < string > Echo ( string message ) ;
}
public class Service : IService
{
public Task < string > Echo ( string message )
{
return Task . FromResult ( message ) ;
}
}
ServiceHost
menggunakan ServiceHostBuilder<TService>
di mana implementasi layanannyaAddXXXServer
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>
pada pembuatnya 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
untuk membuat klien dupleks, perhatikan bahwa ini adalah kelas generik, argumen umum pertama adalah kontrak layanan, sedangkan argumen lainnya adalah implementasi panggilan balik, bukan antarmuka kontrak, sehingga pembuat mengetahui jenis apa yang akan dibuat ketika permintaan panggilan balik adalah diterima. var address = "tcp://myhost/myservice" ;
var client = await new DuplexConnectionBuilder < IService , Callback > ( InstanceMode . Single )
. WithTcpTransport ( address )
. CreateConnection ( ) ;
await client . Join ( "My Name" ) ;
Xeeny mendefinisikan tiga mode untuk membuat instance layanan
Anda menentukan mode instans layanan menggunakan enum InstanceMode
saat membuat ServiceHost
var host = new ServiceHostBuilder < Service > ( InstanceMode . PerCall )
.. .
. CreateHost ( ) ;
await host . Open ( ) ;
Saat Anda membuat koneksi dupleks, Anda meneruskan jenis panggilan balik dan InstanceMode ke DuplexConnectionBuilder
. InstanceMode
bertindak dengan cara yang sama untuk layanan saat membuat ServiceHost
ServiceHostBuilder
memiliki satu kelebihan yang mengambil instance dari jenis layanan, Ini memungkinkan Anda membuat instance dan meneruskannya ke pembuat, hasilnya adalah InstanceMode.Single
menggunakan objek yang Anda lewatiServiceHostBuilder
, DuplextConnectionBuilder
mengambil contoh tipe panggilan balik yang memungkinkan Anda membuat singleton sendiriPerCall
dan PerConnection
dibuat oleh kerangka kerja, Anda masih dapat menginisialisasinya setelah dibuat dan sebelum menjalankan metode apa pun dengan mendengarkan kejadian: ServiceHost<TService>.ServiceInstanceCreated
event dan 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
yang meneruskan IsOneWay = true dalam kontrak (Antarmuka) public interface IService
{
[ Operation ( IsOneWay = true ) ]
void FireAndForget ( string message ) ;
}
Ketika Anda memiliki metode yang kelebihan beban dalam satu antarmuka (atau tanda tangan metode serupa di antarmuka induk), Anda harus membedakannya menggunakan atribut Operation
dengan menyetel properti Name
. Hal ini berlaku untuk kontrak Layanan dan Panggilan Balik.
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 } " ) ;
}
}
Anda ingin mengakses koneksi yang mendasarinya untuk mengelolanya, seperti memantau statusnya, mendengarkan acara, atau mengelolanya secara manual (menutup atau membukanya). Koneksi diekspos melalui antarmuka IConnection
yang menyediakan fungsi berikut:
State
: Status koneksi: Connecting
, Connected
, Closing
, Closed
StateChanged
: Peristiwa dipicu setiap kali status koneksi berubahConnect()
: Menghubungkan ke alamat jarak jauhClose()
: Menutup koneksiSessionEnded
: Acara dipicu ketika koneksi ditutup ( State
diubah menjadi Closing
)Dispose()
: Membuang koneksiConnectionId
: Panduan mengidentifikasi setiap koneksi (untuk saat ini Id di server dan klien tidak cocok)ConnectionName
: Nama koneksi yang ramah untuk memudahkan proses debug dan analisis logOperationContext.Current.GetConnection()
di awal metode Anda dan sebelum metode layanan memunculkan thread baru.OperationContext.Current.GetConnection()
, tetapi kemungkinan besar dengan menelepon OperationContext.Current.GetCallback<TCallback>
. Instans yang dikembalikan adalah instans yang dikeluarkan saat runtime dan mengimplementasikan kontrak panggilan balik Anda (didefinisikan dalam parameter generik TCallback
). Tipe yang dibuat secara otomatis ini mengimplementasikan IConnection
juga, jadi kapan pun Anda ingin mengakses fungsi koneksi saluran challback, transmisikan saja ke 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 _ ) ;
} ;
}
}
Klien adalah contoh tipe yang dihasilkan secara otomatis yang dikeluarkan saat runtime dan mengimplementasikan antarmuka kontrak layanan Anda. Bersama dengan kontrak, tipe yang dipancarkan mengimplementasikan IConnection
yang berarti Anda dapat mentransmisikan klien mana pun (Dupleks atau tidak) ke IConnection
var client = await new ConnectionBuilder < IService > ( )
. WithTcpTransport ( address )
. CreateConnection ( ) ;
var connection = ( IConnection ) client ;
connection . StateChanged += c => Console . WriteLine ( c . State ) ;
connection . Close ( )
CreateConnection
mengambil satu parameter opsional bertipe boolean yang bernilai true
secara default. Bendera ini menunjukkan apakah koneksi yang dihasilkan akan terhubung ke server atau tidak. secara default kapan saja CreateConnection
dipanggil, koneksi yang dihasilkan akan terhubung secara otomatis. Terkadang Anda ingin membuat koneksi dan ingin menghubungkannya nanti, untuk melakukannya Anda meneruskan false
ke metode CreateConnection
lalu membuka koneksi Anda secara manual kapan pun Anda mau var client = await new ConnectionBuilder < IService > ( )
. WithTcpTransport ( address )
. CreateConnection ( false ) ;
var connection = ( IConnection ) client ;
.. .
await connection . Connect ( ) ;
Semua pembangun menampilkan opsi koneksi saat Anda menambahkan Server atau Transport. pilihannya adalah:
Timeout
: Mengatur batas waktu koneksi ( default 30 detik )ReceiveTiemout
: Apakah batas waktu jarak jauh Idle ( default server: 10 menit, default klien: Infinity )KeepAliveInterval
: Interval ping tetap hidup ( default 30 detik )KeepAliveRetries
: Jumlah percobaan ulang sebelum memutuskan koneksi dimatikan ( default 10 percobaan ulang )SendBufferSize
: Mengirim ukuran buffer ( default 4096 byte = 4 KB )ReceiveBufferSize
: Menerima ukuran buffer ( default 4096 byte = 4 KB )MaxMessageSize
: Ukuran maksimum pesan ( default 1000000 byte = 1 MB )ConnectionNameFormatter
: Delegasikan untuk mengatur atau memformat ConnectionName
( defaultnya adalah null ). (lihat Pencatatan)SecuritySettings
: Pengaturan SSL ( defaultnya adalah null ) (lihat Keamanan)Anda mendapatkan tindakan konfigurasi opsi ini di server saat Anda memanggil AddXXXServer:
var host = new ServiceHostBuilder < ChatService > ( InstanceMode . Single )
. WithCallback < ICallback > ( )
. AddTcpServer ( address , options =>
{
options . Timeout = TimeSpan . FromSeconds ( 10 ) ;
} )
. WithConsoleLogger ( )
. CreateHost ( ) ;
await host . Open ( ) ;
Di sisi klien Anda mendapatkannya saat memanggil WithXXXTransport
var client = await new DuplexConnectionBuilder < IChatService , MyCallback > ( new MyCallback ( ) )
. WithTcpTransport ( address , options =>
{
options . KeepAliveInterval = TimeSpan . FromSeconds ( 5 ) ;
} )
. WithConsoleLogger ( )
. CreateConnection ( ) ;
Ketika Anda mengatur Timeout
dan permintaan tidak selesai selama waktu tersebut, koneksi akan ditutup dan Anda harus membuat klien baru. Jika Timeout
diatur di sisi server yang akan menentukan batas waktu panggilan balik dan koneksi akan ditutup ketika panggilan balik tidak selesai selama waktu tersebut. Ingatlah bahwa callaback adalah operasi satu arah dan semua operasi satu arah selesai ketika pihak lain menerima pesan dan sebelum metode jarak jauh dijalankan.
ReceiveTimeout
adalah " Idle Remote Timeout " Jika Anda menyetelnya di server, ini akan menentukan batas waktu bagi server untuk menutup klien yang tidak aktif yang merupakan klien yang tidak mengirimkan permintaan atau pesan KeepAlive apa pun selama waktu tersebut.
ReceiveTimeout
pada klien diatur ke Infinity secara default, jika Anda mengaturnya pada klien dupleks, Anda menginstruksikan klien untuk mengabaikan panggilan balik yang tidak datang selama waktu tersebut yang merupakan skenario aneh tetapi masih mungkin jika Anda memilih untuk melakukannya .
ReceiveBufferSize
adalah ukuran buffer penerima. Menyetelnya ke nilai kecil tidak akan mempengaruhi kemampuan menerima pesan besar, namun jika ukurannya sangat kecil dibandingkan dengan pesan yang diterima, maka perkenalkan lebih banyak operasi IO. Anda sebaiknya membiarkan nilai default di awal kemudian jika perlu lakukan pengujian dan analisis beban untuk menemukan ukuran yang berkinerja baik dan menempati
SendBufferSize
adalah ukuran buffer pengiriman. Menyetelnya ke nilai kecil tidak akan memengaruhi kemampuan pengiriman pesan besar, namun jika ukurannya sangat kecil dibandingkan dengan pesan yang akan dikirim, maka perkenalkan lebih banyak operasi IO. Anda sebaiknya membiarkan nilai default di awal kemudian jika perlu lakukan pengujian dan analisis beban untuk menemukan ukuran yang berkinerja baik dan menggunakan lebih sedikit memori.
ReceiveBufferSize
penerima harus sama dengan SendBufferSize
pengirim karena beberapa transport seperti UDP tidak akan berfungsi dengan baik jika kedua ukuran ini tidak sama. Untuk saat ini Xeeny tidak memeriksa ukuran buffer tetapi di masa depan saya memodifikasi protokol untuk menyertakan pemeriksaan ini selama pemrosesan Connect.
MaxMessageSize
adalah jumlah byte maksimum yang diperbolehkan untuk diterima. Nilai ini tidak ada hubungannya dengan buffer sehingga tidak mempengaruhi memori atau kinerja. Nilai ini penting untuk memvalidasi klien Anda dan mencegah pesan besar dari klien, Xeeny menggunakan protokol awalan ukuran sehingga ketika sebuah pesan tiba, pesan itu akan di-buffer pada buffer dengan ukuran ReceiveBufferSize
yang harus jauh lebih kecil dari MaxMessageSize
. Setelah pesan tiba, ukuran header dibaca, jika ukurannya lebih besar dari MaxMessageSize
pesan ditolak dan koneksi ditutup.
Xeeny menggunakan pesan tetap hidup sendiri karena tidak semua jenis transportasi memiliki mekanisme tetap hidup. Pesan-pesan ini berukuran 5 byte yang mengalir dari klien ke server saja. Interval KeepAliveInterval
adalah 30 detik secara default, ketika Anda mengaturnya di klien, klien akan mengirim pesan ping jika tidak berhasil mengirim apa pun selama KeepAliveInterval
terakhir.
Anda harus menyetel KeepAliveInterval
menjadi lebih kecil dari ReceiveTimeout
server, setidaknya 1/2 atau 1/3 dari ReceiveTimeout
server karena server akan kehabisan waktu dan menutup koneksi jika tidak menerima apa pun selama ReceiveTimeout
KeepAliveRetries
adalah jumlah pesan tetap hidup yang gagal, setelah mencapai klien memutuskan bahwa koneksi terputus dan ditutup.
Mengatur KeepAliveInterval
atau KeepAliveRetries
di server tidak berpengaruh.
Agar Xeeny dapat menyusun parameter metode dan mengembalikan tipe pada kabel, ia perlu membuat serialisasinya. Ada tiga serializer yang sudah didukung dalam framework ini
MessagePackSerializer
: Apakah serialisasi MessagePack diimplementasikan oleh MsgPack.Cli, Ini adalah serializer Default karena data serialnya kecil dan implementasi untuk .net di perpustakaan yang diberikan cepat.JsonSerializer
: Serializer Json diimplementasikan oleh NewtonsoftProtobufSerializer
: Serializer ProtoBuffers Google yang diimplementasikan oleh Protobuf-net Anda dapat memilih serializer menggunakan pembuatnya dengan memanggil WithXXXSerializer
, pastikan saja tipe Anda dapat diserialkan menggunakan serializer yang dipilih.
var host = new ServiceHostBuilder < ChatService > ( InstanceMode . Single )
. WithCallback < ICallback > ( )
. WithProtobufSerializer ( )
. CreateHost ( ) ;
await host . Open ( ) ;
WithSerializer(ISerializer serializer)
Xeeny menggunakan TLS 1.2 (hanya melalui TCP untuk saat ini), Anda perlu menambahkan X509Certificate
ke server
var host = new ServiceHostBuilder < Service > ( .. . )
. AddTcpServer ( tcpAddress , options =>
{
options . SecuritySettings = SecuritySettings . CreateForServer ( x509Certificate2 ) ;
} )
.. .
Dan pada klien Anda harus memberikan Certificate Name
:
await new ConnectionBuilder < IService > ( )
. WithTcpTransport ( tcpAddress , options =>
{
options . SecuritySettings = SecuritySettings . CreateForClient ( certificateName ) ;
} )
.. .
Jika Anda ingin memvalidasi sertifikat jarak jauh, Anda dapat meneruskan delegasi opsional RemoteCertificateValidationCallback
ke SecuritySettings.CreateForClient
Xeeny menggunakan sistem logging yang sama dengan yang ditemukan di Asp.Net Core
Untuk menggunakan logger, tambahkan paket nuget logger, lalu panggil WithXXXLogger
di mana Anda dapat meneruskan LogLevel
Anda mungkin ingin memberi nama koneksi agar mudah dikenali saat melakukan debug atau menganalisis log, Anda dapat melakukannya dengan mengatur delegasi fungsi ConnectionNameFormatter
dalam opsi yang diteruskan IConnection.ConnectionId
sebagai parameter dan pengembalian akan ditetapkan ke IConnection.ConnectionName
.
var client1 = await new DuplexConnectionBuilder < IChatService , Callback > ( callback1 )
. WithTcpTransport ( address , options =>
{
options . ConnectionNameFormatter = id => $ "First-Connection ( { id } )" ;
} )
. WithConsoleLogger ( LogLevel . Trace )
. CreateConnection ( ) ;
Xeeny dibuat dengan performa tinggi dan asinkron, memiliki kontrak asinkron memungkinkan kerangka kerja menjadi asinkron sepenuhnya. Usahakan selalu agar operasi Anda mengembalikan Task
atau Task<T>
alih-alih void
atau T
. Ini akan menyimpan satu thread tambahan yang akan menunggu soket async yang mendasarinya selesai jika operasi Anda tidak async.
Overhead di Xeeny adalah saat ia perlu mengeluarkan tipe "Baru" saat runtime. Itu dilakukan saat Anda membuat ServiceHost<TService>
(memanggil ServiceHostBuilder<TService>.CreateHost()
) tetapi itu terjadi sekali per jenis, jadi setelah xeeny memancarkan host pertama dari jenis tertentu, membuat lebih banyak host dari jenis tersebut tidak memiliki masalah kinerja. lagi pula ini biasanya aplikasi Anda dimulai.
Tempat lain di mana tipe emisi terjadi adalah ketika Anda membuat klien pertama dari kontrak atau tipe panggilan balik tertentu (memanggil CreateConnection
). setelah jenis pertama dari proxy itu adalah emitor, klien berikutnya akan dibuat tanpa overhead. (perhatikan bahwa Anda masih membuat soket baru dan koneksi baru kecuali Anda meneruskan false
ke CreateConnection
).
Memanggil OperationContext.Current.GetCallback<T>
juga memancarkan tipe runtime, seperti semua emisi lain di atas tipe yang dipancarkan di-cache dan overhead hanya terjadi pada panggilan pertama. Anda dapat memanggil metode ini sebanyak yang Anda suka, tetapi sebaiknya Anda menyimpannya dalam cache.
Anda bisa mendapatkan semua fitur kerangka Xeeny di atas untuk bekerja dengan transportasi khusus Anda (Misalnya Anda menginginkannya di belakang perangkat Bluetooth).
XeenyListener
ServiceHostBuilder<T>.AddCustomServer()
IXeenyTransportFactory
ConnectionBuilder<T>.WithCustomTransport()
Jika Anda ingin memiliki protokol sendiri dari awal, Anda perlu mengimplementasikan konektivitas Anda sendiri, pembingkaian pesan, konkurensi, buffering, batas waktu, tetap hidup, ...dll.
IListener
ServiceHostBuilder<T>.AddCustomServer()
ITransportFactory
ConnectionBuilder<T>.WithCustomTransport()