|
Introduction
.NET remoting is a beautiful framework, but its most serious deficiencies preventing it from being useful in today's Internet environment, is that it requires each node to have a global IP, so direct TCP connections can be made. Other incomplete solutions aimed at this problem exist (GenuineChannel), but they use special transport channels (instead of the standard TCP/HTTP), with large amount of unproven custom code, thus make your code heavier and unstable. These solutions also do not let 2 objects both behind firewall call each other, either.
Reachability channel sinks provide a more lightweight, elegant solution. Remoting objects hosted by machines with only local IPs behind firewalls can be made reachable from anywhere (including those machines behind firewalls themselves), using standard transport channels, by a Message Redirector. The whole process is transparent from upper, logic layers. Asynchronous calls, one-way calls are also supported.
Advantages of .NET remoting
.NET remoting has the the following advantages:
- Separation of network-code from logic code.
- Advantage over WebService: Clients can receive events from server. More types can be passed. Higher throughput.
Although .NET remoting is designed to accommodate the need of Enterprise intranet applications, nothing prevents it from being used over the Internet, at least not security problems - it is free from security holes - buffer overrun is impossible in managed code in which .NET remoting framework is entirely written. .NET remoting is ideal for peer-to-peer applications, because it alleviate programmers from writing complex network code that manages complex network topologies.
How to use in your project
Configuration files
- For hosts who are behind firewall and need to expose objects:
<appSettings>
<add key="RedirectorURL"
value="tcp://Redirector's IP:Port/Redirector.rem" />
</appSettings>
....
<serverProviders>
<provider type="Reachability.ServerSinkProvider, Reachability" />
<formatter ref="binary" />
</serverProviders>
- For hosts who need to call objects through
Redirector: <clientProviders>
<formatter ref="binary" />
<provider
type="Reachability.ClientSinkProvider, Reachability" />
</clientProviders>
- For
Redirector: <wellknown mode="Singleton"
type="Reachability.Redirector, Reachability"
objectUri="Redirector.rem" />
...
<channels>
<channel ref="tcp" port="Redirector's Port" >
<serverProviders>
<formatter ref="binary" />
</serverProviders>
<clientProviders>
<formatter ref="binary" />
</clientProviders>
</channel>
</channels>
Code needed
Add reference of Reachability.dll to your project.
Hosts which need to expose objects via the Redirector must call Reachability.ServerSinkProvider.StartWaitRedirectedMsg() after RemotingConfiguration.Configure(), or after RemotingServices.Marshal(). The effect of this call is to start receiving messages from the Redirector.
How it works
There are already many articles on how .NET remoting works and on channel sinks, and I don't have time to repeat such descriptions here. The Reachability sinks have 3 components:
- The
Redirector service
- The "Server" Channel Sink
- The "Client" Channel Sink
Basically, server sink adds reachability information (i.e. via which Redirector can we send message to this object) to (ChannelData in ) the ObjRef. When this ObjRef is passed to somewhere, there, the client sink finds out that reachability information attached to the ObjRef, and instead of directly trying to connect to the object, it pass the call to the Redirector. The Redirector will deliver the call appropriately, provided the host which created the ObjRef is listening on the Redirector properly. (This is done by StartWaitRedirectedMsg()).
Here is the heart of the code that makes hosts behind firewall receive calls (without special transport channel): public void RedirectRequest(Guid slot, bool oneway,
ITransportHeaders requestHeaders, byte[] requestStream,
out ITransportHeaders responseHeaders, out byte[] responseStream)
{
SyncQueue q = reqs[slot] as SyncQueue;
if(q==null)reqs.Add(slot, q=new SyncQueue());
q.Enqueue(new Message(requestHeaders,
requestStream, Thread.CurrentPrincipal, oneway));
if(!oneway)
{
q = resps[slot] as SyncQueue;
if(q==null)resps.Add(slot, q=new SyncQueue());
Response r = q.Dequeue() as Response;
responseHeaders = r.responseHeaders;
responseStream = r.responseStream;
}
else
{
responseHeaders = null;
responseStream = null;
}
}
.....
static void WaitForRequest(object o)
{
Redirector r = Activator.GetObject(typeof(Reachability.Redirector),
RedirectorUrl) as Reachability.Redirector;
Redirector.Message m;
try
{
while(true)
{
r.GetNextRequest(rdata.Slot, out m);
if(m==null)break;
IMessage respMsg;
ITransportHeaders respHeader;
Stream respStream;
ServerChannelSinkStack stack =
new ServerChannelSinkStack();
stack.Push(new ServerSink(theSink),r);
theSink.ProcessMessage(stack,null,
m.requestHeaders,
new MemoryStream(m.requestStream),
out respMsg, out respHeader, out respStream);
if(!m.oneway)
{
r.ReturnResponse(rdata.Slot,
new Redirector.Response(respHeader, respStream));
}
}
}
catch(Exception e){
System.Diagnostics.Trace.Write(e.ToString());
}
}
The node behind firewall will not listen for incoming calls, instead it calls GetNextRequest() and blocks till an incoming call arrives (like the Windows API GetMessage()). The Redirector is blocked until ReturnResponse() is called.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 23 of 23 (Total in Forum: 23) (Refresh) | FirstPrevNext |
|
|
 |
|
|
 |
|
|
Big thanks for this great article, but now my question: Is their a way to get the client to not open a listening socket, and still use this solution?
The problem i curently experience is that when i start the client it opens a listening socket which is noticed by the firewall, the firewall then pops-up and start asking if the Client should be allowed to be a server just to open a listening socket that is never used.
Thanks for any advice anyone can give me.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Here is my architecture :
- a Client behind a firewall - a Server behind a firewall - .NET 2.0 library
I could invoke remote methods from the server and get the result in my client application. Now, I want to make server traitment (with many tasks in) that returns "endOfTask" events to the client when each task is coming over.
It works well when client is not behind a firewall but when client is behind a firewall, server returns a RemotingException !

What is the problem ?
Is your library solve my problem and how could i get it working.
Thanks for your answer.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
hi, i am having problem that if client disconnected abnormally, then when any other client send message to that client, in that case its blocking the queue, not processing any request and server needs to be restart. so what can be done to solve this ? please reply
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
This looks to be a great solution. I'm currently transferring your configuration code into c# code. I noticed the sample was written to work with a configuration file. Perhaps at some point someone can upload a sample which does not use the configuration file.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
First of all a big thanks to Zhiheng Cao, i have been looking for a solution like this for quite some time! THANKS for this great article.
For those who dont like the .config files here the code to do the same thing (i think). If you use this code you can get rid of the server.exe.config and reduce client.exe.config to a single setting (or dont use that one either). This way you can make all the settings from within the code, and the .config files are not needed to setup the connection.
Hope it helps someone.. Don't know how to test if everything with the redirecor still works, but client does seem to work OK from behind a firewall.
<configuration> <appSettings> <add key="server" value="127.0.0.1:6000" /> </appSettings> </configuration>
-------------
//RemotingConfiguration.Configure("Client.exe.config"); string server = System.Configuration.ConfigurationSettings.AppSettings["server"];
ServerSinkProvider.RedirectorUrl = "tcp://"+server+"/Redirector.rem"; ServerSinkProvider.InitServerSinkProvider(); BinaryServerFormatterSinkProvider serverSinkProvider = new BinaryServerFormatterSinkProvider(); ServerSinkProvider serverSinkProviderB = new ServerSinkProvider(null, null); serverSinkProviderB.Next = serverSinkProvider; IDictionary props2 = new Hashtable(); props2["name"] = "Reachability"; props2["port"] = 0; TcpServerChannel tcpChannel = new TcpServerChannel(props2, serverSinkProviderB); ChannelServices.RegisterChannel(tcpChannel, false);
RemotingConfiguration.RegisterWellKnownClientType(typeof(ChatRoom), "tcp://" + server + "/ChatRoom.rem");
-------------
//RemotingConfiguration.Configure("Server.exe.config");
RemotingConfiguration.RegisterWellKnownServiceType(typeof(ChatRoom), "ChatRoom.rem", WellKnownObjectMode.Singleton); RemotingConfiguration.RegisterWellKnownServiceType(typeof(Redirector), "Redirector.rem", WellKnownObjectMode.Singleton); BinaryServerFormatterSinkProvider serverSinkProvider = new BinaryServerFormatterSinkProvider(); serverSinkProvider.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
BinaryClientFormatterSinkProvider clientSinkProvider = new BinaryClientFormatterSinkProvider(); ClientSinkProvider clientsink = new ClientSinkProvider(null, null); clientSinkProvider.Next = clientsink;
IDictionary props2 = new Hashtable(); props2["name"] = "Reachability"; props2["port"] = 6000; TcpChannel tcpChannel = new TcpChannel(props2, clientSinkProvider, serverSinkProvider); ChannelServices.RegisterChannel(tcpChannel, false);
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hello, I am trying to make your code work but without luck. I don't understand how it's work because for example chat room it's not marshaled in order to became a proxy (all the clients must see this class to know the reigistred clients). Cand you provide me the code with the configs files too?
Thank you for your time.
Best regards, Mihai
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Can you update your demo code to make it just build and run? It would be much better to make just two sulotions, one is server and one is client as well as those app.config files. Then I can just build them and run them. For now I cannot just download and run your demo. I have to do some work such as adding the config files. But I still cannot make them run correctly. I like your solution and it should be very useful to be used in many cases. But your description is very simple and I cannot really follow it. So if I have your demo run successfully, it will help me understand you solution. Thank you very much.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
 |
|
|
Could I ask you a small question? With this structure, when installing client application, we must install the director application to another computer that is known by both of client computer and server computer, is that right?
+ Client invoke server : [Client 192.168.0.5] --> [NAT 192.168.0.1 203.162.xxx.yyy] --> [Server 203.162.ccc.ddd]
+ Server fire event to client: [Server 203.162.ccc.ddd] --> [Director 203.162.mmm.nnn - 192.168.0.2] --> [Client 203.162.ccc.ddd]
Is that right?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Sorry, i means
+ Client invoke server : [Client 192.168.0.5] --> [NAT 192.168.0.1 203.162.xxx.yyy] --> [Server 203.162.ccc.ddd]
+ Server fire event to client: [Server 203.162.ccc.ddd] --> [Director 203.162.mmm.nnn - 192.168.0.2] --> [Client 192.168.0.5]
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
After installing the solutions I ran the server. Then when I started the client and the code gets to the AttachClient call I get the following error message:
.NET Remoting has been configured from the Client.exe.config file. System.Runtime.Serialization.SerializationException: Because of security restric tions, the type System.Runtime.Remoting.ObjRef cannot be accessed. ---> System.S ecurity.SecurityException: Request failed. at System.Security.SecurityRuntime.FrameDescSetHelper(FrameSecurityDescriptor secDesc, PermissionSet demandSet, PermissionSet& alteredDemandSet) at System.Runtime.Serialization.FormatterServices.nativeGetSafeUninitializedO bject(RuntimeType type) at System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject( Type type) --- End of inner exception stack trace ---
Server stack trace: at System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject( Type type) at System.Runtime.Serialization.Formatters.Binary.ObjectReader.ParseObject(Pa rseRecord pr) at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Parse(ParseRec ord pr) at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWi thMapTyped(BinaryObjectWithMapTyped record) at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWi thMapTyped(BinaryHeaderEnum binaryHeaderEnum) at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run() at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(He aderHandler handler, __BinaryParser serParser, Boolean fCheck, IMethodCallMessag e methodCallMessage) at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize (Stream serializationStream, HeaderHandler handler, Boolean fCheck, IMethodCallM essage methodCallMessage) at System.Runtime.Remoting.Channels.CoreChannel.DeserializeBinaryRequestMessa ge(String objectUri, Stream inputStream, Boolean bStrictBinding, TypeFilterLevel securityLevel) at System.Runtime.Remoting.Channels.BinaryServerFormatterSink.ProcessMessage( IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders reques tHeaders, Stream requestStream, IMessage& responseMsg, ITransportHeaders& respon seHeaders, Stream& responseStream)
Exception rethrown at [0]: at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage req Msg, IMessage retMsg) at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgDa ta, Int32 type) at KnownObjects.ChatRoom.AttachClient(IChatClient iChatClient) in C:\Document s and Settings\Gery D. Dorazio\My Documents\Downloads\dotNet Development\dotNet Apps\Remoting Message Redirection\reachability_demo\demo\KnownObjects\ChatRoom.c s:line 46 at Client.ChatClient.Main(String[] args) in c:\documents and settings\gery d. dorazio\my documents\downloads\dotnet development\dotnet apps\remoting message redirection\reachability_demo\demo\client\chatclient.cs:line 33
Can you give me some idea how to correct this problem? I am running this test on one machine...XP Prof with .NET 1.1 framework installed.
Thanks, Gery
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
I'll not write a book explaining this, but you'll find the 'problem' will quickly be solved by an internet search on:
TypeFilterLevel.Full
...which is a new 'feature' in 1.1 applying to FormatterSink's.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Actually proposed solution is rather a big headache than a solution which will get you through firewalls/NAT/etc. You'll have to have redirecting sinks at all of your firewalls and so on. Instead of just implementing your solution using true bidirectional TCP channels (like Genuine Channels)
[Server ] --> [Firewall] --> [Client ]
You'll have to have either Server at the same computer where firewall is set up (Actually in this case this solution is pretty useless because actually no firewall between the server and the client, or/and you can set it up to allow incoming requests):
[Server + Firewall] --> [Client]
Or have redirecting sinks everywhere:
[Server ] -> [Firewall + your solution with redirecting ] -> [Client ]
Again, instead of just creating your solution with bidirectional channels you will have to create several servers with redirecting sinks and you'll have to install them and tune them at each host with firewalls/NATs/etc.
Wanted to notice that with this solution [Server ] --> [Firewall] --> [Client ] you will only specify server's address while initializing client TCP channel. And it will work.
|
| Sign In·View Thread·PermaLink | 1.50/5 (2 votes) |
|
|
|
 |
|
|
One thing you misunderstood is that you don't have to place Redirectors at your firewall, they can be placed anyplace to which the client behind NAT can make a connection. Since the Redirector does not call back the client, the client does not have to be connectable from Redirector. Suppose you plan to use this solution to provide a service. Users behind NAT will only have to install your client in their machine. The client can be configured to connect to a certain address, can call GetNextRequest() and wait. This way the client behind NAT can receive requests.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
No 
For example:
client [with sink 192.168.1.3] -- NAT server [192.168.0.1 and 84.156.35.84] -- redirector server [with sink 35.12.63.35] -- server [also real IP address]
So we want to reach the client from our server: server [with sink] --> redirector server [with sink, it's OK] -- Oops. We can't get though the NAT because client is in LAN and is unreachable (Microsoft channels have to open direct connection). Your solution won't provide back access. Any bidirectional channel will do.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
In fact, the "bidirectional" channels such as GenuineChannel work because it sends message back to client behind NAT using the same socket that it received from the "accept" socket call. This is the way web servers send back response to clients behind firewall. What NAT prevents, is that the server call the "connect" socket call, to try to actively connect to the client behind NAT. My solution(Redirector) works the same way. The Redirector never call "connect" to actively send messages to client behind NAT. This is because it does not need to "unmarshal" MBR objects in client. Instead, the client calls the method of Redirector, "GetNextRequest". This method blocks until the Redirector receives request for that client. Then the "GetNextRequest" returns, along with the request stream in a byte[] block. Did this explanation cleared your doubt?
P.S. One possible drawback of this solution is that the Redirector must hold a thread in the thread pool for each client that is being blocked by GetNextRequest. You will need to enlarge the thread pool count otherwise it can only serve 25 clients.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
That is your redirector keeps connection opened to the client forever? Server can send a message to the client at any moment.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
You are right, but that is also what Genuine channel is doing I believe. Considering that you can distribute the load to multiple Redirector, each opening ~10k connections will not be such a serious problem.
|
| Sign In·View Thread·PermaLink | 2.00/5 (2 votes) |
|
|
|
 |
|
|
25 clients connected to the server in total, or 25 clients calling the server at the same time?
|
| Sign In·View Thread·PermaLink | 2.00/5 (2 votes) |
|
|
|
 |
|
|
It must be Connected in Total since each client (upon connecting) calls GetNextRequest and waits. That means the server has one thread waiting to respond for each connected client.
Kevin
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|