Академический Документы
Профессиональный Документы
Культура Документы
NET Remoting
Author: Olexandr Malko
Date: 09/29/2008
Introduction
.NET Remoting is available since beginning of .NET intoduction. It’s time to get to
know it well eventually. I hope this topic will help you with this. This document has
many samples attached. It was decided not to overload one project with all features at
once. Even though only couple lines should be changed for switching from one final
application to another, there will be a separate solution to avoid text like “//uncomment
this to gain that”. All samples are introduced with Visual Studio 2003 solutions. So, you
should be able to open them with VS2005 and VS2008.
Sometimes objects on pictures won’t have numbers even though those will be referred
as “second” or “fifth”. I will use such numbering with rules to count from top to bottom
and from left to right.
Content
1. What is .NET Remoting?
2. Simple project to show .NET Remoting
3. Configuration file and configuration in code
4. Types of remote object activation
4.1. Server Side Object Activation. Singleton
4.2. Server Side Object Activation. SingleCall
4.3. Client Side Object Activation
5. What is Lease Time? How to control it?
6. Hide Implementation from Client. Remoting via Interface exposure.
7. Custom types as parameters and return values
8. Custom exceptions through remoting channel
9. Events in .NET Remoting
10. Asynchronous calls
11. Several Services in one Server. Several Server links from single Client app
12. Summary
Now, let’s take a look at picture below. You can see two separate processes. Server is
holding a real instance of MyService. This instance can be used by other processes
over .NET Remoting. Client process is not instantiating the instance of MyService. It just
has some transparent proxy. When Client application invokes methods of MyService
proxy, the proxy redirects those calls to .NET Remoting Layer in Client process. That
remoting layer knows where to send such call – so, call goes over network (e.g. over
.NET Remoting channel) right to our remoted Server process. After that Remoting layer
on Server side knows if it should use already existing instance of MyService or create
new one. It depends on type of activations. Activation types can be configured in *.xml
config file or through code. All this will be described later in this article.
You may find “Simple Remoting” solution in downloads. It consists of three core
projects. Almost all samples in this article will have them:
You can start as many Client applications as you want. All of them will be served by
single Server application. You cannot start several Servers at the same time though. This
is because there is a port to listen for remote Client applications. You cannot initiate
several socket listeners on the same network card and the same port.
Also, I would like to pay your attention at Log and Utils classes. They will be used
with all samples. You will find Log useful to print timestamp with each print out. Also, it
prints id of current thread – so we can easily see if the same thread was used for group of
actions or not. As for Utils class, it dumps information about all registered remoting
service and client types. It helps you to catch some misconfiguration in case something is
not working:
Collapse
public class MyService : MarshalByRefObject
{
public MyService()
{
Log.Print("Instance of MyService is created");
}
Collapse
class MyServer
{
[STAThread]
static void Main(string[] args)
{
RemotingConfiguration.Configure("ONXServer.exe.config");
Utils.DumpAllInfoAboutRegisteredRemotingTypes();
It might surprise you if you really see .NET Remoting for first time. There is nothing
specific and complex here. Why is it working? The
“Utils.DumpAllInfoAboutRegisteredRemotingTypes()” is simply invoked to print
registered .NET services. The “Log.WaitForEnter(..)” is just user prompt to press
ENTER to close our console application. So, the only line of code that really turns our
regular console application into .NET Remoting Server is
“RemotingConfiguration.Configure("ONXServer.exe.config")”. This method reads *.xml
file and has enough information from there to start socket listener on some port and to
wait for requests from remote Client applications! This is nice approach as you can
change behavior of your application without need to change and recompile our code.
Now, let’s take a look at this configuration file:
Collapse
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<service>
<wellknown
type="ONX.Cmn.MyService, ONXCmn"
objectUri="MyServiceUri"
mode="SingleCall" />
</service>
<channels>
<channel ref="tcp" port="33000" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
If you have several services that should be registered in our application, list
“wellknown” sections one after another inside “service” section.
Also, beside from “service” section there is “channels” section. Here we might have
several channels defined. In our sample we have only “tcp” channel defined. It will be
listening on port 33000.
Collapse
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<client>
<wellknown
type="ONX.Cmn.MyService, ONXCmn"
url="tcp://localhost:33000/MyServiceUri" />
</client>
</application>
</system.runtime.remoting>
</configuration>
You may notice pretty much similarity between Server and Client configurations. The
difference is that in Client configuration we have “<client />” section instead of “<service
/>”. It makes application understand that when we create instance of MyService we
actually want to request this class remotely. Also, wellknown section has “url” attribute
that will connect to “localhost” machine to port 33000 and request named service with
URI MyServiceUri. Attribute “type” says application to use this remoting whenever
Client application code tries to instantiate the MyService object on client side. So, no
actual instance of MyService is created in Client application. We only create Proxy that
knows where to send our call requests whenever we call some method.
Collapse
class MyClient
{
[STAThread]
static void Main(string[] args)
{
RemotingConfiguration.Configure("ONXClient.exe.config");
Utils.DumpAllInfoAboutRegisteredRemotingTypes();
Here is what we get in Server and in Client consoles for our sample:
Collapse
SERVER:
You can see that Instance of Service is created in Server application even though we
have “new MyService()” in Client application code!
As a brief example here is code that makes the same configuration as we have for our
Client application in “Simple Remoting” sample in previous topic:
Collapse
//RemotingConfiguration.Configure("ONXClient.exe.config");
RemotingConfiguration.RegisterWellKnownClientType(
typeof(MyService),
"tcp://localhost:33000/MyServiceUri");
You may want to check MSDN to get more details on .NET Remoting configuration in
code.
2) Server Side SingleCall - Object is created for EACH method call. So, it does not
matter how many Client applications are running. Every method call from any Client
application has this life-cycle:
3) Client Side Activation - Object is created in Server application with every "new"
operator that is in Client application. Client application has full control over this remote
object and does NOT share it with other Client applications. Also, if you create 2 or more
remote objects in your Client application - yes, there will be created the exact number of
remote objects in Server application. After that you may work with each instance
individually as you would do without .NET remoting involved. The only issue here is
Lease Time that might destroy your remote object on Server application earlier than you
expect. See “5. What is Lease Time? How to control it?”
For Server Activation Object you will need to register “well known type”. For Client
Activation Object you will need to register “activated type”. Let take a look at each type
of activation closer.
If Lease Time is expired, Singleton might be destroyed on Server. In this case with
new request from Client application new Singleton is created and is used in the same way
– e.g. Single object for all Clients requests. See “5. What is Lease Time? How to control
it?”
To use this type of activation you should configure server with well-known type like
this:
Collapse
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<service>
<wellknown
type="ONX.Cmn.MyService, ONXCmn"
objectUri="MyServiceUri"
mode="Singleton" />
</service>
<channels>
<channel ref="tcp" port="33000"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
Collapse
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<client>
<wellknown
type="ONX.Cmn.MyService, ONXCmn"
url="tcp://localhost:33000/MyServiceUri" />
</client>
</application>
</system.runtime.remoting>
</configuration>
Collapse
class MyClient
{
[STAThread]
static void Main(string[] args)
{
RemotingConfiguration.Configure("ONXClient.exe.config");
Utils.DumpAllInfoAboutRegisteredRemotingTypes();
string result;
//create myService1
Log.WaitForEnter("1) Press ENTER to create Remote Service...");
MyService myService1 = new MyService();
Log.Print("myService1 created. Proxy? {0}",
(RemotingServices.IsTransparentProxy(myService1)?"YES":"NO"));
//query myService1.func1()
Log.WaitForEnter("2) Press ENTER to query 1-st time...");
result = myService1.func1();
Log.Print("myService1.func1() returned {0}", result);
//query myService1.func2()
Log.WaitForEnter("3) Press ENTER to query 2-nd time...");
result = myService1.func2();
Log.Print("myService1.func2() returned {0}", result);
//create myService2
Log.WaitForEnter("4) Press ENTER to create another instance of
Remote Service...");
MyService myService2 = new MyService();
Log.Print("myService2 created. Proxy? {0}",
(RemotingServices.IsTransparentProxy(myService2)?"YES":"NO"));
//query myService2.func1()
Log.WaitForEnter("5) Press ENTER to query from our new Remote
Service...");
Log.Print("myService2.func1() returned {0}", myService2.func1());
We get
Collapse
SERVER:
Here in this sample only 1 MyService instance was created on Server side. It served all
3 calls even though 2 calls came from myService1 and 1 call from myService2.
4.2. Server Side Object Activation. SingleCall
As for creation of object on a Server side, we have the same situation – no object is
created with “new MyService()” on a Client application. But as soon as you invoke ANY
method in Client code, the invocation is directed to Server application. The .NET
Remoting creates NEW instace for each such query. As you can see on a picture below,
there were 5 invocations sent from 2 Client applications. It made .NET Remting create 5
instances of MyService. Each of instances was used only once – for single call. Pay
attention that “Instance of MyService” #3 and #5 were for the same created with the same
call of “myService1.func1()”, but still .NET Remoting created a separate instance for
each call.
Single Trasparent Proxy is created for all MyService objects in Client application (see
second Client).
To use this type of activation you should configure server with well-known type like
you did for SSA Singleton. The only difference is that mode should be set to
“SingleCall”:
Collapse
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<service>
<wellknown
type="ONX.Cmn.MyService, ONXCmn"
objectUri="MyServiceUri"
mode="SingleCall" />
</service>
<channels>
<channel ref="tcp" port="33000"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
Collapse
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<client>
<wellknown
type="ONX.Cmn.MyService, ONXCmn"
url="tcp://localhost:33000/MyServiceUri" />
</client>
</application>
</system.runtime.remoting>
</configuration>
Collapse
SERVER:
In our sample the “id” is the unique id of each instance of MyService object that is
created on Server side. As you can see, we have as many instances created in SERVER
app as number of calls (e.g. 2 calls for myService1 and 1 call for myService2 – in sum we
got 3).
Also, according to timestamps you may conclude that MyService is created right with
“func#()” call.
To use this type of activation you should configure server with “<activated />”section,
not with well-known type:
Collapse
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<service>
<activated type="ONX.Cmn.MyService, ONXCmn" />
</service>
<channels>
<channel ref="tcp" port="33000"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
Collapse
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<client url="tcp://localhost:33000">
<activated type="ONX.Cmn.MyService, ONXCmn" />
</client>
</application>
</system.runtime.remoting>
</configuration>
Pay attention that “url” parameter is specified in “<client />” section with this type of
activation. There is no need for objectURI here as .NET Remoting will know what type
to use.
As for sample, locate “CAO” solution. I won’t present text of Client code as it is the
same as for 2 tests from above. The only change is configuration that controls the type of
activation. Now, we get
Collapse
SERVER:
In this sample MyService instances created on Server side right at the time that Client
application code hits “new MyService()” command. You can see some delay in creation
of myService1 (15 ms). This is because this was first call from our Client application to
Server. It required establishing physical network connection between our applications
and did all other hidden .NET Remoting handshakes. As for myService2 it was created
right at the same millisecond. Also, as you can see, each of our myService# on Client
side was served with corresponding MyService instance on Server side.
There are means to set lease time through configuration files (showed below) and
through code:
Collapse
using System;
using System.Runtime.Remoting.Lifetime;
...
LifetimeServices.LeaseTime = TimeSpan.FromMinutes(30);
LifetimeServices.RenewOnCallTime = TimeSpan.FromMinutes(30);
LifetimeServices.LeaseManagerPollTime = TimeSpan.FromMinutes(1);
Here is how it works. For each server object we can get CurrentLeaseTime time from
Lease helper. This CurrentLeaseTime is how much time left for object to live. There is
a .NET Remoting LeaseManager that wakes up periodically and checks every available
server object in Server application. With each check it reduces the CurrentLeaseTime for
each checked object. If object is expired then its reference is removed and that object is
marked for GC to be collected. Every time when remote call comes for server object, this
object’s CurrentLeaseTime is set to RenewOnCallTime time span.
Take a look at “Lease Time” solution. As you can see it uses Server Activation in
Singleton mode. It should make all Clients and all MyService objects in Clients’
application use the same instance of MyService that is on Server.
Collapse
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
...
<lifetime
leaseTime="5S"
renewOnCallTime="5S"
leaseManagerPollTime="1S" />
</application>
</system.runtime.remoting>
</configuration>
It makes .NET Remoting mark remoted object for garbage collection until 5 seconds
passed with no queries from any Client:
Collapse
1) Press ENTER to create Remote Service...
[5044] [2008/10/01 14:17:47.442] myService1 created. Proxy? YES
See “3)” in output. As you can see, we were waiting too long (e.g. >5 seconds) before
we invoked query 2-nd time. It made Server forget about MyService#4 and create new
one – MyService#5. After that in “5)” we invoked func1() within 2 seconds and it was
using MyService#5 as it was not expired yet on Server side.
Here we start our Client application again and press ENTER continuously with no
delays. As you can see, all three invokes use the same MyService instance:
Collapse
1) Press ENTER to create Remote Service...
[5380] [2008/10/01 14:30:39.355] myService1 created. Proxy? YES
We can also make our remoting object never expire. In order to do so we will need to
override one of the MarshalByRefObject methods and make it return “null”:
Collapse
public class MyService : MarshalByRefObject
{
...
If you add such override to LeaseTime solution, you will see that even though we waited
too long and have <lifetime> parameter specified in configuration – our MyService is not
expired and reused for all calls:
Collapse
2) Press ENTER to query 1-st time...
[3056] [2008/10/01 14:36:51.455] myService1.func1() returned
MyService#1.func1()
There is also “sponsoring” mechanism that allows customizing the lease time
according application needs. You can read “sponsors” topic in MSDN to get more
information on this.
6. Hide Implementation from Client. Remoting via
Interface exposure
It is not always a good idea to expose to the world the implementation of your
remoting object. This is due to security reasons and due to size of assembly that has
complex implementation. Also, implementation can use some other assemblies that you
would not want to deploy to client computers. In this case it is a good idea to split our
MyService class into:
At this point we can put out types into separate assemblies and deliver only small part
to client computer:
Then during delivery we need to put only small part of product on Client computers:
You may find “Hide Implementation from Client app” solution to see how it is
implemented. The idea is to request remote type by Uri and cast returned object to
interface. On a server side such Uri request will instantiate our real implementation that is
defined in ServerLib assembly.
Server configuration:
Collapse
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<service>
<wellknown
type="ONX.Server.MyService, ONXServerLib"
objectUri="MyServiceUri"
mode="Singleton" />
</service>
<channels>
<channel ref="tcp" port="33000"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
Collapse
IMyService myService1 =
Activator.GetObject(
typeof(IMyService),
"tcp://localhost:33000/MyServiceUri"
) as IMyService;
Note, that there is no way to use Client Activation Object if you decide to go with
hiding implementation behind interface. This is because you need to _instantiate_ object
of class in client side for Client Side activation. But you don’t have type information on
client side – only interface. So, you can do this only with well known type definition (e.g.
Server Activation Object).
Collapse
[Serializable]
public class MyContainer
{
private string str_;
private int num_;
Collapse
class MyClient
{
[STAThread]
static void Main(string[] args)
{
MyService myService = new MyService();
Log.Print("myService created. Proxy? {0}",
(RemotingServices.IsTransparentProxy(myService)?"YES":"NO"));
Collapse
[3660] [2008/10/03 10:05:27.970] func1() is invoked, got
MyContainer[str="From Client",num=555]
Collapse
[2696] [2008/10/03 10:05:27.892] myService created. Proxy? YES
[2696] [2008/10/03 10:05:27.970] myService.func1() returned
MyContainer[str="abc",num=123]
4) It must override
5) If your custom exception has some members, those should be taken care to write
and read to/from stream.
Collapse
[Serializable]
public class MyException : ApplicationException
{
private string additionalMessage_;
Collapse
public class MyService : MarshalByRefObject
{
public void func1()
{
throw new MyException("Main text for custom ex", "Additional text");
}
We simply try to throw both our custom exception and starndard one. Having such
Client implementation:
Collapse
class MyClient
{
[STAThread]
static void Main(string[] args)
{
RemotingConfiguration.Configure("ONXClient.exe.config");
try
{
myService.func1();
}
catch(MyException ex)
{
Log.Print("Caught MyException: message=\"{0}\", add.msg=\"{1}\"",
ex.Message, ex.AdditionalMessage);
}
try
{
myService.func2();
}
catch(Exception ex)
{
Log.Print("Caught Exception: message=\"{0}\"",
ex.Message);
}
Collapse
[15:09:39.380] Caught MyException: message="Main text for custom ex",
add.msg="Additional text"
[15:09:39.380] Caught Exception: message="Main text for standard ex"
If we comment out saving and restoring of additionalMessage field in MyException
class – after deserialization we will get default string value. So, no error will be generated
but not full state restoring. If we comment out [Serializable] attribute, we will get runtime
exception.
1) Server application should have runtime type information about type that holds
callback method.
2) This callback method should be public and cannot be static
3) To avoid Server to wait and make sure that callback got recipient, we have to
mark callback with [OneWay] attribute. It makes us unable to return some data neither
through “return” value nor through “ref” of “out” parameters.
4) As instance of this type will instantiated on Client side and will be used on Server
side, it should derive from MarshalByRefObject class.
Take a look at “Events” solution. All these limitations make us to introduce some even
sink and define it in Cmn assembly so it is available for both Server and Client
application:
Collapse
public class EventSink : MarshalByRefObject
{
public EventSink()
{
}
[System.Runtime.Remoting.Messaging.OneWay]
public void EventHandlerCallback(string text)
{
}
Collapse
public class EventSink : MarshalByRefObject
{
private OnEventHandler handler_;
[System.Runtime.Remoting.Messaging.OneWay]
public void EventHandlerCallback(string text)
{
if (handler_ != null)
{
handler_(text);
}
}
Also, since .NET Framwork v1.1 there are security restriction on deserialization of
some types. In order to override default setting we need to set filterLevel to “Full”. Here
is full Server config file:
Collapse
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<service>
<wellknown
type="ONX.Cmn.MyService, ONXCmn"
objectUri="MyServiceUri"
mode="Singleton" />
</service>
<channels>
<channel ref="tcp" port="33000">
<serverProviders>
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>
Collapse
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<client>
<wellknown
type="ONX.Cmn.MyService, ONXCmn"
url="tcp://localhost:33000/MyServiceUri" />
</client>
<channels>
<channel ref="tcp" port="0">
<clientProviders>
<formatter ref="binary" />
</clientProviders>
<serverProviders>
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>
It is also possible to configure this through code. See MSDN for details. Take a look at
MyService class now.
Collapse
public delegate void OnEventHandler(string message);
As you can see we invoke callback immediately when some Client called
“MyService.func()” and also we do it one more time from separate thread after 5 seconds
timeframe. It was done for testing purposes to show that events can be invoked at any
time (e.g. not even to answer on call invocation). We span a separate thread and return
control to Client that invoked “func1()”. And then, after 5 seconds our spanned thread
will raise event for all registered event handlers. Once Client registers its event handler -
it will get ALL events from Server.
Here is stripped code for our Client application. Full version is available in “Events”
solution:
Collapse
class MyClient
{
private MyService myService_;
private EventSink sink_;
public MyClient()
{
//create proxy to remote MyService
myService_ = new ONX.Cmn.MyService();
[STAThread]
static void Main(string[] args)
{
RemotingConfiguration.Configure("ONXClient.exe.config");
Collapse
[5412] [09:43:55] myService.func1() returned MyService.func1()
[5412] [09:43:55] Press ENTER to exit...
[7724] [09:43:55] Got … callback! Event from Server: func1() is invoked
[7724] [09:44:00] Got … callback! 5 seconds passed from one of method
calls
As you can see we got initial event right after call to “func1()” and then one more after
5 seconds. Pay attention that callback functions were invoked on a separate thread. So, if
you need to synchronize some data access, beware.
Collapse
public class MyService : MarshalByRefObject
{
public string func1(string text)
{
Log.Print("func1(\"{0}\") is invoked", text);
return text+DateTime.Now.ToString("HH:mm:ss.fff");
}
}
Collapse
delegate string GetStringHandler(string arg);
class MyClient
{
private const int NUMBER_OF_INVOCATIONS = 5;
[STAThread]
static void Main(string[] args)
{
RemotingConfiguration.Configure("ONXClient.exe.config");
for(int index=1;index<=NUMBER_OF_INVOCATIONS;++index)
{
Log.Print("Invoking myService.func1() #{0}...", index);
GetStringHandler handler = new GetStringHandler(myService.func1);
handler.BeginInvoke("from Client", new
AsyncCallback(OnCallEnded), index);
}
As you can see we loop 5 times in “for”. With every iteration we create delegate that
corresponds to prototype of “MyService.func1” method and make asynchronous call with
“BeginInvoke(…)”. As we passed our “OnCallEnded” method as callback, when
asynchronous invocation is done, we get control in our OnCallEnded method. There we
get reference to our delegate and get result by calling “EndInvoke(ar)”.
Collapse
[0216] [2008/10/03 16:39:45.243] myService created. Proxy? YES
[0216] [2008/10/03 16:39:45.243] Invoking myService.func1() #1...
[0216] [2008/10/03 16:39:45.274] Invoking myService.func1() #2...
[0216] [2008/10/03 16:39:45.274] Invoking myService.func1() #3...
[0216] [2008/10/03 16:39:45.290] Invoking myService.func1() #4...
[0216] [2008/10/03 16:39:45.290] Invoking myService.func1() #5...
We were even lucky to get our 5 calls in order, that is different from original – call #2
ends earlier than call #1. The same about calls #4 and #5.
Also notice, that not all calls were running in the same thread. And all of them are
different from thread where we initiated our 5 calls.
Collapse
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<service>
<wellknown
type="ONX.Cmn.MyService1, ONXCmn"
objectUri="MyService1Uri"
mode="SingleCall" />
<wellknown
type="ONX.Cmn.MyService2, ONXCmn"
objectUri="MyService2Uri"
mode="SingleCall" />
</service>
<channels>
<channel ref="tcp" port="33000"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
You cannot:
1) Have several channels with the same protocol (e.g. “ref” parameter). Otherwise
you will get exception that such protocol is already registered. But you can specify
several channels if they are for different protocols
2) Each known type should have unique objectUri. Otherwise definition of types
will be overlapped and only on of types will be available
Collapse
[7496] [2008/10/05 00:01:04.047] ALL REGISTERED TYPES IN REMOTING -
(BEGIN)---------
[7496] [2008/10/05 00:01:04.047] WellKnownServiceTypeEntry:
type='ONX.Cmn.MyService2, ONXCmn'; objectUri=MyService2Uri;
mode=SingleCall
[7496] [2008/10/05 00:01:04.047] WellKnownServiceTypeEntry:
type='ONX.Cmn.MyService1, ONXCmn'; objectUri=MyService1Uri;
mode=SingleCall
[7496] [2008/10/05 00:01:04.047] ALL REGISTERED TYPES IN REMOTING -
(END) ---------
Collapse
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<client>
<wellknown
type="ONX.Cmn.MyService1, ONXCmn"
url="tcp://localhost:33000/MyService1Uri" />
<wellknown
type="ONX.Cmn.MyService2, ONXCmn"
url="tcp://localhost:33000/MyService2Uri" />
</client>
</application>
</system.runtime.remoting>
</configuration>
If we would want to use Services from different Servers, each Server would listen on
different port. So, there would be different port in each “<wellknown/>” section.
There is “Two Services in single Server” solution if you would like to try it for
yourself.
12. Summary
Thank you for your time. I hope it was spent with use. Any comments are welcome. I
will try to adjust this article as soon as I have some comments and time. Happy remoting!
License
This article, along with any associated source code and files, is licensed under The Code
Project Open License (CPOL)