As much as I'd like to jump straight into the typical "Hello World" example of .NET Remoting, you need to make a few fundamental choices before you even start to implement an architecture that incorporates .NET Remoting. These choices concern the type of activation, the object lifetime, and the message format and network transport used for communication. Activation Modes.NET Remoting supports three types of objects:
The inevitable question is which activation type represents best design practices? SingleCall objects are usually ideal because they have a very low overhead. They don't consume any server resources when they aren't working, and they can easily be hosted on multiple servers with clustering because of their stateless design. They also match the "service provider" design pattern introduced in Chapter 2. Client-activated objects, on the other hand, are often easier to program with because they resemble ordinary objects. The problem is that this false sense of familiarity can lead developers to use them in disastrous ways. Because client-activated objects retain state, for example, you might be tempted to use property procedures, member variables, and other object-oriented practices to simplify coding. Because of the nontrivial overhead required to make a network call, however, setting and retrieving properties individually can greatly slow down your application. Singleton objects are probably the most difficult to manage properly. Because multiple clients can call methods at the same time, there is the potential for concurrency errors subtle but serious problems that occur when data is modified simultaneously. Unfortunately, concurrency errors often don't appear in development testing and are difficult to repeat and diagnose. In addition, because of their state, singleton objects can't be used in load-balancing scenarios with multiple servers. Generally, you shouldn't use a singleton object unless you need to. In some peer-to-peer scenarios in which components need to share synchronized information, for instance, singleton objects are extremely attractive. Make sure, however, that you polish your threading skills by reading Chapter 6 and Chapter 7. Object LifetimeSingleton and client-activated objects have state, but they can't be allowed to live forever. Consider what would happen if a client were to forget to release the object or just disappear from view due to network problems. In these cases, the server-side objects they created would linger on in a useless zombie state, wasting valuable server resources. Multiplied by hundreds of clients over the period of several days, the server might be choked to a standstill by memory that was never properly released. Clearly, a distributed component technology needs to take this danger into consideration. In .NET, this problem is dealt with using a lifetime leasing scheme. Essentially it works like this: An object is allowed to exist only for a fixed amount of time. If it needs to last longer, someone needs to renew its lease. This "someone" can be a dedicated lease sponsor, the client component, or the remote component itself. To ensure long-time stability and scalability of your system, however, you must implement a realistic, conservative leasing policy (as discussed a little later in this chapter). Note Lifetime leasing is a dramatic departure from DCOM, which uses keep-alive pinging (euphemistically known as distributed garbage collection) to manage object lifetime. Essentially, as soon as the pinging stops, the server assumes that the client had been disconnected and releases the object. The serious drawback to this technique is that the pinging is inherently unscalable in a large distributed system. Having hundreds of clients pinging remote components at the same time can significantly reduce network bandwidth and performance. Server and Client ActivationTo further complicate matters, singleton and SingleCall objects are both categorized as server-activated objects. The terms server-activated object (SAO) and client-activated object (CAO) turn up in the MSDN documentation and various publications. They cause a great deal of confusion. For example, it's sometimes mistakenly said that lifetime leases apply only to client-activated objects, not server-activated objects. This just isn't true. Lifetime leases apply to both client-activated and singleton objects. The real distinction between client-activated and server-activated objects is that a server-activated object isn't really created when a client instantiates it. Instead, it is created as needed. If the object is a SingleCall object or a singleton that doesn't exist yet, it's created with the first method call. Client-activated objects, on the other hand, are created when the client instantiates them. Why is this distinction important? The difference is minor, except for one detail: client-activated objects can use parameterized constructors. Server-activated objects can be created only with the default (no parameter) constructor because they're not created directly by the client they're created indirectly by the server. CommunicationThe final decision you need to make when implementing a remote component is the communication format and transport to use. Although .NET uses a pluggable format that allows any type of formatter or channel, by default the .NET Framework ships with two formatters and two channels. These prebuilt choices serve most needs. You can choose from two formatters:
There are also two channels:
When defining a channel, you also need to choose a port number for the communication. Essentially, to communicate with a remote object you need the machine name (or IP address) and a port number (from 0 through 65,535) that maps to a network service and specific application. Choosing the correct port in a production environment might require some consultation with a network or firewall administrator in your organization. However, there are several rules of thumb:
I've actually simplified the issue slightly. When creating a connection, you have the choice of creating dedicated client and server channels, or bidirectional channels. With a dedicated client-server channel, the client invokes methods and the server returns information. However, the server can't invoke a method of the client object or raise an event to the client. You can see all the prebuilt channels in the machine.config file (which is stored in the directory C:\[WinDir]\Microsoft.NET\Framework\[Version]\CONFIG). Each channel is given a friendly id attribute (which is the name you use in your configuration files) and more specific information about the underlying class, assembly, and version, as shown in Listing 4-4. Listing 4-4 Channel templates in the machine.config file<channels> <channel type="System.Runtime.Remoting.Channels.Http.HttpChannel, System.Runtime.Remoting, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <channel type="System.Runtime.Remoting.Channels.Http.HttpClientChannel, System.Runtime.Remoting, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <channel type="System.Runtime.Remoting.Channels.Http.HttpServerChannel, System.Runtime.Remoting, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <channel type="System.Runtime.Remoting.Channels.Tcp.TcpChannel, System.Runtime.Remoting, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <channel type="System.Runtime.Remoting.Channels.Tcp.TcpClientChannel, System.Runtime.Remoting, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <channel type="System.Runtime.Remoting.Channels.Tcp.TcpServerChannel, System.Runtime.Remoting, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> </channels> If you want to, you can add your own defaults to the machine.config file. Doing so enables you, for example, to register a channel that always uses a certain port. However, these changes must then be applied to the machine.config file in every user's computer, which can be a tedious (and even risky) process. Note The .NET Framework also provides lower-level classes that enable you to create socket-based connections or send information over HTTP, FTP, or UDP manually. (See, for example, the System.Net and System.Net.Sockets namespaces.) These classes allow a great deal of flexibility and are probably required if you need optimum performance in a complicated peer-to-peer application (such as a multiplayer networked computer game). However, it's much less common to develop enterprise applications with these classes, both because it requires a good deal more code and because it can easily introduce unexpected vulnerabilities or unnoticed errors. It's also much less likely if you need to interoperate with components hosted on other platforms, languages, or operating systems. When designing distributed applications, you'll generally want to use the higher-level .NET Remoting Framework (or XML Web services) and concentrate on optimizing the database and network architecture instead of reinventing the wheel with custom networking code. |