.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
속성을 사용하여 속성을 지정해야 합니다. 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
호출될 때마다 생성된 연결이 자동으로 연결됩니다. 때때로 연결을 생성하고 나중에 연결하려는 경우 CreateConnection
메서드에 false
전달한 다음 원할 때 수동으로 연결을 엽니다. var client = await new ConnectionBuilder < IService > ( )
. WithTcpTransport ( address )
. CreateConnection ( false ) ;
var connection = ( IConnection ) client ;
.. .
await connection . Connect ( ) ;
모든 빌더는 서버 또는 전송을 추가할 때 연결 옵션을 표시합니다. 옵션은 다음과 같습니다:
Timeout
: 연결 시간 제한을 설정합니다. ( 기본값은 30초 )ReceiveTiemout
: 유휴 원격 시간 초과입니다( 서버 기본값: 10분, 클라이언트 기본값: Infinity ).KeepAliveInterval
: 연결 유지 핑 간격( 기본값 30초 )KeepAliveRetries
: 연결이 꺼졌다고 결정하기 전의 재시도 횟수( 기본값 10회 재시도 )SendBufferSize
: 송신 버퍼 크기( 기본값 4096바이트 = 4KB )ReceiveBufferSize
: 수신 버퍼 크기( 기본값 4096바이트 = 4KB )MaxMessageSize
: 메시지의 최대 크기( 기본값 1000000바이트 = 1MB )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는 버퍼 크기를 확인하지 않지만 앞으로는 Connect 처리 중에 이 확인을 포함하도록 프로토콜을 수정하고 있습니다.
MaxMessageSize
수신이 허용되는 최대 바이트 수입니다. 이 값은 버퍼와 관련이 없으므로 메모리나 성능에 영향을 주지 않습니다. 이 값은 클라이언트를 검증하고 클라이언트로부터 큰 메시지를 방지하는 데 중요하지만 Xeeny는 크기 접두사 프로토콜을 사용하므로 메시지가 도착할 때 MaxMessageSize
보다 훨씬 작아야 하는 ReceiveBufferSize
크기의 버퍼에 버퍼링됩니다. 메시지가 도착한 후 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와 동일한 로깅 시스템을 사용합니다.
로거를 사용하려면 로거의 너겟 패키지를 추가한 다음 LogLevel
을 전달할 수 있는 WithXXXLogger
호출하세요.
로그를 디버깅하거나 분석할 때 쉽게 찾을 수 있도록 연결 이름을 지정하고 싶을 수 있습니다. IConnection.ConnectionId
매개 변수로 전달하는 옵션에서 ConnectionNameFormatter
함수 대리자를 설정하면 반환이 IConnection.ConnectionName
에 할당됩니다.
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>
반환하도록 작업을 수행해 보세요. 이렇게 하면 작업이 비동기가 아닌 경우 기본 비동기 소켓이 완료되기를 기다리는 하나의 추가 스레드가 절약됩니다.
Xeeny의 오버헤드는 런타임에 "새" 유형을 내보내야 하는 경우입니다. ServiceHost<TService>
( ServiceHostBuilder<TService>.CreateHost()
호출)를 생성할 때 이 작업이 수행되지만 이는 유형당 한 번 발생하므로 xeeny가 해당 유형의 첫 번째 호스트를 내보내면 해당 유형의 더 많은 호스트를 생성해도 성능 문제가 없습니다. 어쨌든 이것은 일반적으로 응용 프로그램 시작입니다.
유형 방출이 발생하는 또 다른 장소는 지정된 계약 또는 콜백 유형의 첫 번째 클라이언트를 생성할 때입니다( 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()
에 전달합니다.