This project has moved. For the latest updates, please go here.
The content of this page has been copied from WebArchive.

GotDotNet User Sample: TcpEx Remoting Channel

TcpEx Remoting Channel: TcpExChannel.rar
Uploaded by Deason, rikware.com

Description

The TcpEx channel is a replacement for the built-in Tcp remoting channel. It improves on the standard tcp channel by allowing communication in both directions on a single tcp connection, instead of opening a second connection for events and callbacks. The channel has similar goals to Ingo Rammer's Bidirectional Tcp Channel; however, it does not use any explicit threads and supports hostnames as well as IP addresses. Updated: 14/4/03 - See TcpExChannel.html for details. Updated v1.2 - 19/11/03 - check http://g2.fit.qut.edu.au/G2P2P/Utilities/ for any further developments.

GuyEttinger Posted on: 04/16/2003 09:29:05Great Channel

This is definately looking to be a strong implementation of a bidirectional tcp channel. Keep up the good work!
dcazzulino Posted on: 08/07/2003 12:04:31

A trivial but almost required and urgent update is to make the private MessageException class derive from IOException, to keep catch blocks (usually trapping SocketException and IOException) compatible with built-in channel.
KimmoK Posted on: 09/25/2003 06:23:42

Updating due to .NET framework 1.1 is also required. To get the sample working in 1.1 you need to modify TcpExChannel.cs. Changes below. Two lines are added and one line changed. Note that only relevant code is shown below (e.g. two unchanged lines are missing from the code below. They are not deleted, just not shown)

void Initialise( ... ) 
{ 
   IDictionary props = new Hashtable(); // for .NET 1.1 
   props["typeFilterLevel"] = "Full"; // for .NET 1.1 

   if (serverSinkProvider == null) 
      serverSinkProvider = new BinaryServerFormatterSinkProvider(props, null); // params added

Geco Posted on: 11/06/2003 03:14:48

What about scalability?
TheLoneCabbage Posted on: 02/18/2004 04:47:20

For those of us who detest (or are simply to daft to understand) config files.

The moral of the following story, is that both the marshaled class, and the class remoting it, must be marshelable...

Now feel free to critisize me for the bad programming practices you see in the following code.

namespace TestServer 
{ 
   class TestServer 
   { 
      [STAThread] 
      static void Main(string[] args) 
      { 
         ChannelServices.RegisterChannel(new TcpExChannel(TypeFilterLevel.Full,1948)); 
             testclass.TestClass tc=new testclass.TestClass(); 
         RemotingServices.Marshal(tc,"TLC8675309"); 

         Console.WriteLine("Press Enter to close server."); 
         Console.ReadLine(); 
      } 
      } 
} 




namespace TestClass 
{ 
   [Serializable] 
   public class TestClass: MarshalByRefObject 
   { 
      private int cnt=0; 
      public delegate void dPoke(int x); 
      public event dPoke OnPoked; 

      public TestClass() 
      {} 

      public string SayHello() 
      { 
         cnt++; 
         Console.WriteLine("I'm saying 'Hello'. Are you happy? This is the " 
                                +cnt.ToString()+" time!"); 

         return "Hello, putz!"; 
      } 

      public void PokeBack(int x) 
      { 
         Console.WriteLine("Poking Client!"); 
         if(OnPoked!=null) 
            if(OnPoked.GetInvocationList().Length>0) 
            { 
               OnPoked(x); 
               Console.WriteLine("I poked the client."); 
               return; 
            } 
         Console.WriteLine("No Events Registered."); 

      } 

      public override object InitializeLifetimeService() 
      { return null; //return base.InitializeLifetimeService (); } 

   } 
} 



namespace TestClient 
{ 
   [Serializable] 
   class TestClient :MarshalByRefObject 
   { 
      public void pk(int x) 
      { 
        Console.WriteLine("I've been poked ("+x.ToString()+")!"); 
      } 


      public TestClient(int timeout) 
      { 
         Console.WriteLine("Press Enter to start client."); 
         Console.ReadLine(); 
    

         ChannelServices.RegisterChannel(new TcpEx.TcpExChannel(TypeFilterLevel.Full,true)); 

         TestClass tc=(TestClass)Activator.GetObject(typeof(testclass.TestClass), @"tcpex://localhost:1948/TLC8675309"); 
          
         tc.OnPoked+=new TestClass.dPoke(pk); 
          
         Console.WriteLine(tc.SayHello()); 

         tc.PokeBack(42); 
   } 
}. 

BobishKindaGuy Posted on: 06/30/2005 09:42:15

Just a VB guy, trying to implement the TheLoneCabbage's replacement code so as not to have to use a config file....

When I replace the original TestServer code with the suggested code, I get one error: " } expected "

If I add the brace, I get several errors:
The name 'tc' does not exist in the class or namespace 'TestServer.TestServer'

and some errors like this:

The type or namespace name 'ChannelServices' could not be found (are you missing a using directive or an assembly reference?)

The others are similar ("could not be found"), regarding "TcpExChannel", "testclass", and "TypeFilterLevel"

Thanks for any clarification.
Bob
BobishKindaGuy Posted on: 06/30/2005 16:04:31

I've converted this project to VB.NET, and got it down to 1 build error.
Would anyone like to review my code and make it work?
chrislove Posted on: 07/29/2005 09:22:11

I'm not a VB user but that sounds like an error you would get if you didn't have

System.Runtime.Remoting

listed as one of your references. Project/Add Reference.. then choose System.Runtime.Remoting.

Hope that helps!
Igilima Posted on: 01/17/2006 11:32:37

I have spent some time putting together a fairly robust client/server application that requires bi-directional communication over a TCP connection.

I used this channel for a long while but found that it has a pretty significant bug that shows up under heavy multi-threaded use. The gist of it is that server-to-client calls will sometimes block indefinitely. In my case this caused a slow "thread leak" and loss of server-to-client messages. One solution might be to allow only one thread to make calls back to the client at a time but that won't work for me.

I have a client object that inherits from an abstract class and a server object that implements an interface. This allows the client and server implementations to know nothing about each other except for the other's interface methods. The client gets a reference to the remoted server object via the TcpEx remoting channel and then passes a reference to itself to the server with each client-to-server request. Later, when the server has a response, it will call a method on the client reference to return the response. My requests are all numbered and the client object uses that number (returned in the response) for correlation to outstanding requests. Every time the server sends a response to the client it spawns a worker thread to execute the client-side method.

Unfortunately, when a number of calls are made to the client in rapid succession a few of them will get "lost" and the server-side worker thread stays blocked inside of TcpEx indefinitely. The place where it gets blocked is at the ar.AsyncWaithandle.WaitOne(); in TcpEx’s ClientTransportSink.ProcessMessage(…) (ClientTransportSink.cs). It is blocked here waiting for the result of the remote method execution on the client.

Meanwhile, on the client, the "lost" messages never get translated into the intended method execution because they are sitting in the outstandingMessages hashtable declared in Manager.cs. They are queue'd there by the ReceiveMessage() method when the listeners hashtable is empty. This rarely happens because there is usually at least one listener (placed there by the BeginReadMessage() method). The main problem is that whenever ReceiveMessage() finds a listener in the listeners hashtable to handle an incoming message, it removes that listener (which may be the only one there) and if messages are piling in they could lock and access the empty listeners hashtable before BeginReadMessage() can repopulate it with a listener.

Unhandled messages get queue'd into the outstandingMessages hashtable using connection.LocalAddress as key. BeginReadMessage() does attempt to process queue'd messages out of outstandingMessages but it almost always tries to locate them using a guid as the key whereas they were usually placed there using an IP address as the key.

My attempted fix was to change the key being used to queue into outstandingMessages and use the LocalGuid instead of LocalAddress. Now all my method calls completed but I suddenly found that my client-side messages were getting messed up because either wrong methods were being called or wrong message bodies were being used.

Anyway, I don't have time to pursue a fix for this right now but I figured I would post what I know for posterity before I swap in a different channel implementation.
BobishKindaGuy Posted on: 01/17/2006 11:52:33

Well, I found that if a function call (as opposed to a sub) was "pending" or "being processed" and therefore incomplete, that is, the function (in the server module) had not done a "return" yet.....

In that case, if the server module tried to send some message "back" to the client, that would hang remoting.

So what I did was, firstly, limited the number of function calls to ones that would simply do the work and return right away.

Secondly, I set up a queueing system for the "subs" sent to and from the client(s).

So whenever the server remoting functionality sees an incoming message, it doesn't try to "process" it, but just sticks every incoming message into a little class (say, "Thingy") containing the vital info passed in, and sticks the class on the queue. (Thingy.Enqueue).

Same thing for the outgoing messages (server to client). Server puts the message data into the little class and sticks the class on the outgoing queue.

Next, I set up a thread to process the queues. (note: do not use peek!!! use Dequeue and if you can't process it at that time, requeue it with Enqueue)

My strategy ended up being this: The queue checker thread loops through the following steps:
1. Dequeues one incoming item, processes whatever has to be done for that item.... (which may generate some outgoing message(s)... but that's okay because they just go on the outgoing queue anyway)
2. Processes all of the outgoing queue items. That way, if several outgoing messages were associated with some complicated functionality, they would all go back to the client before another message is processed.

After a LOT of trial and error, this seems to be working, because the queue processor guarantees that one message and only one message is processed at the server side at a time, but allows many messages to go back to client for status, etc

Let me know if anything needs clarification.
Post edited by BobishKindaGuy on 01/17/2006 11:56:52
bfrasure Posted on: 02/27/2007 15:38:20

Are you able to post this code here? I'm running into a similar problem. This seems to be a good tool but just scalable enough. It's a shame the author has left it behind.
BobishKindaGuy Posted on: 02/27/2007 16:09:04

I ended up not using this as the basis for my remoting solution.

I did end up using queues though.

Basically my strategy is:

Have 2 queues on the server side, one incoming; one outgoing.
A thread does nothing but receive remoting messages and put them on the incoming queue, and send out messages on the outgoing queue.

It does it like this: Put one incoming message on the incoming queue, then pop every message off the outgoing queue and send them all out.

Another thread checks the incoming queue and processes it. If any message(s) have to go back to any client(s), it simply pops the message on the outgoing queue.

Bear in mind that so far, these involve "subs" on the client side, not "functions". Because functions are bad unless you know for sure that the server can process it quickly and return a value without much work. I use a function to assign an ID to a new client. Then that client includes the ID for every other "sub" type of remoting call to the server.

This model allows server to keep any and all clients informed about the progress of any long-running process. For example, a user on the client side tells the server to initiate some process that is loosely-coupled. Therefore, nobody knows when or if it will ever complete. But whenever any progress report (or when the process is complete) needs to be sent to client so the user sees something happening, the server is free to send it back, because it goes on the queue and is processed in order and in an orderly way. If I had used a function for the long-running process, I could not keep client informed about anything until the process was complete, because if you try to send a message out on a remoting channel that is still "in the middle of" a function call, that's when you get this mysterious "blocking" that gives (as far as I could tell) no feedback about why. I found all this out through painful trial and error.

On the client side, I don't think any queues or other threads are necessary, because it's all real-time, where the user is clicking on things and waiting until they're done. But just to be safe, I think I did set up the queueing model on the client too!

How scalable is this? I don't know, because I am alone here and don't have the time to test under stress, and my needs are not high volume anyway.

As far as sending you code, it's fairly integrated into my app. Hard to isolate it now, although I do try to do object-oriented as much as possible.

If you need my help, are you in a position to pay for any consulting time?
I hope this helps a bit.

Last edited May 20, 2014 at 11:28 PM by yallie, version 9

Comments

No comments yet.