Click here to Skip to main content
15,880,651 members
Articles / Programming Languages / C#

Pfz.Remoting - Stateful Remoting by interface

Rate me:
Please Sign up or sign in to vote.
4.56/5 (8 votes)
29 Oct 2009CPOL8 min read 27.4K   594   16   17
This article explains the concept of a stateful remoting framework capable of doing the remoting of any object by interface, and how to use the classes.

Introduction

This is a stateful Remoting framework which works without MarshalByRefObjects.

The evil MarshalByRefObject

I must say I don't like MarshalByRefObjects because they don't allow the developer to do local optimizations on them. See IDbConnection and SqlConnection, for example. If you use SqlConnection, there is no virtual-call involved, as the class is sealed. But, if you use IDbConnection, you do the virtual call. If SqlConnection was a MarshalByRefObject, you will see SqlConnection sealed, but still will be invoking the methods virtually, as MashalBuRefObjects turn everything into virtual implicitly.

The solution? Never create base objects from MarshalByRefObject. Create normal objects that implement interfaces. If you need them remotely, create a MarshalByRefObject wrapper that also implements the same interfaces. So, locally, you can use the objects directly, without the interface, and you will be avoiding the unnecessary virtual calls. You can even use CodeDOM to create the wrappers automatically for you. But, this is not enough, as Remoting is stateless.

What stateless really means?

Let's see the IDbConnection example, with stateless Remoting.

  1. I create the connection. In the server, nothing happens.
  2. I set the connection string. In the server, it creates a new connection and sets the connection string.
  3. I call Open. In the server, it creates a new connection, without the connection string, and calls Open, causing an exception.

Bad, isn't it? OK, but the right way to create a WebService is to pass all the required parameters in a single call... wait, I am talking about Remoting, not Web-Services. For example, I create a Remoting application because I need to access a 32-bit database driver from a 64-bit application. Instead of changing all the application logic, I simple create a connection, set a connection string, and call Open, and have these calls redirected to the server, in a stateful manner. So...

The solution - Stateful Remoting

The solution is relatively simple in theory, but very complex in implementation, because it must work in a thread-safe manner and deal with some problems caused by some TCP/IP restrictions. The basic idea is: when the client asks to create an object, the server creates the object, generates an ID for it, adds it to a dictionary, and sends the ID to the client. The client will then create a wrapper implementation over the interface (as Remoting will work only over interfaces) and redirects all calls to the server, passing the ID of the object, the method/property being invoked, and the parameters. When the wrapper object is collected in the client, it sends a message to the server, telling the server to remove the object from the dictionary so it can also be collected in the server.

Well, that's the basic idea. But I wanted more. The client can also send an object (by reference) to the server. So, if the server then calls any method of that object, it will redirect the call to the client, in the same way it's done when the client asks for something in the server. So, in practice, the client and server are only different by the fact that the server starts listening, but after that, both run as client and server.

The first problems

Each call writes data then reads data. But I also needed a thread to be always reading for new requests. As one read must not read data from another, I needed more than one port. But, as I wanted a multi-threaded solution, I didn't want each thread to have a new TCP/IP port created. Also, during implementation, I discovered that many threads sending data from the same TCP/IP port, even with full locks, caused buffer problems in TCP/IP, so every write must be done by only one thread. My solution - the StreamChanneller. The StreamChanneller is a class that is created over an already existing stream (TCP/IP stream, in my case). It allows to create many "channels" (other streams) which, in practice, use the same real stream.

How it works

  • Each channel buffers its writes. After some amount of data, or when calling Flush, the channel sends the channel ID, the buffer length, and the buffer data. In my first version, I did this using a lock on the real stream, but TCP/IP started to have buffer problems, so the real writing is now done by a writer thread.
  • The writer thread waits for an event to be set. When it is set, it dequeues all buffers and writes them to the real stream, flushes the real stream, and starts to wait again. Very simple.

OK. The write part is simple. But, the problem is always in the read part. It is not very complex in theory, but the partial reads are a problem to implement.

How the read works

  • If it has an already read buffer, it reads the data from that buffer, which does all the necessary calculations for partial reads.
  • If it does not have an already read buffer, it waits for an event to be set. When it is set, it will have at least one buffer.
  • There is also a thread that's always reading the real stream. That thread knows that it will receive the channel ID, the buffer size, and then the real buffer. Only after receiving the full buffer will it add the buffer to the "channel buffers" and set the event of that channel, so it can continue reading if it is actually waiting for data to continue its read.

Also, the StreamChanneller itself needs to use a channel (channel 0) to be informed of newly created channels to the other side, and to be informed of closed channels so the other side can close them too. And, if there is a buffer already sent to the closed channel, it must be able to "discard" such information when it is read.

If you don't want to use this remoting solution, but want to have many channels inside your own TCP/IP connection (or other similar connections, like IPC), you can use the StreamChanneller. It is thread-safe, and it is working in production for over a year now.

Getting back to the topic

Returning to the remoting itself, I needed to care about many things:

  1. I can call a method/set a property on the server, passing as the parameter an object already created by the server. For example:
  2. C#
    command.Connection = connection;
  3. When I am processing a call, I can be receiving a remote reference, and so I need to create the wrapper here, or I can be receiving the reference of a local object, so I must find the object referenced.
  4. I must be able to work with ref and out parameters.
  5. When returning a value, I must also be able to return a reference of an object.
  6. Obviously, primitives and some other types must be serialized.

  7. When I invoke a method and wait for its return, I can receive an Invoke request instead of the return value. This is not an error. Suppose that the client calls MethodA(), which, in the server, executes clientObject.MethodB(). MethodB uses ThreadStatic variables, and so, must be executed in the same thread that called MethodA(), as it will be if it was used locally.
  8. Support events. So, the client can register in an event on the server, and when the server triggers the event, the code is executed in the client.

Well, from that, you can imagine the implementation is very complex. I will not try to explain this in the article. If someone is interested in the key parts, please write a message asking for the specific details.

Let's see how to use the code, but first, keep in mind that you must use public interfaces known by the client and the server to be able to do the remoting, so, it is very much indicated that you use a common DLL with the interfaces in the client and in the server.

The server

All you have in the server is the RemotingServer class. With it, you:

  • Register the static methods you want the clients to be able to call.
  • Register the interface types accessible to the clients, and the classes that will in fact implement these interfaces.
  • Can set if cryptography is required, optional, or forbidden. And, if accepting cryptography, which ones will be accepted.
  • And then, call Run to start listening.

For example, the main class of the "simple chat server" looks like this:

C#
using System;
using System.Security.Cryptography;
using System.Threading;
using Pfz.Remoting;
using RemotingArticleSamples.Common;

namespace RemotingArticleSamples.Server
{
  class Program
  {
    static void Main(string[] args)
    {
      using(var server = new RemotingServer())
      {
        Thread thread = new Thread(p_Server);
        thread.Name = "Listener server thread";
        thread.Start(server);
        
        Console.WriteLine("Press ENTER to close the program.");
        Console.ReadLine();
      }
    }
    private static void p_Server(object serverObject)
    {
      try
      {
        RemotingServer server = (RemotingServer)serverObject;
        
        // To this sample, I will require cryptography, and will only
        // accept RijndaelManaged and TripleDES. If I don't register a
        // valid cryptography, all cryptographies are valid.
        
        server.CryptographyMode = CryptographyMode.Required;
        server.RegisterAcceptedCryptography<RijndaelManaged>();
        server.RegisterAcceptedCryptography<TripleDESCryptoServiceProvider>();
        
        // Here I register the valid classes that the client can create
        // directly.
        server.Register(typeof(IServer), typeof(Server));
        
        // Here I start the server.
        server.Run(658);
      }
      catch(ObjectDisposedException)
      {
        // If during initialization the user press ENTER and disposes the
        // server, I will simple ignore the exception and finish the thread.
      }
    }
  }
}

The client

The client can be created directly, like:

C#
RemotingClient client = new RemotingClient("127.0.0.1", 658);

Or indirectly, create RemotingParameters first, set all the values, and then create the client. The RemotingParameters class has properties to automatically reconnect after the connection is lost (but the instances will be lost if this happens), to set the cryptography to be used, and two events, one when it gets the TCP/IP stream (so you can force it to use an SSL stream, for example, if you don't want automatic cryptography), and one that happens just after the server answers that it accepts the cryptography or not and which ones are accepted. At this moment, the client can also set the cryptography, but it will at least know if the cryptography it tries to use is valid or not.

That's all for now. Look at the examples to see how simple it is to use the code.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions

 
QuestionKey not found in dictionary (runtime Exception) Pin
Member 1358738524-Dec-17 4:55
Member 1358738524-Dec-17 4:55 
AnswerRe: Key not found in dictionary (runtime Exception) Pin
Member 1358738524-Dec-17 10:31
Member 1358738524-Dec-17 10:31 
GeneralRe: Key not found in dictionary (runtime Exception) Pin
Member 1358738524-Dec-17 18:32
Member 1358738524-Dec-17 18:32 
GeneralRe: Key not found in dictionary (runtime Exception) Pin
Paulo Zemek27-Dec-17 17:28
mvaPaulo Zemek27-Dec-17 17:28 
GeneralRe: Key not found in dictionary (runtime Exception) Pin
Member 1358738528-Dec-17 0:41
Member 1358738528-Dec-17 0:41 
GeneralRe: Key not found in dictionary (runtime Exception) Pin
Member 1358738528-Dec-17 3:44
Member 1358738528-Dec-17 3:44 
GeneralRe: Key not found in dictionary (runtime Exception) Pin
Member 1358738528-Dec-17 3:53
Member 1358738528-Dec-17 3:53 
GeneralRe: Key not found in dictionary (runtime Exception) Pin
Paulo Zemek29-Dec-17 17:07
mvaPaulo Zemek29-Dec-17 17:07 
QuestionError for type Server Pin
Member 1358738522-Dec-17 3:38
Member 1358738522-Dec-17 3:38 
AnswerRe: Error for type Server Pin
Paulo Zemek22-Dec-17 10:21
mvaPaulo Zemek22-Dec-17 10:21 
GeneralRe: Error for type Server Pin
Member 1358738523-Dec-17 15:35
Member 1358738523-Dec-17 15:35 
GeneralRe: Error for type Server Pin
Member 1358738523-Dec-17 15:54
Member 1358738523-Dec-17 15:54 
GeneralRe: Error for type Server Pin
Member 1358738523-Dec-17 20:39
Member 1358738523-Dec-17 20:39 
GeneralLeases Pin
Paulo Zemek19-Nov-09 2:48
mvaPaulo Zemek19-Nov-09 2:48 
GeneralPuzzling Pin
John Brett3-Nov-09 2:15
John Brett3-Nov-09 2:15 
I can't work out whether this is genius or pointless.
The code is comprehensive, deals with a wide range of issues, and looks to be (mostly) very well written.
However, I can't see what problem this is trying to solve that remoting can't solve already. Just to make this absolutely clear, .Net remoting can already do stateful objects out of the box, with thread safety &c. Of course it isn't recommended, because it doesn't scale well.
And whichever way I look at it, re-writing infrastructure written and tested by Microsoft as big as remoting is a daunting task.

So, why bother? What is the problem you're trying to solve?

John
GeneralRe: Puzzling Pin
Paulo Zemek3-Nov-09 6:35
mvaPaulo Zemek3-Nov-09 6:35 
GeneralOr better... Pin
Paulo Zemek3-Nov-09 13:41
mvaPaulo Zemek3-Nov-09 13:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.