|
Remoting Channels
Now that you have an understanding of how to set up and use .NET remoting, we’ll discuss choosing an appropriate remoting channel for your applications needs. Two remoting channels are currently available in the .NET Framework, HTTP and TCP. Take note that the new version of the .NET Framework is expected to include an interprocess communication (IPC) channel that will allow communication between application domains in different processes on the same computer in the most efficient manner possible.
HTTP Channel
The HTTP channel is designed to communicate over the HTTP protocol. The channel features both a client side and a server side to host HTTP-based remoting communication across application domains. The client side of the HTTP channel is also designed to work with Microsoft Internet Information Server (IIS). IIS can substitute for the server side of the HTTP channel. Using IIS as the server allows the HTTP channel to be more secure by using authentication and more scalable where IIS can more efficiently service thousands of HTTP requests per second. The HTTP channel provides two major features for hosting remoting communications, interoperability and security.
Interoperability
The HTTP channel by default uses SOAP serialization to encode class types for data transmission. Chapter 4 described SOAP serialization in more detail. The advantage of using SOAP is that the other end of communication can be practically any platform or device as long as it understands the SOAP format and understands how to respond to remoting messages accordingly. This makes communications portable. On the downside, however, SOAP requires more data bytes to be transmitted from the client and the server than other serialization formatters. If you do not want to use SOAP as the HTTP channel’s default formatter, you can specify another formatter, such as binary serialization, using a remoting configuration file as described earlier. Another major advantage of using the HTTP channel is that HTTP communications are usually allowed to pass across firewalls. As a result, you can build distributed applications that can run on both sides of a firewall without special protocol configurations. HTTP communication is sometimes by proxy, which means there is a server that makes HTTP requests on your behalf. Web browsers such as Microsoft Internet Explorer allow you to configure proxy settings that enable the browser to reach the Internet from an intranet environment. The HTTP channel also allows you to configure proxy settings through a remoting configuration file. |
An advantage of using the HTTP channel is that you can have IIS host the server side of the remoting communications channel. Using IIS to host remoting allows you to authenticate the user attempting to access a remoting class. IIS handles authentication security by allowing you to lock down the published URI that a client attempts to access. For example, we could expose a well-known object type URI of the DemoClass type described earlier and require that the client use Kerberos authentication to access the class. The authentication capability is only available when running IIS hosting. If you decide to provide direct HTTP channel hosting, then you will lose the authentication security functionality.
The HTTP channel features several channel properties that can control the behavior of both the HTTP server and the client side of the channel. Table 12-1 outlines the available properties that can be specifically configured on the client and the server.
Table 12-1: HTTP Channel Properties
|
||
Property |
Availability |
Description |
allowAutoRedirect |
Client |
A Boolean value that determines whether the client will handle HTTP redirects. |
bindTo |
Server |
A string representing an IP address for the server to bind to. |
clientConnectionLimit |
Client |
An integer defining how many concurrent connections can be opened to the server. |
connectionGroupName |
Client |
A string representing a group name if the unsafeAuthenticatedConnectionSharing property is set. |
credentials |
Client |
An ICredentials instance that allows a client to pass credentials for password- based authentication schemes such as Kerberos authentication. |
exclusiveAddressUse |
Server |
A Boolean value that forces the server to use the listening IP address and port exclusively so that another application cannot steal the listening TCP port. |
listen |
Server |
A Boolean value that determines whether to allow activation to hook into the outside listener service. |
machineName |
Client and server |
A string representing the machine name of the listening server. |
name |
Client and server |
A string representing the name of the channel. |
port |
Server |
An integer value that specifies what TCP port the server will listen on or what port the client will send TCP packets from. If the value 0 is selected, the infrastructure will choose a port automatically. |
priority |
Client and server |
An integer value that defines the priority of the channel when multiple channels are available. |
proxyName |
Client |
A string identifying a proxy if the client communication must pass through a proxy. |
proxyPort |
Client |
An integer identifying the port on which a proxy is listening for communication from a client. |
suppressChannelData |
Server |
A Boolean value that prevents the server channel from returning channel properties. |
timeout |
Client |
An integer that specifies how long the client channel will wait for a server response. |
unsafeAuthenticatedConnectionSharing |
Client |
A Boolean value that indicates the client will supply credentials and a group name for the connection. |
useAuthenticatedConnectionSharing |
Client |
A Boolean value that tells the server to reuse a connection from an authenticated user. |
useIpAddress |
Server |
A Boolean value that forces the channel to use a specific IP address instead of using the machineName property. |
In general, channel properties can be configured in an XML configuration file or they can be configured programmatically using a channel constructor when the channel is created. Values specified in a configuration file are represented as strings while values specified programmatically are configured with native data types.
For example, if you needed to configure proxy settings for an HTTP client channel to pass communications through a proxy server, you could do so by setting the proxyName and proxyPort parameters programmatically on the creation of the client side of the HTTP channel. You also could specify values in an XML client configuration file. The following XML configuration demonstrates how to configure proxy settings for the client side of an HTTP channel to remotely access the class Demo.DemoClass.
<configuration> <system.runtime.remoting> <application name="DemoClient"> <client url="http://localhost:80"> <activated type="Demo.DemoClass, Demo" /> </client> <channels> <channel ref="http" proxyName="MyCorporateProxy" proxyPort="8080" /> </channels> </application> </system.runtime.remoting> </configuration> |
The TCP channel is a remoting channel that communicates directly over TCP. One of its advantages is that it handles remoting communications in the most efficient manner possible. On the downside, however, the channel does not offer security mechanisms to protect remoted resources.
By default, the TCP channel uses binary serialization to format messages to streams. In Chapter 4 you learned that the advantage of binary serialization is that the resulting serialized stream is compact, whereas the disadvantage is that a binary stream is not very portable. If the computers on both ends of a remoting channel have the same operating system, then using the TCP channel with binary serialization is the way to go. However, if your application needs to interoperate with a different type of computer, you can override the default binary formatter with the SOAP formatter.
A major disadvantage of the TCP channel is that it does not offer any security authentication solutions to protect the server from malicious clients; anyone can connect to the server and invoke remoted classes. This can be hazardous if you run a TCP channel server on the Internet and expose classes that can perform hazardous operations on your computer, such as adding or removing files.
The new version of the .NET Framework is expected to offer an authentication mechanism in the TCP channel. This should enable you to safely deploy TCP channel remoting solutions on the Internet.
The TCP channel features several channel properties that can control the behavior of both the server and the client side of the channel. Table 12-2 outlines the available properties that can be specifically configured on the client and the server.
Table 12-2: TCP Channel Properties
|
||
Property |
Availability |
Description |
bindTo |
Server |
A string representing an IP address for the server to bind to. |
exclusiveAddressUse |
Server |
A Boolean value that forces the server to use the listening IP address and port exclusively so that another application cannot steal the listening TCP port. |
machineName |
Client and server |
A string representing the machine name of the listening server. |
Name |
Client and server |
A string representing the name of the |
Port |
Server |
An integer value that specifies what TCP port the server will listen on or what port the client will send TCP packets from. |
Priority |
Client and server |
An integer value that defines the priority of the channel when multiple channels are available. A larger value indicates greater priority. |
rejectRemoteRequests |
Server |
A Boolean value that determines whether the server will only accept requests from the local host. |
suppressChannelData |
Server |
A Boolean value that prevents the server channel from returning channel properties. |
useIpAddress |
Server |
A Boolean value that forces the channel to use a specific IP address rather than the machineName property. |
The .NET Framework remoting architecture provides substantial extensibility that allows you to develop a custom channel. Developing a custom channel requires building a class that features both the sender side and the receiver side of a remoting channel. The work required for a custom channel is not trivial, but with a little perseverance the task is not too daunting.
Figure 12-2 shows a custom remoting channel. As with the built-in channels, a custom channel has two sides, client side and server side. The client side is responsible for sending out formatted (or serialized) messages when an application attempts to access a remoted class. The server side, on the other hand, is responsible for receiving the serialized client request that in turn will deserialize the message and access the remoted class. Once the remoted class has been accessed, a serialized response message is returned to the client indicating the status of accessing the remoted class. Chapter 4 examined the serialization process in greater detail.
Figure 12-2: Remoting channel overview
The first step in developing a custom channel is to develop a class that implements the IChannelSender and IChannelReceiver interface classes. Inheriting these classes requires you to implement the abstract methods and properties of both interfaces and also the IChannel interface. Figure 12-3 outlines the methods and properties that must be implemented in the channel class. The IChannelSender interface represents the client (or sender) side of the channel, while IChannelReceiver represents the server (or receiver side). The basic idea is to have the channel class handle both sides of communication when .NET remoting communicates across application domains.
Figure 12-3: Abstract methods and properties required for implementing a custom channel
In addition to the abstract methods and properties that must be implemented, you also will need to provide several overloaded constructor methods that will respond to channel-creation activities. You should supply at least one constructor for handling client and server creation of the channel from a configuration file, as described earlier in this chapter. The following C# code demonstrates how to create multiple constructors for creating the server and client side of a User Datagram Protocol (UDP) custom channel.
private int m_ChannelPriority = 1; private string m_ChannelName = "udp"; private int m_ChannelPort = 5150; // default port
public UDPChannel() { SetupClientSinkProviders(null); }
// This constructor is used by a server application to // programmatically configure the server side of the remoting channel. public UDPChannel(int Port) : this() { m_ChannelPort = Port; SetupServerSinkProviders(null); }
// This constructor is used by the .NET remoting // infrastructure to configure the channel via a configuration file. public UDPChannel( IDictionary Properties, IClientChannelSinkProvider ClientProviderChain, IServerChannelSinkProvider ServerProviderChain ) { if (Properties != null) { foreach (DictionaryEntry entry in Properties) { switch ((string) entry.Key) { case "name": m_ChannelName = (string) entry.Value; break;
case "priority": m_ChannelPriority =Convert.ToInt32(entry.Value); break;
case "port": m_ChannelPort = Convert.ToInt32(entry.Value); break; } } }
SetupClientSinkProviders(ClientProviderChain); SetupServerSinkProviders(ServerProviderChain); } |
Since both sender and receiver functionality is handled in the main channel class, we recommend separating the client and server portions of the channel into two separate helper classes that work with channel sink providers. In the preceding constructors’ code, we demonstrated the idea of separating the client and server functionality by calling out SetupClientSinkProviders and SetupServerSinkProviders methods. These methods are responsible for setting up the channel sink providers needed on both sides of the channel to handle the sending and receiving of messages. Client and server channel sink providers are discussed in more detail later in this chapter.
As we discussed earlier, when a custom channel class inherits the IChannelSender and IChannelReceiver interface classes, the channel must provide implementation for the IChannel, IChannelReceiver, and IChannelSender abstract methods and properties, as shown in Figure 12-3. The following C# code demonstrates how to implement IChannel for a custom UDP channel.
public string ChannelName { get { return m_ChannelName; } }
public int ChannelPriority { get { return m_ChannelPriority; } }
public string Parse(string Url, out string ObjectUri) { ObjectUri = null; string ChannelUri = null;
try { System.Uri ParsedURI = new System.Uri(Url);
ChannelUri = ParsedURI.Authority; ObjectUri = ParsedURI.AbsolutePath; } catch(Exception) { ObjectUri = null; ChannelUri = null; }
return ChannelUri; } |
The ChannelName and ChannelPriority properties simply return the name and the priority for a channel. A channel name identifies the name of a channel to the remoting infrastructure and is useful when a client attempts to activate a remote class and specifies a channel URI using ActivatedClientTypeEntry, as shown earlier. For example, a channel name for a UDP channel can be udp. The ChannelPriority helps the remoting system decide what channel to pick if a client and server have multiple channels registered on the client and server. The channel with the highest number will be picked first.
The Parse method is responsible for returning a channel URI and an object URI from an input URL. A channel URI is the authority component part of a URI that identifies the connection information for the server of the channel. The object URI identifies the path component part of a URI and identifies a remote class instance on the server. For example, given the URL udp: //www.microsoft.com:5150/RemoteActivationService.rem, the channel URI would be /www.microsoft.com:5150 and the object URI would be /RemoteActivation Service.rem. See Chapter 5 to review URIs in more detail.
IChannelReceiver is the next interface that must be implemented. The IChannelReceiver interface is the starting point for running the server side of the channel and requires that you implement the ChannelData property and the StartListening, StopListening and GetUrlsForUri abstract methods. The goal of the interface is to start a listening server that handles requests from a client. When the server receives a request, the request is handed to the server provider’s sink chain that is set up at the channel’s initialization. Once the request is serviced by the remoting infrastructure on the server side, a response message can be generated and the server is responsible for sending the response message back to the client. The following C# code sample shows how to set up IChannelReceiver for a UDP custom channel.
private ChannelDataStore m_ChannelDataStore; private Thread m_ServerThread = null;
public object ChannelData { get { return m_ChannelDataStore; } }
public void StartListening(object Data) { m_ServerThread = new Thread(new ThreadStart(RunServer)); m_ServerThread.IsBackground = true; m_ServerThread.Start(); }
public void StopListening(object Data) { ServerChannel.StopServer(); if (m_ServerThread != null) { m_ServerThread.Abort(); m_ServerThread = null; } }
public string[ ] GetUrlsForUri(string ObjectUri) { string[ ] UrlArray = new string[1];
if (!ObjectUri.StartsWith("/")) ObjectUri = "/" + ObjectUri;
string MachineName = Dns.GetHostName();
UrlArray[0] = m_ChannelName + "://" + MachineName + ":" + m_ChannelPort + ObjectUri;
return UrlArray; } |
The ChannelData property is responsible for returning channel-specific data to the remoting infrastructure, such as the channel’s URI. The StartListening and StopListening methods are responsible for starting and stopping the server channel from listening for client request messages. A server-side remoting application can freely start and stop the listening server by calling these methods. We’ll describe how to implement a listening server later in this chapter. The GetUrlsForUri method takes a URI and returns an array of URLs that specifically represent the URI. See Chapter 5 for a review of how to derive URLs from a URI.
To complete the abstract methods and properties needed for a remoting channel, you’ll have to implement the IChannelSender interface. IChannelSender only requires you to implement one method named CreateMessageSink. CreateMessageSink prepares the client side of the remoting infrastructure to communicate with the server side using a client provider sink chain set up during initialization of the client channel described earlier. The method should call CreateSink from the chain of client sink providers to set up communication to a remoting server for accessing a remote class. The method determines how to reach a listening server using either a passed-in URL or channel-specific connection information returned from the server side of the channel and passed in from RemoteChannelData. The following C# code describes how to develop IChannelSender.
public IMessageSink CreateMessageSink(string Url, object RemoteChannelData, out string ObjectUri) { // Set the out parameters ObjectUri = null; string ChannelUri = null;
if (Url != null) { ChannelUri = Parse(Url, out ObjectUri); } else { if (RemoteChannelData != null) { IChannelDataStore DataStore = RemoteChannelData as IChannelDataStore; if (DataStore != null) { ChannelUri = Parse(DataStore.ChannelUris[0], out ObjectUri);
if (ChannelUri != null) Url = DataStore.ChannelUris[0]; } } }
if (ChannelUri != null) { if (Url == null) Url = ChannelUri;
// Return the first sink of the newly formed sink chain return (IMessageSink) m_ClientSinkProvidersChain.CreateSink(this, Url, RemoteChannelData); } return null; } |
In the preceding sections we presented an overview for the methods that are necessary to set up the server side of a remoting channel that receives communications from a client channel by starting a server application. We also discussed how to implement CreateMessageSink, which is used by the client side remoting infrastructure to establish communication to a remoting server to access a remote class. The methods described thus far depend on underlying methods that actually handle the client and server remoting communications. The next two sections describe how to implement the client and server sides of a custom channel.