NET P2P: Writing Peer-to-Peer Networked Apps with the Microsoft .NET Framework
This article assumes you’re familiar with HTTP and .NET
Level of Difficulty 1 2 3
Download the code for this article: NetPeers.exe(66KB)
Browse the code for this article at Code Center: ShareBaby
SUMMARY Peer-to-peer applications such as Napster, Gnutella, and Scour that communicate as peers sharing and receiving information are becoming commonplace as a means for users connected on large networks to take advantage of the vast resources available to them. The Microsoft .NET Framework provides a rich platform for building P2P apps.
This article explains the concepts that make up peer-to-peer applications. The peer-to-peer application model, discovering other peers, and querying peers for information are discussed. The article goes on to cover the System.Net namespace for the use of Internet protocols, the System.Web.Services namespace for exposing Web Services, and firewall and port issues. Finally, the role of the .NET Framework in simplifying the design of powerful peer-to-peer applications is outlined.
he Microsoft® .NET Framework SDK is a common framework of classes designed to provide a rich platform for building applications. In this article, I'll explain how this framework can be used to create peer-to-peer (P2P) applications and discuss the design decisions that are important to consider when writing a peer-to-peer application. I'll look at the .NET Framework support for the particular features listed in Figure 1, and discuss how they can help you.
What is a Peer-to-peer Application?
Communication is a key element when writing nearly any type of application. An application gains value when it becomes distributed and interacts with other resources available to it on the Internet or intranet. The most common model for communication over the Internet today is client/server, where there is a client that knows how to request information and post information to a server, and the server knows how to respond to requests from the client. A browser talking to a Web server is a common example of this model. Many browsers can send requests to the Web server, and the server does its job by listening for those requests and responding back to each of the browsers requesting or sending information (usually in the form of Web pages). In this model, the Web server cannot arbitrarily contact the browser. The "conversation" is always initiated by the client.
A peer-to-peer application is different from the traditional client/server model because the applications involved act as both clients and servers. That is to say, while they are able to request information from other servers, they also have the ability to act as a server and respond to requests for information from other clients at the same time. This approach increases the amount of value that each node on the network can add because it not only takes information from a source, but it also has the ability to share that information with other sources. A typical peer-to-peer application has the following key features that help define it:
Discovering other peers The application must be able to find other applications that are willing to share information. Historically, the application finds these peers by registering with a central server that maintains a list of all applications currently willing to share and giving that list to any new applications as they connect to the network. However, there are other means available, such as network broadcasting or discovery algorithms.
Querying peers for content Once these peers have been discovered, the application can ask them for the content that is desired by the application. Content requests often come from users, but it is highly conceivable that the peer-to-peer application is running on its own and performing its query as a result of some other network request that came to it rather than a specific request made by a user at that machine.
Sharing content with other peers In the same way that the peer can ask others for content, it can also share content after it has been discovered.
There are a number of design options to consider when designing a peer-to-peer application using the .NET Framework. The decisions you make about the architecture for your peer-to-peer application will have a significant impact on the type of features that your application is able to offer and, therefore, the experience that your users will have when using your app. The range of applications in this area can be thought of as a continuum from what I'll call pure peer-to-peer to client/server. In the next sections, I'll take a look at a few of the choices.
A pure peer-to-peer application has no central server whatsoever, as you can see in Figure 2. It dynamically discovers other peers on the network and interacts with each of them for sending and receiving content. The strength of this type of application is that it does not rely on any one server to be available for registration of its location in order for other peers to find it. At the same time, the lack of a central discovery server poses a problem because a relatively low number of clients can be discovered, thereby limiting the application's reach. In this scenario, a peer can either use information from a local configuration scheme to discover the clients (for example, a configuration entry that tells it who to talk with) or it can employ network broadcasting and discovery techniques such as IP multicast to discover the other peers.
Figure 2 A Pure P2P
Using IP multicast can be problematic since it is not widely deployed on the Internet, but it can be useful in intranet scenarios where the network is more controlled and infrastructure required for multicast is known to exist. Pure peer-to-peer is also being deployed on the Internet in cases where non-multicast schemes are used to discover the peers. In this case, the applications use some other scheme such as a well-known node approach, where each peer knows about at least one other peer and they share this knowledge with other peers to form a loosely connected mass of nodes.
Peer-to-peer with a Simple Discovery Server
This architecture, shown in Figure 3, works just like the pure peer-to-peer architecture except that it relies on a central server for discovery of the other peers. In this model, the application usually notifies the central server of its existence at startup time (or login time). The peer application then uses this server to download a list of the other peers participating on its network that it can use to query for content. When content is needed, it goes through the list and contacts each peer individually with its request.
Figure 3 P2P with a Simple Discovery Server
In many cases, it is easier to make this solution scale better than the pure peer-to-peer option because it circumvents the issues of discovery by requiring only one request to the central server. Note that it is possible to make pure peer-to-peer solutions scale extremely well, but if you are able to rely on a server for some of the basic tasks (like discovery), high scalability can be achieved with a lower cost in terms of development time. However, this approach hinges on the availability of the central server. If the central server is not available, the peer-to-peer application will not be able to find other peers.
In addition, requesting content from each individual peer can be quite expensive from a network resource perspective. This may not seem like a big deal if you're thinking about a few peers interacting over a network, but if your app is being written for use over the Internet or a large enterprise environment, this consideration suddenly becomes much more significant, scaling factorially.
Peer-to-peer with a Discovery and Lookup Server
This model, similar to the one shown in Figure 3, extends the discovery server so that it also includes content lookup services. In this case, the peer application not only registers with a discovery server, but it also uploads a list of its contents at regular intervals. When an application is looking for some particular content, it queries the central server rather than sending a query to each client. The central server then responds with a list of the clients that contain the requested content, and the peer application can contact those clients directly to retrieve the content.
Quite often this approach will scale better than the previous options because it reduces the number of queries going over the network (arguably one of the scarcest resources). However, this saving will incur a cost on the server. Servers are now more involved in the process of content sharing and the peer's demands will use significant resources.
P2P with a Discovery, Lookup, and Content Server
Just to show that this can actually come full circle, a system can be designed so that the peers can upload the content to the server as well, if you so choose (see Figure 4). This approach effectively becomes the client/server model because the peers are no longer contacting other peers for content. Each peer registers with a server (if needed), queries it for information, and transfers any desired content down from the server. The problem with this approach is that when content is downloaded from all of the clients, the server quickly becomes the bottleneck and is easily overwhelmed by the peers (clients).
Figure 4 P2P with a Discovery, Lookup, and Content Server
To put this into perspective, consider a peer-to-peer application that shares video content. Let's assume the application supports up to 100,000 peers, each containing megabytes of data. The total amount of content available to any one peer can quickly reach into hundreds of terabytes. While server capacity can always grow, placing such demand on the server can be costly and can create a significant number of bottleneck and reliability issues on the network. Placing all of the demand on the server also means that the substantial resources available on the clients that would be utilized in the peer-to-peer model are potentially wasted in the client/server model.
Application Model for Peer-to-peer
The .NET Framework has a significant range of choices when it comes to the type of application that you can create. When writing your peer-to-peer application, it is important to understand how it will be used. This will make it easier to decide which .NET application model to use. In the case of peer-to-peer, four powerful application models (or application types) are available. A brief overview of these models follows.
Web Services Found in the System.Web.Services namespace, the Web Services technology provides an excellent way to handle registration, discovery, and content lookup for your peer-to-peer application. A Web Service allows you to quickly write a class that listens for incoming requests, processes them as they arrive, and sends back useful information in the form of objects that are easily understood by the peer application. An example later in this article shows how a Web Service for a peer-to-peer application might be implemented.Windows
Forms Found in the System.WinForms namespace, Windows
® Forms is the .NET Framework solution for writing the type of rich Windows
-based GUI applications that help to make the peer-to-peer experience much more exciting. Windows
Forms is the ideal technology for writing the GUI for the peer that lets your users log in, request, and share content.
Web Forms Found in the System.Web namespace, the Web Forms technology makes it very easy to return HTML content to a peer application. This can be useful if you want to spice up your peer-to-peer application with general content about the service or advertisements about using the service. When the peer-to-peer application starts up and registers with the Web Service, it can also call a Web Forms application to get the latest HTML content from the server.
Service Process Found in the System.ServiceProcess namespace, a service process (also known as a Windows
NT® service) is useful in peer-to-peer scenarios as a long-lived discovery server. In most cases, a Web Service is better for fulfilling the role of the discovery service (mentioned previously). However, in cases where the discovery mechanism is not using the HTTP protocol, a service process listening for some other protocol might be the best way to go.
In addition to providing rich choices for application models, the .NET Framework drastically simplifies the networking side of peer-to-peer application creation, thanks to the classes found in the System.Net namespace and the System.Web.Services namespace. Let's take a closer look at each of these namespaces, the classes they provide, and applications built around each.
The System.Web.Service namespace contains classes for consuming and exposing a Web Service. A Web Service in the .NET Framework is programmable application logic that is accessible via the Simple Object Access Protocol (SOAP). SOAP is a W3C-submitted note that uses standards-based technologies (HTTP as a transport and XML for data description) to encode and transmit application data. Consumers of a Web Service do not need to know anything about the platform, object model, or programming language used to implement the service; they only need to understand how to send and receive SOAP messages (which are simply composed of XML sent over HTTP).
With the .NET Framework, creating a Web Service is as simple as creating a class in a page on the server. Likewise, consuming that Web Service from the peer application is extremely easy and is accomplished by calling a method on a proxy class that is generated with Visual Studio.NET or with the WebServiceUtil.exe program in the .NET Framework SDK. For more general details on how Web Services work, check out the QuickStart documentation in the SDK.
Figure 5 shows the code you might write in a Web Service to expose an API on the server that is called by the peer application for registration and file lookup. Note that in this case I am mixing client/server technology with peer-to-peer concepts to create an application that is fairly simple due to the traditional nature of client/server, yet extremely powerful because it is able to use the resources of all peers registered on the network.
First, you'll notice a class called P2PService. This class contains the methods that you'll be calling to interact with the service. In addition to a constructor, you'll see the class's methods described in Figure 6.
You'll also notice a simple PeerFile class. This class is used as a container to simplify the storage of peer file information. It contains the following members:
IpAddress, which represents the peer's IP network address
Name, which contains the name of the file
Connection, which contains the connection type and speed through which the peer is connecting
Size, which denotes the size of the file
For the sake of simplicity, the implementation of these methods is mostly blank in this article; however, the ShareBaby sample that I've provided (downloadable from the link at the top of this article) contains a full working implementation.
Next, the corresponding client code in Figure 7 shows the proxy class that has been built against the Web Service using the WebServiceUtil.exe tool or using a Web Reference in Visual Studio.NET.
The System.Net namespace contains classes that provide support for creating applications that use Internet protocols to send and receive data. Using a layered approach, the Net classes enable applications to access the network with varying levels of control, depending upon the needs of the application. The spectrum covered by these levels includes nearly every scenario on the Internet today—from fine-grained control over sockets to a generic request/response model. Furthermore, the model is extensible so that it will continue to work with your application as the Internet evolves. For more general details on how the Net classes work, check out the Networking section of the QuickStart documentation in the SDK.
In Figure 8 and Figure 9, you'll see how the Net classes can be used to transfer a file from one peer to another. This code is divided into two methods. The first method, shown in Figure 8, is called ListenForPeers. This method puts the peer application in a listening mode so that it can respond when any other peers try to communicate with it. Once another peer does contact it, the listening peer will proceed to read in the name of the file being requested and then write back the data that represents the requested file. Running this code in the main part of an application form would cause the application to appear as though it had hung while waiting for a request to arrive because this method will block processing while it waits for a peer to contact it. In ShareBaby, this method is called on a thread that is distinct from the main peer, so the user application remains responsive even when no requests for content are being processed.
The second method, shown in Figure 9, is called DownloadToClient. This method will contact a peer (who is listening for requests) and issue a request that it transfer a particular file. If the listening peer has the file, the content is then transferred to the peer application making the request with DownloadToClient.
In addition to being a useful tool for sharing distributed information, a peer application can also be integrated with traditional Web browsing technologies to add HTML content to the user experience. Doing this turns the application into somewhat of a hybrid between a browser and a peer application. For example, there are many cases where your peer-to-peer application can provide opportunities for including up-to-date visual information about your peer-to-peer service or as a tool for advertising that supports your service. You could also build a peer-to-peer app for sharing corporate information. In it, documents and presentations can be shared using the peer service, while general company information such as benefits and HR information can be displayed in a traditional browser window. The code in Figure 10 shows how the WebRequest and WebResponse classes in System.Net can be used to download HTML content from a Web server.
Firewalls and Ports
The peer application in my example is listening for a connection on port 8081. The selection of ports and the protocol used by the peers should be taken into account when designing the application because firewalls are often designed to allow communication only over a particular port. In the case of ShareBaby, port 8081 may be fine for sending and receiving content in a particular network or directly on the Internet, but it probably won't work through a corporate firewall because that port is likely to be closed. If you are designing your peer-to-peer application for a corporate environment, it might be useful to talk with your network administrator about which ports are open or recommended for application use. If the application is designed for users who will most likely not be going through a firewall to find other peers, just about any port that isn't already reserved for another protocol will work. In general, port numbers are divided into three ranges:
Well-known ports (from 0 through 1023)
Registered ports (from 1024 through 49151)
Dynamic and/or private ports (from 49152 through 65535)
For a list of the well-known and registered ports, check out the IANA port assignments list at http://www.isi.edu/in-notes/iana/assignments/port-numbers. In most cases, applications intended for general use over the Internet should use a port in the dynamic or private range or register.
Several of the options mentioned in this article can affect the scalability of your peer application. Network bandwidth is often the scarcest resource (hence the bottleneck) in a peer-to-peer application. Careful consideration should be given to the trade-offs between the flexibility of the peer application to discover other peers and query them for content, and the application's ability to scale up to hundreds of thousands of clients. Unfortunately, further discussion of these issues is beyond the scope of this article.
A Sample Peer-to-peer Application
The ShareBaby application is a working example of a peer-to-peer with a discovery/lookup server. It illustrates the use of the System.Web.Service namespace to expose a ShareBaby Web Service that is then consumed by ShareBaby peer applications. These peers use the ShareBaby service to register the names of the files they want to share and to query for the locations of files they are interested in obtaining from another peer. To represent the ShareBaby peer application, the ShareBaby sample demonstrates the use of the Windows
Forms application model discussed earlier in this article. Finally, ShareBaby uses the System.Net namespace in order to demonstrate how content is transferred from one peer to another.
The peer-to-peer application model applied to large networks such as the Internet offers an exciting opportunity to share information and content over a network on a scale that has never been seen before. The .NET Framework and Visual Studio.NET make it easier for developers to write peer applications by offering a rich UI client programming model, support for scalable Web services, and easy-to-use networking classes, all within the same powerful development environment.