In the last chapter, you learned how to catalog peers and resources with a discovery web service. In this chapter, we'll develop a sophisticated file-sharing application that uses the discovery service.
The file-sharing client is a lengthy example, and it will take the entire chapter to dissect the code. This complexity is a result of the multiple roles that a peer-to-peer application must play. A peer-to-peer client needs to periodically submit registration information to a central web service, serve files to hordes of eager peers, and retrieve more files from a different set of peers on the network—potentially all at once. The only way to handle these issues is with careful, disciplined threading code.
The FileSwapper application is built around a single form (see Figure 9-1). This form uses multiple tables and allows users to initiate searches, configure settings, and monitor uploads and downloads.
FileSwapper divides its functionality into a small army of classes, including the following:
SwapperClient, which is the main form class. It delegates as much work as possible to other classes and uses a timer to periodically update its login information with the discovery service.
Global, which includes the data that's required application-wide (for example, registry settings).
App, which includes shared methods for some of the core application tasks such as Login(), Logout(), and PublishFiles(), and also provides access to the various application threads.
RegistrySettings, which provides access to the application's configuration settings, along with methods for saving and loading them.
ListViewItemWrapper, which performs thread-safe updating of a ListViewItem.
Search, which contacts the discovery service with a search request on a separate thread (allowing long-running searches to be easily interrupted).
FileServer and FileUpload, which manage the incoming connections and transfer shared files to interested peers.
FileDownloadQueue and FileDownloadClient, which manage in-progress downloads from other peers.
Messages, which defines constants used for peer-to-peer communication.
The file-transfer process is fairly easy. Once a peer locates another peer that has an interesting file, it opens a direct TCP/IP connection and sends a download request. Conceptually, this code is quite similar to some of the examples shown in Chapter 7. However, the application is still fairly complex because it needs to handle several tasks that require multithreading at once. Because every peer acts as both a client and a server, every application needs to simultaneously monitor for new incoming connections that are requesting files. In addition, the application must potentially initiate new outgoing connections to download other files. Not only does the client need to perform uploading and downloading at the same time, but it also needs to be able to perform multiple uploads or downloads at once (within reason). In order to accommodate this design, a separate thread needs to work continuously to schedule new uploads or downloads as required.
Figure 9-2 shows a simplified view of threads in the FileSwapper application. Note that for the most part, independent threads run code in separate objects to prevent confusion. However, this isn't a requirement, and a single object could be executed on multiple threads or a single thread could run the code from multiple objects.
The full FileSwapper application can be downloaded with the code for this chapter. In this chapter, we'll walk through all the threading and networking code, but omit more trivial details such as namespace imports and the automatically generated Windows designer code. We'll begin by examining some of the building blocks such as the classes used to register the peer, to read configuration information, and to process MP3 files. Next, you'll look at the code for searching available peers. Finally, you'll see the multithreaded code for handling simultaneous uploads and downloads over the network.
The FileSwapper requires a web reference to the discovery service in order to work. To add this, right-click the project name in the Solution Explorer, and choose Add Web Reference. Type the full path to the virtual directory and web service .asmx file in the Address field of the Add Web Reference window. When you press Enter, the list of web-service methods from the Internet Explorer test page will appear, as shown in Figure 9-3.
Click Add Reference to generate the proxy class and add it to your project. The proxy class should not be manually modified once it's created, and so it isn't shown in the Solution Explorer. However, you can examine it by choosing Project Show All Files from the Visual Studio .NET window. The proxy class is always named Reference.vb, as shown in Figure 9-4. We won't consider the proxy class code in this book, although it makes interesting study if you'd like to understand a little more about how web services convert .NET objects into SOAP messages and back.
If you need to change a web service, you must recompile it and then update the client's web reference. To do so, right-click the web-service reference in the Solution Explorer, and choose Update Web Reference.
If you click on a web reference in the Solution Explorer, you can find a property called Url Behavior in the Properties window. This property is set to static by default, in which case a fixed URL is set for the discovery service and added to the generated proxy class code. If you change the Url Behavior property to dynamic, an application setting will be added to the client application's configuration file with the web-service URL. This way, if you move the web service to another server you only need to change the configuration file, rather than recompile the client application.