-
Notifications
You must be signed in to change notification settings - Fork 38
What's new in Zyan
This version adds the secure SRP-6a authentication protocol support.
In this release we are proud to introduce Android platform support (using Xamarin Mono for Android version 4.6 or greater). Android version of the library is still in its beta stage, so we welcome beta-testers. Mobile version supports most features of the full desktop version, except of the following:
- Only duplex TCP channel is supported.
- MEF integration is not available.
Android version requires at least Indie edition of Mono for Android (please notice that it's not free, although it's the cheapest one). We added a very simple example application to help you getting started with Zyan on Android.
To help you get most of the modern multi-core architectures, Zyan enables writing multi-threaded server applications. When you spawn multiple threads to handle client requests, every thread still belongs to the session of the client that initiated the request. For example:
public void ServerMethod(string[]() args)
{
// print SessionID for debugging
Console.WriteLine("Current session: {0}", ServerSession.CurrentSession.SessionID);
// spawn multiple threads to handle arguments
Parallel.ForEach(args, arg =>
{
// this code may be executed on its own thread
ProcessArgument(arg);
// SessionID is the same as above
Console.WriteLine("Current session: {0}",
ServerSession.CurrentSession.SessionID);
});
}
ServerSession.CurrentSession
property retains its value when the code spawns a new thread using Thread.Start(), ThreadPool.QueueUserWorkItem(), Task.Factory.StartNew(), Parallel.For() and Parallel.ForEach().
Built-in ThreadPool is used by framework classes for all asynchronous operations (invoking delegates asynchronously, handling incoming connections, file I/O, etc). In an extreme high load scenarios ThreadPool may run out of available threads to handle new requests. This situation is commonly known as a thread pool starvation.
To prevent thread pool starvation, in the new version of Zyan we added a new custom adjustable thread pool to raise server-side events. Thanks Joe Duffy for his excellent series of articles on building custom thread pool!
- Reconnection and re-subscription of remote events now works much more reliable.
- All client protocol classes now include FormatUrl method to help you to create the proper URLs.
- ZyanComponentHost now has new events for debugging and monitoring scenarios:
- SubscriptionAdded — when a client subscribes to an event (+=)
- SubscriptionRemoved — when a client unsubscribes from an event (-=)
- SubscriptionCanceled — when a server cancels the subscription due to an exception.
This version introduces lots of improvements transparent to Zyan user. You don't have to make any changes to your code — just update the Zyan library and recompile your application to make use of the built-in traffic compression, non-blocking server-side events, lazy-initialized duplex tcp connections, and more.
All Zyan protocols now support transparent traffic compression: serialized messages matching the specified criteria are compressed to save the network bandwidth. Zyan supports two compression algorithms: DeflateStream (average speed, better compression) and LZF (very fast, but poor compression). By default, all messages greater than 64 kilobytes in size are compressed using the DeflateStream algorithm.
Client-side and server-side protocols don't need to have the same compression settings. For example, a server can return messages compressed with LZF (to save CPU resources) while clients communicate using DeflateStream traffic compression. Compression settings only affect outgoing traffic, so even if a party disables compression completely, it still can receive compressed messages.
Zyan 2.4 includes several important improvements to distributed event system.
In previous versions of Zyan, events in the server components were always raised in the same thread. It could block server threads for significant amounts of time (if server component had lots of remote subscribers). The new event-raising code uses ThreadPool so it doesn't block the server thread.
Legacy event raising mode is still available. It can be turned on for the whole server application using static boolean property ZyanComponentHost.LegacyBlockingEvents:
// enable blocking server-side events mode
ZyanComponentHost.LegacyBlockingEvents = true;
In Zyan 2.3 and earlier, events of the single-call components was only delivered to the same client which was making the remote call, while events of the singleton components behaved just like normal events broadcast to all subscribers. As a result, the developer had to use either singleton mode for the server component or to use single-call component with static events. Starting from Zyan 2.4, all events are broadcast in the same manner for both singleton and single-call activation modes.
To emulate pre-2.4 behavior (events only delivered to the same client), you can use session-bound events. Session-bound events work regardless of the component activation mode, so you can easily mix normal and session-bound events within the same server component, whether it single-call or singleton.
You can now intercept subscriptions to your server-side events (for example, to be able to raise them locally):
// create call interceptor for event subscription
var interceptor = new CallInterceptor(typeof(IMyService),
MemberTypes.Method, "add_MyEvent", // «add_» + event name
new[]() { typeof(EventHandler) }, data =>
{
// suppress remote call (ignore subscription)
data.Intercepted = true;
});
This version includes a new remoting channel explicitly tuned for the local (inside the same AppDomain) communication. NullChannel is very fast because it doesn't serialize messages and pass them as is between the client and the server. Primary uses for NullChannel are:
- Unit testing and integration testing
- Profiling Zyan applications (and Zyan library itself)
- Enabling policy injection features of Zyan for the local callers.
- Education: NullChannel is very easy so it can be used as a base for the new channel.
Here is a sample of the NullChannel usage:
using System;
using Zyan.Communication;
using Zyan.Communication.Protocols.Null;
struct Program
{
static void Main()
{
// sample server
var serverSetup = new NullServerProtocolSetup(123);
using (var zyanHost = new ZyanComponentHost("Sample", serverSetup))
{
zyanHost.RegisterComponent<ISample, Sample>();
// track method calls
zyanHost.BeforeInvoke += (s, e) =>
Console.WriteLine("Method called: {0}", e.MethodName);
zyanHost.AfterInvoke += (s, e) =>
Console.WriteLine("Return value: {0}", e.ReturnValue);
// sample client
var url = "null://NullChannel:123/Sample";
using (var conn = new ZyanConnection(url))
{
var proxy = conn.CreateProxy<ISample>();
Console.WriteLine("Sample.Version = {0}", proxy.Version);
}
}
}
}
public interface ISample
{
string Version { get; }
}
internal class Sample
{
public string Version { get { return "1.0"; } }
}
ZyanConnection can now automatically select the proper client connection protocol based on the connection URL. It's not a brand new feature, but it will simply save the developer from typing long protocol setup class names. Here is a list of the default mappings (of course, they can be changed using the ClientProtocolSetup.RegisterClientProtocol static method):
- tcp:// — TcpBinaryClientProtocolSetup
- tcpex:// — TcpDuplexClientProtocolSetup
- http:// — HttpCustomClientProtocolSetup
- ipc:// — IpcBinaryClientProtocolSetup
- null:// — NullClientProtocolSetup
// Zyan 2.3
using Zyan.Communication.Protocols.Ipc;
var protocol = new IpcBinaryClientProtocolSetup();
var connection = new ZyanConnection("ipc://portName/hostName", protocol);
// Zyan 2.4
var connection = new ZyanConnection("ipc://portName/hostName");
// overriding default protocol mapping for the tcp connections
ClientProtocolSetup.RegisterClientProtocol("tcp://", () => new TcpCustomProtocolSetup());
New version of the duplex tcp channel supports lazy-initialized connections (enabled by default) and the bintTo configuration parameter. In Zyan 2.0–2.3, duplex tcp channel always binds to all available IP addresses (IPAddress.Any). Now we can specify what address it should bind to:
var tcpDuplexProtocol = new TcpDuplexServerProtocolSetup
{
TcpPort = 8084,
AuthenticationProvider = new NullAuthenticationProvider(),
};
// add channel settings
tcpDuplexProtocol.AddChannelSetting("bindTo", "127.0.0.1");
Prior to Zyan 2.4, duplex channel established the connection inside its constructor. This was just fine for Zyan, but could cause problems when the channel was used in legacy remoting applications:
// standard TcpChannel: this code will work even if the server is not yet started
var srv1 = (IServer)Activator.GetObject(typeof(IServer), "tcp://srv:123/server.rem");
// duplex tcp channel: this code will break with Zyan 2.3
// if server is not started, but it will work well with Zyan 2.4
var srv2 = (IServer)Activator.GetObject(typeof(IServer), "tcpex://srv:123/server.rem");
This behavior can be changed using the "connectDuringCreation" channel setting:
// enable legacy connection mode
var clientProtocol = new TcpDuplexClientProtocolSetup();
tcpDuplexProtocol.AddChannelSetting("connectDuringCreation", "true");
This version improves compatibility with Mono Framework. We don't need a separate assembly for Mono anymore. Mono will run both .NET4 and .NET3.5 versions of Zyan.Communication assembly. Other notable improvements include better localization of diagnostic messages, more XML comments translated to English and lots of bugfixes.
Standard .NET event model is the simplest Observer pattern implementation. Every event is dispatched to all listeners who subscribed it, so the listener is responsible for filtering out events that should not be handled. That's fine for local events, but not as good for the client-server scenarios.
Consider a chat application, for example. Chat server raises message events and sends them to all chat clients. Clients need to filter out chat messages that were sent by themselves. Server generates some extra network traffic which is simply ignored.
The situation gets worse when the server delivers private messages (i.e., messages for the certain users or groups of users). Chat server broadcasts a message as usual, and clients are responsible for the message filtering. We can easily write our evil chat client to intercept private messages.
To summarize, standard event model has the following disadvantages in a client-server scenario: — potential security problems; — extra network traffic for the events that are not handled.
To address these issues, the new version of Zyan adds the ability to filter event handlers at the server-side based on event arguments (and, maybe, some server call context). Built-in flexible event filter can take any predicate expression and transfer it to the server, so the event will only be delivered to the client if the expression evaluates to true. Here is an example:
// establish connection, get the proxy and subscribe to the event
using (var conn = new ZyanConnection(serverUrl, protocolSetup))
{
var proxy = conn.CreateProxy<ISampleService>();
var eventHandler = new EventHandler<SampleEventArgs>((sender, args) =>
Console.WriteLine("Event: argument value = {0}", args.Value);
// simple event handler: no event filter attached,
// the event is always delivered to a client over a wire
proxy.SampleEvent += eventHandler;
// filtered event handler: the event is only delivered if argument equals 123
proxy.SampleEvent += eventHandler.AddFilter((f, args) => args.Value == 123);
...
}
A special case of filtered events is session-bound events: events that are only delivered to a certain client session. Session-bound events use special SessionEventArgs type with Sessions property (which holds a set of session IDs). When we need to declare a session-bound event, we derive our EventArgs type from SessionEventArgs (for example, DiagnosticsEventArgs with a string Message property). When the server fires an event, it checks for Sessions property, and if it's not empty, it only delivers an event to the session listed in Sessions set. Otherwise, an event is broadcast as usual.
Call interceptors now can be enabled or disabled after creation. Interception pause mode is added to CallInterceptor class, allowing interceptor code to call any remote method without being intercepted. This mode is important when you can't simply use data.MakeRemoteCall(): suppose you need to call the method with other arguments (or you're going to call another remote method). Here is an example:
// create call interceptor
var interceptor = CallInterceptor
.For<IOracleHelperService>()
.Func<int, string>(
// method to intercept
(service, errorCode) => service.GetOracleErrorMessage(errorCode),
// interceptor code
(data, errorCode) =>
{
// do not ask for negative error codes
if (errorCode < 0)
{
errorCode = -errorCode;
}
// get value from cache or from server
string errorMessage;
if (!cache.TryGetValue(errorCode, out errorMessage))
{
// disable interception and make remote call
using (CallInterceptor.PauseInterception())
{
errorMessage = MyServiceProxy.GetOracleErrorMessage(errorCode);
cache[errorCode](errorCode) = errorMessage;
}
}
// return intercepted value
data.Intercepted = true;
return errorMessage;
});
// disable call interceptor
interceptor.Enabled = false;
HttpCustomClientProtocolSetup has now better support for restrictive network environments. New constructor overloads allow passing custome IWebProxy object to be used for all web requests within the same application domain.
Create ZyanCatalog class and attach it to ZyanConnection, the rest is done automatically. Any components published by remote ZyanComponentHost will be available as exports in ZyanCatalog. Use CompositionContainer to resolve dependencies, as usual:
// shared assembly
public interface IRemoteService
{
void ExecuteCallback(Action action);
}
// client assembly
[Import](Import)
IRemoteService MyRemoteService { get; set; }
using (var connection = new ZyanConnection(url))
{
// resolve imports
var catalog = new ZyanCatalog(connection);
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
// use remote service
MyRemoteService.ExecuteCallback(() =>
{
Console.WriteLine("Hello from the other side!");
});
}
Zyan is built on top of the existing .NET Remoting infrastructure which is available in .NET Framework and Mono since the early versions. To shield the user from the difficulties of configuring .NET Remoting channels and sinks, Zyan provides protocol setup classes (Since the first Zyan version). They´re easy to use. To specify settings, you just have to set the corresponding properties. For many scenarios the provided protocol setup classes are fine. But if you have some special environment, they can be too stiff. In Zyan versions before 2.2, you had to write your own protocol setup classes to gain full control of the Remoting channel and sinks to be used. This might be difficult if you don´t have deeper knowledge of Zyan and .NET Remoting.
Zyan 2.2 simplifies the customizing. You now are able to add custom Remoting sinks to existing protocol setup classes. Or you compose your own protocol setup with the new fluent interface without the need of writing custom protocol setup classes.
var clientSetup = new TcpDuplexClientProtocolSetup()
.AddClientSinkBeforeFormatter(new MyCustomLoggingClientSinkProvider())
.AddServerSinkAfterFormatter(new MyCustomLoggingServerSinkProvider());
var serverSetup = new TcpBinaryServerProtocolSetup(8080)
.AddServerSinkBeforeFormatter(new MyCustomLoggingClientSinkProvider())
.AddClientSinkBeforeFormatter(new MyCustomLoggingClientSinkProvider());
var clientSetup = ClientProtocolSetup.WithChannel((settings, clientSinks, serverSinks) => new TcpExChannel(settings, clientSinks, serverSinks))
.AddClientSink(new MyCustomLoggingClientSinkProvider())
.AddClientSink(new BinaryClientFormatterSinkProvider())
.AddServerSink(new BinaryServerFormatterSinkProvider() { TypeFilterLevel = TypeFilterLevel.Full })
.AddServerSink(new MyCustomLoggingServerSinkProvider());
var serverSetup = ServerProtocolSetup.WithChannel((settings, clientSinks, serverSinks) => new TcpExChannel(settings, clientSinks, serverSinks))
.AddServerSink(new BinaryServerFormatterSinkProvider() { TypeFilterLevel = TypeFilterLevel.Full })
.AddServerSink(new MyCustomLoggingServerSinkProvider())
.AddClientSink(new MyCustomLoggingClientSinkProvider())
.AddClientSink(new BinaryClientFormatterSinkProvider())
.AddChannelSetting("port", 8080);
As you can see, it´s very easy to customize protocol setups in Zyan 2.2.
Writing your own .NET Remoting sinks (like MyCustomLoggingSink in the examples above) isn´t difficult. See the following MSDN page for details: http://msdn.microsoft.com/en-us/library/tdzwhfy3%28VS.80%29.aspx
DynamicWireFactory is rewritten completely to make use of fast compiled Linq expressions. Event subscriptions and callback functions now work several times faster (depends on the platform). Here is an example session (running Mono 2.10.2 under OpenSuse 11.4):
yallie@OpenSuse:~/Dropbox/OldZyan> time mono IntegrationTest_DistributedEvents.exe
real 0m11.618s
user 0m8.067s
sys 0m0.288s
yallie@OpenSuse:~/Dropbox/NewZyan> time mono IntegrationTest_DistributedEvents.exe
real 0m6.235s
user 0m2.772s
sys 0m0.061s
New ZyanConnection.Disconnected event allows detecting unexpected disconnection. Here is a short sample code snippet, how to use the new feature:
var conn = new ZyanConnection(url);
conn.PollingEnabled = true;
conn.PollingInterval = TimeSpan.FromMinutes(1);
conn.Disconnected += (s, e) => Console.WriteLine("Connection is lost! Exception details: " + e.Exception.Message);
There is also a possibility to make reconnection attempts. Set the Retry property of the DisconnectedEventArgs to true, if you want Zyan to retry. Retries are done as long as your set the Retry property inside your event handler to true.
For logging and troubleshooting purposes you can enable Polling Event Tracing on your host, to get notified when clients poll (sending heartbeat signals):
host.PollingEventTracingEnabled = true;
host.ClientHeartbeatReceived += (s, e) =>
Console.WriteLine(string.Format("{0}: Heartbeat from client session '{1}' received.", e.HeartbeatReceiveTime, e.SessionID));
- SessionManagerBase class to simplify custom session manager implementation
- Zyan.Communication.dll has now a strong name (Important for CAS policies and strict versioning)
- Versioning of serialized types supports strict and lax mode
- Several bugfixes: #1107, #1173, #1227, #1248, #1252, #1254, #1255, #1256, #1259, #1263, #1308, #1333
Published components can now have generic methods, and Linq queries don't need any special handling anymore. RegisterQueryableComponent() and CreateQueryableProxy() methods are now obsolete. To use Linq queries, implement an interface with parameterless methods returning either IQueryable or IEnumerable as follows:
interface ISampleService
{
IEnumerable<T> GetList<T>() where T : class;
IQueryable<T> Query<T>() where T : class;
}
// client-side
using (var connection = new ZyanConnection(url))
{
var proxy = connection.CreateProxy<ISampleService>();
var query =
from c in proxy.Query<Customer>()
where c.BirthDate > DateTime.Today.AddYears(-18)
select c;
...
}
Explicit cleanup is useful in the following scenarios:
- Single-call component uses some limited resource and implements IDisposable — it should be cleaned up upon method invocation
- Singleton component does the same — it should be disposed of together with its owner, ZyanComponentHost
- Some component is registered with the use of factory and requires explicit deinitialization For more information, please refer to Deterministic resource cleanup wiki page.
Managed Extensibility Framework (MEF) is an advanced IoC container and add-in framework available in .NET 4.0. Server-side MEF integration allows to set up server application declaratively by decorating public services with special attributes. For more information, see the dedicated wiki page: Managed Extensibility Framework Integration.
Duck typing is a kind of dynamic typing where the objects are treated as having the same type when they provide identical methods and properties (see Duck Typing in Wikipedia). Zyan allows registering components by calling Register<I, T>(...) when type T doesn't implement interface I, but has all methods and properties defined in I. Zyan automatically does all the necessary registration-time and invocation-time checks (i. e., it throws registration exception when not all methods and properties of I are implemented by type T):
// This is an interface defined in our library
public interface IMyInterface
{
void DoStuff();
}
// This is third-party component which doesn't implement IMyInterface
public class ThirdPartyComponent
{
public void DoStuff(); // same signature as in IMyInterface.DoStuff()
}
// This component can not be cast to IMyInterface
public class SomeOtherComponent
{
public void DoStuff(int parameter); // method signature mismatch
}
// set up server and publish ThirdPartyComponent
using (var host = new ZyanComponentHost(...))
{
host.RegisterComponent<IMyInterface, ThirdPartyComponent>(); // works fine
host.RegisterComponent<IMyInterface, SomeOtherComponent>(); // throws an exception
}
// client-side code is as usual
using (var conn = new ZyanConnection(...))
{
var proxy = conn.CreateProxy<IMyInterface>();
proxy.DoStuff();
}
- NUnit test project (both MSTest and NUnit frameworks are now supported)
- Numerous Mono interoperability improvements in TcpExChannel and InterLinq
- MonoDevelop 2.x IDE support (separate solution and project files)
Zyan 2.0 introduces a new transport channel. It´s called TcpExChannel and implements a bi-directional protocol. This makes it possible for clients to receive events and callbacks from server even through a client side NAT firewall. Just use the TcpDuplexClientProtocolSetup and TcpDuplexServerProtocolSetup in your applications to take advantage of this new feature.
// Setting up duplex channel on server with port 8080, no authentication and encryption enabled
TcpDuplexServerProtocolSetup protocol = new TcpDuplexServerProtocolSetup(8080, new NullAuthenticationProvider(), true);
using (ZyanComponentHost host = new ZyanComponentHost("YourApp", protocol))
{
...
}
// Setting up duplex channel on client
TcpDuplexClientProtocolSetup protocol = new TcpDuplexClientProtocolSetup(true);
ZyanConnection connection = new ZyanConnection("tcpex://server:8080/YourApp",protocol); // Note the 'tcpex' prefix!
Zyan 2.0 provides you with a new call interception feature. This is very useful if specific calls should be canceled, monitored, or for caching scenarios. You can decide which calls to intercept.
// Sexual content filter for chat application implemented with Zyan call interception
connection.CallInterceptors.For<IMiniChat>().Add(
(IMiniChat chat, string nickname, string message) => chat.SendMessage(nickname, message),
(data, nickname, message) =>
{
if (message.Contains("fuck") || message.Contains("sex"))
{
System.Console.WriteLine("TEXT CONTAINS FORBIDDEN WORDS!");
data.Intercepted = true;
}
});
Zyan 2.0 comes with an easy way to integrate LINQ queries in your distributed applications. LINQ support is implemented in the Zyan.InterLinq assembly. Add a reference to that new assembly to your project. Publish your server side LINQ sources with a unique name:
// Register LINQ source
host.RegisterQueryableComponent("YourSourceName", t => GetYourLinqSource(t));
...
/// <summary>
/// Query handler.
/// </summary>
/// <param name="t">Element type</param>
private IEnumerable GetYourLinqSource(Type t)
{
...
}
LINQ queries can be easily executed from client on the published queryable component. Look at the following example code:
// Create a proxy for the published queryable component
var proxy = connection.CreateQueryableProxy("YourSourceName");
// Execute LINQ query for server-side processing
var result = from item in proxy.Get<Item>()
where item.Name.StartsWith("Hello World")
orderby item.Name
select item;
.NET Remoting knows OneWay methods. That are methods which are marked with an {"OneWay"} attribute. OneWay methodes don´t block the calling thread, so they´re useful for fire & forget scenarios. Zyan 2.0 now recognizes OneWay attributes, too. You can use them the same way you would do with .NET Remoting.