Every .NET program consists of one or more threads inside an application domain. Application domains are isolated logical processes that can't communicate directly. To bridge the gap between more than one application domain, you can use .NET Remoting.
Remoting is often described as the way that programs communicate with each other in .NET. This description is accurate, but it ignores the fact that there are literally dozens of different ways for applications to communicate on any platform. Some of the options for inter-process communication include
Serializing information to a data store that both applications can access (such as a database or a file)
Sending a custom message to a Microsoft Message Queuing queue
Calling an ASP.NET web service with a SOAP message
Creating a connection by directly using .NET's networking support, which provides classes that wrap Transmission Control Protocol (TCP) and User Datagram Protocol (UDP) channels, or raw sockets
Using an operating system service such as named pipes or COM/DCOM (or see Microsoft Knowledge Base Article Q95900 for some other legacy choices)
All of these approaches have dramatically different characteristics, and different niches in the programming world. For example, the first choice (serializing information to a data store) is never workable if you need to provide instantaneous communication. In order to receive new messages regularly, multiple applications would need to poll the data source continuously, thereby creating an unbearable burden on your system. I've seen examples of chat applications that rely on this sort of continuous polling and, as a result, cannot scale beyond a small set of users without crippling the server.
On the other hand, the second option (using a message queue) is extremely scalable because it uses a disconnected message-based architecture. Every machine has its own queue that it monitors for received messages. To send a message, you simply need to know the queue name of the recipient. However, this approach is rarely used for peer-to-peer applications because it ties each client to a specific machine (the one on which the queue exists). It also requires aWindows PC with Microsoft Message Queuing installed and properly configured. Finally, message queuing only allows one-way "fire-and-forget" communication. To respond to a message, a new message must be created and sent to the original sender's queue. This means that a complex interaction (such as querying a computer for a list of files and initiating a download) could require several back-and-forth messages, thereby increasing the complexity and possibility for error. As a result, it's much more likely to find message queuing at work in the enterprise world (for example, as the backbone of an internal order processing system).
The third option, web services, excels at no-nonsense cross-platform communication. Unfortunately, it's too feature-limited for a peer-to-peer application. The problem is that web services are essentially a client-server technology. To use a web service, a client contacts the web server, makes a request, and waits for a response. There's no way for the server to contact the client at a later time, and there's no way for multiple clients to interact (unless they too are configured as web servers running ASP.NET and providing their own web services). Web services are the ticket when you wish to provide server-side functionality to all kinds of clients. They aren't any help if you want to build a system of equal peers that work together.
The final two options suggest some more useful ways to create a peer-to-peer application. Direct networking in .NET is an important technique, and you'll look into it in the third part of this book. However, direct networking can be complicated, and it will dramatically inflate the amount of code you need to write. To simplify your life, you can make use of one of the higher-level abstractions provided by Microsoft. In the past, this was the quirky technology of COM/DCOM. Today, DCOM is replaced by a newer and more flexible standard: .NET Remoting.
Remoting is a generic method of inter-process and remote communication in.NET. It allows applications in different processes and different computers to communicate seamlessly. Like DCOM, Remoting is designed to let you use the objects in another application in the same way that you use local objects. The heavy lifting takes place behind the scenes and requires little programmer intervention.
The real strength of Remoting, however, is the fact that it abstracts the way you use remote objects from the way you communicate with them. When you use Remoting, you have the choice of different activation types, transport protocols, serialization formats, and object-lifetime policies. You can change these options with a few lines of code or a configuration file, but the code for using the remote object remains unchanged, and your application stays blissfully unaware of how the communication takes place.
Remoting is a boon for any sort of distributed application developer. Some of its advantages include the following:
Remoting can be used with different protocols and even in cross-platform projects. Because Remoting supports the ability to send SOAP-formatted messages, you can bring a Java client into the mix, although it won't be quite as easy as it is with web services.
Remoting handles state management and object lifetime, ensuring that objects time out when the client isn't using them (thereby preventing potential memory leaks).
Remoting is extensible. You can create building blocks for other transport channels or formats that will plug in to the Remoting infrastructure.
Remoting is scalable. Remote requests are handled by a pool of listener threads provided by the CLR. If too many concurrent requests are sent to the same object, the excess requests will be politely queued and may time out without damaging the performance of the overall system.
When used in conjunction with Internet Information Server (IIS), Remoting allows you to use Secure Sockets Layer (SSL) security to encrypt messages.
Even though Remoting hides some infrastructure details, it's inherently trickier than programming a local application. You'll need to perform extra work to make events, custom structures, and object creation work the way you expect with remote objects. You'll also need to accept some significant trade-offs.
Remoting supports several types of objects, including client-activated, SingleCall, and Singleton objects. For reasons you'll discover shortly, a peer-to-peer application requires Singleton objects. Singleton objects are, generally speaking, the most complex types of objects because they need to deal with the reality that multiple clients may use them at once. (In other words, a single computer in your peer-to-peer system may be simultaneously contacted by several peers, each of which will call methods on the same object.) In order to handle this possibility, you'll need to introduce threading code at some point, as explained in Chapter 5.
Programming with Remoting means that you're programming at a higher level than with raw sockets and channels. Although this means you're insulated from a number of costly errors, it can also restrict some of the things that you can do. Here are a few examples:
Remoting imposes some rules about how objects are exposed. For example, you can't tie objects in the same application domain to separate channels.
Remoting is inextricably tied up with objects. Clients interact with a remotable object by calling any of its public methods. Typically, this means you need to create a dedicated Remoting "front end" for any application that requires remote communication.
Remoting is not designed for on-the-fly configuration. Although it's possible to create an application that dynamically unregisters Remoting channels and creates new ones, you would need to do more work to implement it. Usually, Remoting applications are designed with the assumption that it's acceptable to restart the hosting application if the configuration information changes.
Remoting sends objects in all-or-nothing chunks. If you need to stream large files across the network, this may not be the best approach. It's for this reason that we'll use a different approach in the third part of this book to build a file-sharing application.
You have no control of the thread pool used to handle Remoting requests. That means you can't fine-tune details such as the number of maximum requests.
For the most part, Remoting is a perfect compromise between flexibility and safety. For example, the fact that you can't configure how the CLR allocates its thread pool is usually a benefit. The CLR handles requests very efficiently, and by performing its work automatically, it ensures that you won't unwittingly choose an ill-suited setting that would harm the scalability of your system. (It's for a similar reason that you can't configure how frequently the garbage collector runs or how much memory is initially allocated to an application.)
The next two chapters will discuss some of these issues in more detail as they develop a messaging application using Remoting. The remainder of this chapter introduces the basics of the Remoting infrastructure.
In this chapter, you'll look at Remoting in terms of objects, and consider how these objects interact across application domain boundaries. We won't consider how remotable objects are used for communication in a peer-to-peer application yet—those design decisions will be considered in the next chapter.