Table of Contents
Introduction
The lack of system recources leads to this idea that we can utilize the enormous (and mostly available) number of computers which exist throughout the world. For instance, we can use the computing resources of PCs which most of the time are idle. Also we may use the huge amount of data which are stored in computers. Moreover, instead of using server/client architecture it is possible to connect computers directly through a peer to peer connection. It is helpful because when we want to work over centralized architecture some problems come to the fore and passing time makes those problems tougher and tougher. These growing problems force us to switch to even more powerful hardwares. As a result, we have to use larger bandwidth and more powerful servers ( maybe super computers) _which are expensive. For this reason, a new scope of computing systems named "Distributed systems" was emerged. A Distributed system is a software system in which components located on networked computers communicate and coordinate their actions by passing messages.[1].
Peer-to-peer (P2P) computing or networking is a distributed application architecture that partitions tasks or work loads between peers. Peers are equally privileged, equipotent participants in the application. They are said to form a peer-to-peer network of nodes. Each node is a computer on the network which acts and communicates with other Peers to make a portion of their resources, such as processing power, disk storage or network bandwidth, directly available to other network participants, without the need for central coordination by servers or stable hosts.[1]
Peers are both suppliers and consumers of resources, in contrast to the traditional client–server model where the consumption and supply of resources is always divided. Emerging collaborative P2P systems are going beyond the era of peers doing similar things while sharing resources, and are looking for diverse peers that can bring in unique resources and capabilities to a virtual community thereby empowering it to engage in greater tasks beyond those that can be accomplished by individual peers, yet that are beneficial to all the peers.[2]
You can see the Peer To Peer implementation in Torrents and some famous file sharing systems such as Emule project. I am going to explain P2P file sharing systems below.
Background
For more than one year, I thought how does Torrents work? How does Emule work? Well I couldn't grab a right opportunity to research about this until I participated in distributed systems course during my master degree. Our teacher asked us to develop a Peer To Peer file sharing system as a practical work. I was like "Oh My God, this has been such a big question in my head for a long time and now BoOoOom, that is it. Now I have an apportunity for learning about this". So I started to research and the result was disappointing. The more I studied, the more I got confused. Thereby, I decided to find a sample code to obtain some initial information but it was disappointing as well because I couldn't find any source code by which I could share a file between two peers. I only managed to find some code samples for peer to peer chatting although these codes did not help me to understand how peer to peer file sharing works. So I started to ask some questions in different forums although this was not helpful either. You can read my questions here and here. Eventually, I realized that I should do this all by myself from the scratch. In this article however, I am going to make it easy for all the enthusiastic programmers to get a good understanding about Peer To Peer systems and the way we can share files between peers using C#.
Well, let's get back to the real game. Are you ready? So, let's start.
Why WCF?
A Brief History of WCF
Microsoft developed COM (Component Object Module) to enable application components to interact and communicate with each other in a local machine but COM doesn't provide a way for components to communicate through remote calls in distributed systems. Hence, Microsoft developed DCOM (Distributed Component Object Module) to compensate COM's deficiency. DCOM provides an opportunity to distribute application's component across different locations. In addition, DCOM provides an infrastructure to guarantee the security issues, reliability, location independence. .NET Remoting was introduced to create a way to create distributed applications in .NET. "It replaces DCOM as the preferred technology for building distributed applications. It addresses problems that have wounded distributed applications for many years (i.e., interoperability support, extensibility support, efficient lifetime management, custom hosts, and an easy configuration process). .NET Remoting delivers on the promises of easily distributed computing by providing a simple, extensible programming model, without compromising flexibility, scalability, and robustness. It comes with a default implementation of components, including channels and protocols, but all of them are pluggable and can be replaced with better options without much code modification "[Pro WCF4,Practical Microsoft SOA Implementation]. COM+ is a combination of COM and DCOM. It provides an infrastructure that applications use it to access services and capabilities beyond the development team who builds these services and capabilities. COM+ was initially introduced to provide an infrastructure for COM components but .NET can also utilize its services.
So, why do web services come as a new communication method?
Imagine if you have developed an application based on COM+, how another application which has been coded by JAVA can use it? How could we communicate with another applications which were working on other platforms, OS and so on? Then, we conclude that interoperability is an important issue which sometimes companies spend high costs on to reach it by using a Bridge application as a Middleware. Webservice made this interoperability more accessible and cheaper. "Web services are not just another way of creating distributed applications. The distinguishing characteristic of web services, compared to other distributed technologies, is that rather than relying on proprietary standards or protocols, they rely on open web standards (such as SOAP, HTTP, and XML). These open standards are widely recognized and accepted across the industry. Web services have changed how distributed applications are created. The Internet has created a demand for a loosely coupled and interoperable distributed technology. Specifically, prior to web services, most of the distributed technologies relied on the object-oriented paradigm, but the Web has created a need for distributed components that are autonomous and platform independent."[Pro WCF4,Practical Microsoft SOA Implementation].
WCF subsume all of the best parts of the distributed technologies. WCF brings together the efficiency of ASMX, the ability, extensibility, and flexibility of .NET Remoting, and the supremacy of MSMQ for building queed application,as well the WSE’s interoperability. The below figure gives quite a true view about WCF and its usage. (For more information, refer to the Pro WCF 4 book.)
The Figure has been added from "Pro WCF 4" book.
WCF Basis
Since using WCF requires having knowledge about its major concepts, in this section some of the WCF basics are described. Some developers may think they know WCF well, however I am going to say WCF is much more profound than you may imagine and they will not understand this unless they study WCF in details.
EndPoint: EndPoint is the way that a service exposes itself to the external world. An EndPoint contains information about the path that determines the ways which a service is available through them. EndPoints are a combination of Address, Binding and Contract.
- Address: Specifies where the message can be sent or where the service is live or accessible
- Binding: Describes how to send the message
- Contract: Determines what the message should contain
For some scenarios we use a set of common Addresses, Bindings and Contracts. Hence we can have some Default or Standard Addresses, Bindings, and Contracts.
In the below lines you may see a group of Standard EndPoints:
Mex Endpoint
- Addressing is essential for working with WCF services and for being able to send message to the service. Addresses in WCF has been constituted from several parts which includes :Port, Machine Name, Transport Scheme, Path. The port is an optional field, and Transport Scheme is the protocol of Message transportation. So, the format of a service address is as follows:
scheme://<machinename>[:port]/path1/path2
And you can see a sample of addressing as following lines:
<endpoint
address="http://localhost:8080/QuickReturns/Exchange"
bindingsSectionName="BasicHttpBinding"
contract="IExchange" />
Base Addresses
When there are multi endpoints which are associated with a WCF service, we can define a primary address and then address those endpoints through a relative address. The primary address is called Base Address. The base addresses are defined as below:
<host>
<baseAddresses>
<add baseAddress="http://localhost:8080/QuickReturns"/>
<add baseAddress="net.pipe://localhost/QuickReturns"/>
</baseAddresses>
</host>
And the relative addresses for endpoint would be like this:
<endpoint
name="BasicHttpBinding"
address="Exchange"
bindingsSectionName="BasicHttpBinding"
contract="IExchange" />
<endpoint
name="NetNamedPipeBinding"
address="Exchange"
bindingsSectionName="NetNamedPipeBinding"
contract="IExchange" />
Binding: Biding defines the ways that you are able to communicate with a service. Based on this definition, there are several predefined bindings as follows:
Contracts: Contracts are used to achieve a true collaboration between different platforms and on the other hand include a bunch of conventions that expose the service to the outside world. Contract comes in a variation of flavors. ServiceContract
is the exposed behavior of a service, DataContract
introduces the persistent data (fields and properties of a service), MessageContract
makes the possibility to customize the SOAP message with consideration to your in time requirements and expectations of a service as though security stuffs and so on. OperationContract
is one of the aspects of MessageContract
which presents the methods to access and use the behavior of service.
namespace GettingStartedLib
{
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
public interface ICalculator
{
[OperationContract]
double Add(double n1, double n2);
[OperationContract]
double Subtract(double n1, double n2);
[OperationContract]
double Multiply(double n1, double n2);
[OperationContract]
double Divide(double n1, double n2);
}
}
[ServiceContract]
public class Calculator
{
[OperationContract]
public double Add(double a, double b) { … };
[OperationContract]
private double Subtract(double a, double b) { … };
}
Channels
Channels are responsible for transfering the message between Client and Server depending on the messaging pattern which has been chosen. Channels are created by factories so that they can talk to the service side across a specified channel or a set of channels. In the below figure, you see the structure of messaging stack and the channel's position in that.
Now we can say that Binding is a handler for controlling the Channel stack. When we choose one of the predefined bindings, we literally are picking a specific Channel.
ServiceHost and ChannelFactory
ServiceHost
is a class that allows you to make a service host programmatically and you can do anything with it such as setting the behavior, address, binding and contract. For using this ServiceHost
and ChannelFactory
, you should use the System.ServiceModel
namespace.
using System;
using System.ServiceModel;
using QuickReturns.StockTrading.ExchangeService;
using QuickReturns.StockTrading.ExchangeService.Contracts;
namespace QuickReturns.StockTrading.ExchangeService.Hosts
{
class Program
{
static void Main(string[] args)
{
Uri address = new Uri
("http://localhost:8080/QuickReturns/Exchange");
ServiceHost host = new ServiceHost(typeof(TradeService);
host.Open();
Console.WriteLine("Service started: Press Return to exit");
Console.ReadLine();
}
}
}
The ChannelFactory
is a class that makes the Client capable of getting access to the service which has been set up across server side. Client just knows the exposed contract of the service, thus, it gets an interface as its generic parameter. Then, when you use a channel, you will be able to get access to the service and call its methods. This is the real background process of Visual Studio when it creates and then adds a webservice as a WebReference
to your project. You can see the sample code in the below lines that calls a method from a service which we created and started it through the ServerHost
class:
internal class ExchangeServiceSimpleClient
{
private static void Main(string[] args)
{
EndpointAddress address =
new EndpointAddress
("http://localhost:8080/QuickReturns/Exchange");
BasicHttpBinding binding = new BasicHttpBinding();
IChannelFactory<ITradeService> channelFactory =
new ChannelFactory<ITradeService>(binding);
ITradeService proxy = channelFactory.CreateChannel(address);
Quote msftQuote = new Quote();
msftQuote.Ticker = "MSFT";
msftQuote.Bid = 30.25M;
msftQuote.Ask = 32.00M;
msftQuote.Publisher = "PracticalWCF";
Quote ibmQuote = new Quote();
ibmQuote.Ticker = "IBM";
ibmQuote.Bid = 80.50M;
ibmQuote.Ask = 81.00M;
ibmQuote.Publisher = "PracticalWCF";
proxy.PublishQuote(msftQuote);
proxy.PublishQuote(ibmQuote);
}
}
App config file for client:
<configuration>
<system.serviceModel>
<client>
<endpoint address="http://localhost:8080/QuickReturns/Exchange"
binding="basicHttpBinding"
contract="QuickReturns.StockTrading.ExchangeServiceClient. ITradeService">
</endpoint>
</client>
</system.serviceModel>
</configuration>
These two classes are pretty important because they are the foundation of all connections between nodes in a network of computers. We will employ these classes vastly in our codes to build a file sharing system. Thereby the more understanding you have of how these two classes work, the more you understand how it is possible to share files between peers in a network. You can read chapter 3 of the mentioned book to perceive them deeply.
There are so many details that you need to know about WCF. So I strongly recommend studying it in details otherwise reading the code will just be a wild-goose chasing. Here I will try to explain the basis of WCF however you also should develop at least one project making a remote call from a client to a server in order to have a better understanding of what is going on. As for the rest let's talk about Peer to Peer architecture and its concepts.
Peer to Peer Concepts
Since this introduction is important for creating a file sharing system, in this section I am going to summarize the main concepts of P2P architecture.
First, I am going to talk about the various types of Peer to Peer networks. There are two kinds of Peer to Peer Network. Pure P2P Network
and Hybrid P2P Network
. However these namings seem explanatory, but we should know the main distinction between these 2 types. A Pure P2P Network is a network of peers that just works through Peers and there is no separated concept of Client or Server, instead each of the nodes are connected and work together and play the role of both Client and Server as needed. A Hybrid P2P Network is a network which consists of the client and server concepts though. This server is just responsible to answer the requests which is sent by peers to get some information. The server never stores any data except some totalities. For instance, in a P2P file sharing system, peers request some information about the other peers and the current place of the shared file, then server responds to their request by giving some information about the shared files such as File size, Peer name, File extension and the available peers. When we are talking about a P2P network, we meet some expressions and notions which are too important to comprehend the Peer to Peer network. In the below lines, I am going to illustrate some of the most important concepts and summarize them in order to be understandable in this article.
Mesh:
The Network in Peer to Peer applications called Meshes or Mesh Networks
Mesh Types
: There are two types of Mesh Networks:
Peer Channels
: Peer Channels are basically a message based service which is available in WCF Groupings
: In this kind of peer network, Peers exchange message by replicating records containing the Data.Grouping
is available in Windows XP SP2
Peer:
Each computer in a network of computers who are interacting together, called Peer
Cloud:
A Cloud is a Mesh Network which has a specified address scope and this scope is related to an IPV6 scope. Peers in a cloud are those ones who can communicate across this scope. There are 2 types of predefined Cloud as below:
Global_:
Cloud: If a computer connects to the internet, then, it is joined to a Global cloud LinkLocal_:
Cloud: A set of nodes who are connected to a via Lan, they are working on a LinkLocal cloud
PNRP (*Pretty momentous):
When we are in a cloud, each node should be identified by a unique ID. As you know, when we use internet, we identify each server by a DNS. But a Mesh network cannot use DNS because of the peers dynamic essence. Peers nature are dramatically dynamic and we cannot use a Static IP address for it. Then we use another Protocol called Peer Name Resolution Protocol
(PNRP) to resolve the peers IDs instead of DNS.
PeerName(PeerHostName in .NET):
Each peer beside its ID gets a name is called PeerName
. PeerName
can be registered either as secured or unsecured names. Secured
names are recommended in private networks and secured names are suggested in the Global network (Internet) . Unsecured
networks can seem with a 0.
at the first of Peer's Name, for instance "0.Peer1
". Secured names on the other hand are signed by a digital signature. You can resolve a peer by its name in .NET.
Peer Graph:
A graph is a collection of peer nodes that can communicate with other nodes across their neighbor's connections, hence, it makes this possibility to publish a message for all nodes in a graph Registering Peer
: First of all, the peer needs to become registered in a cloud. You can register a cloud programatically or use netsh command to register peer in a cloud such as the below figure:
Resolving Peer
: We can use both .NET and netsh command to resolve a Peer.When we resolve a peer, indeed, we get access to the peer's information such as peer's name, peer's port and peer's port and become capable to work with it.
For more information about NetShell and its commands for Peer To Peer Networking, see here and this article is pretty informative too.
Namespaces: For taking advantage of .NET classes to work with peer, we need to use both System.ServiceModel
and System.Net.PeerToPeer
namespaces.
Code Survey (How Does It All Work)
The code of this project has been divided into 5 main subprojects as follows:
FreeFile.DownloadManager
FreeFiles.TransferEngine.WCFPNRP
FreeFiles.UI.WinForm
FreeFilesServerConsole
So, let's talk about how these parts work together and then I'll describe each one in detail.
At first, I should mention that this project is a Hybrid P2P Network. As I cited at the pristine lines, it means we have some nodes and a server (we name it Super Peer) who serves the nodes by storing and preparing some information concerning file's path and node's ID to facilitate the nodes collaboration. So, we have a server for taking on some tasks such as searching the files. Therefore, we need to start a Server that provides a WCF service in order for the other nodes be able to connect to it and get their desired information. This server can be a Windows Service or a Console server. However in this project it is a Console project, I believe it should run over a Windows Service. The another WCF service is run when peers should connect to each others for downloading the required files. So, we ought to have two WCF running services in a same time.
When me and my Colleague, decided to develop this project as an open source application, we planned for making a reliable and flexible coding style, hence, we divided this project into several layers and amortized each task to a separated layer. I am going to talk about the codes of DownloadManager
, TransferEngine
and the ServerConsole
layers.
Core Transfer Engine Layer
This layer undertakes all the tasks of Peer actions and also transfers the requested files between peers. Then, in this project, we expect to see some codes around Peers. This part is the heart of this system. When a peer starts to work, first it should register itself as a peer, then it should play the role of both server and client. Afterwards, if any peer asks a file, first it should search the file and when it receives the file's information (such as the destination peer host name), it should use that information to connect to the peers and then, download the file. The PNRPManager
class is responsible to Register and Resolve peers.
The Register()
method registers the peer in the cloud and accepts a list of PeerInfo
type as its input argument.
public List<PeerInfo> Register()
{
List<PeerInfo> registerdPeer = new List<PeerInfo>();
foreach (var registration in registrations)
{
string timeStamp = string.Format("FreeFile Peer Created at : {0}",
DateTime.Now.ToShortTimeString());
registration.Comment = timeStamp;
try
{
registration.Start();
if (registerdPeer.FirstOrDefault(x => x.HostName ==
registration.PeerName.PeerHostName) == null)
{
PeerInfo peerInfo = new PeerInfo(registration.PeerName.PeerHostName,
registration.PeerName.Classifier, registration.Port);
peerInfo.Comment = registration.Comment;
registerdPeer.Add(peerInfo);
}
}
catch { }
}
this.CurrentPOeerRegistrationInfo = registerdPeer;
return registerdPeer;
}
The Start()
method registers peers and the Stop()
method unregisters the peer from a specified cloud. For getting access to a peer's information, the peer should be resolved. When the peer gets resolved, some information as though peer host name, Classifier, port can be accessible. The ResolveByPeerHostName method
can resolve a peer by its host name and returns a list of PeerInfo
type.
public List<PeerInfo> ResolveByPeerHostName(string peerHostName)
{
try
{
if (string.IsNullOrEmpty(peerHostName))
throw new ArgumentException("Cannot have a null or empty host peer name.");
PeerNameResolver resolver = new PeerNameResolver();
List<PeerInfo> foundPeers = new List<PeerInfo>();
var resolvedName = resolver.Resolve(new PeerName(peerHostName,
PeerNameType.Unsecured), Cloud.AllLinkLocal);
foreach (var foundItem in resolvedName)
{
foreach (var endPointInfo in foundItem.EndPointCollection)
{
PeerInfo peerInfo = new PeerInfo(foundItem.PeerName.PeerHostName,
foundItem.PeerName.Classifier,endPointInfo.Port);
peerInfo.Comment = foundItem.Comment;
foundPeers.Add(peerInfo);
}
}
return foundPeers;
}
catch (PeerToPeerException px)
{
throw new Exception(px.InnerException.Message);
}
}
}
After resolving, there are a list of peers who appear as an EndPointCollection
and if we use a foreach
loop, we can access each peer as an endpoint.
FileTransferServiceHost
class makes each peer as a server host to provide required files to another peers. This class uses TCP protocol for transferring the data between peers. The DoHost()
method gets an address, based on the peer host name, then adds an interface who applied the ServiceContract
attribute. Therefore each peer publishes a service to the external world in order to make its methods accessible across service. (In this case, methods are TransferFile
and TransferFileByHash
.)
sealed class FileTransferServiceHost
{
public void DoHost(List<PeerInfo> peers)
{
Uri[] Uris = new Uri[peers.Count];
string Address = string.Empty;
for (int i = 0; i < peers.Count; i++)
{
Address = string.Format("net.tcp://{0}:{1}/TransferEngine",
peers[i].HostName, peers[i].Port);
Uris[i] = new Uri(Address);
}
FileTransferServiceClass currentPeerServiceProxy = new FileTransferServiceClass();
ServiceHost _serviceHost = new ServiceHost(currentPeerServiceProxy, Uris);
NetTcpBinding tcpBinding = new NetTcpBinding(SecurityMode.None);
_serviceHost.AddServiceEndpoint(typeof(IFileTransferService), tcpBinding, "");
_serviceHost.Open();
}
}
[ServiceContractAttribute]
interface IFileTransferService
{
[OperationContractAttribute(IsOneWay = false)]
byte[] TransferFileByHash(string fileName,string hash, long partNumber);
[OperationContractAttribute(IsOneWay = false)]
byte[] TransferFile(string fileName, long partNumber);
}
If a client (peer) wants to get access a method of the another peer, it should use the Channels to reach this ability. This part has been coded in FileTransferServiceClientClass
class as follows:
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Single,
InstanceContextMode = InstanceContextMode.Single, UseSynchronizationContext = false)]
class FileTransferServiceClientClass : System.ServiceModel.ClientBase<IFileTransferService>
{
public FileTransferServiceClientClass() :base()
{
}
public FileTransferServiceClientClass(string endpointConfigurationName) :
base(endpointConfigurationName)
{
}
public FileTransferServiceClientClass(string endpointConfigurationName, string remoteAddress) :
base(endpointConfigurationName, remoteAddress)
{
}
public FileTransferServiceClientClass(string endpointConfigurationName,
System.ServiceModel.EndpointAddress remoteAddress) :
base(endpointConfigurationName, remoteAddress)
{
}
public FileTransferServiceClientClass(System.ServiceModel.Channels.Binding binding,
System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress)
{
}
public byte[] TransferFile(string fileName,string hash, long partNumber)
{
return base.Channel.TransferFileByHash(fileName, hash, partNumber);
}
public byte[] TransferFile(string fileName, long partNumber)
{
return base.Channel.TransferFile(fileName, partNumber);
}
}
This point can be useful to know that each instance of this application registers itself as a peer and then starts to setting up a WCF service using the FileTransferServiceHost
as below:
void IFileProviderServer.SetupFileServer()
{
var peers = pnrpManager.Register();
if (peers == null || peers.Count == 0) throw new Exception("Host not registered!");
var fileTransferServiceHost = new FileTransferServiceHost();
fileTransferServiceHost.DoHost(peers);
}
Download Manager Layer
This Layer pledges to manage all activities behind the downloading tasks such as Managing Download processes and exceptions, downloading files, slabbing the requested file into the several pieces and then downloading each part asynchronously, searching the file based on its hash id or its name and ultimately, making a shared folder and storing the downloaded file into it.
The Search()
class provides a search engine to look for the demanded file across the service which runs into the server layer. As I mentioned formerly, the server publishes a service provides some public information to the applicator peers (such as file name, peer host name, file type). I will expatiate the server layer's code during the next paragraphs as well.
public List<Entities.File> Search(string searchPattern)
{
FileServer.FilesServiceClient fileServiceClient = new FileServer.FilesServiceClient();
List<Entities.File> filesList = new List<File>();
foreach (var file in fileServiceClient.SearchAvaiableFiles(searchPattern))
{
Entities.File currentFile = new File();
currentFile.FileName = file.FileName;
currentFile.FileSize = file.FileSize;
currentFile.FileType = file.FileType;
currentFile.PeerID = file.PeerID;
currentFile.PeerHostName = file.PeerHostName;
filesList.Add(currentFile);
}
return filesList;
}
The vital part of this layer is FileTransferManager
class which manages the file transferring process. It consists of all the needed methods for downloading a whole or a part of a file. The UI layer calls the Download()
method of this class. This method starts a task which uses the StartDownload
method as its action. Afterward the StartDownload
method is called. Across this method, the file is requested based on its part number which is generated based on a constant value(10240).
public void Download(Entities.File fileSearchResult)
{
var downloadAction = new Action<object>(StartDownload);
Task downloadActionTask = new Task(downloadAction, fileSearchResult);
downloadActionTask.Start();
}
const long FilePartSizeInByte = 10240;
private void StartDownload(object state)
{
Entities.File fileSearchResult = state as Entities.File;
long partcount = fileSearchResult.FileSize / FilePartSizeInByte;
long mod = fileSearchResult.FileSize % FilePartSizeInByte;
if (mod > 0) partcount++;
downloadFilePart(new DownloadParameter {FileSearchResult=fileSearchResult,
Host = fileSearchResult.PeerHostName, Part = partcount });
}
As you observe, the StartDownload
method calls downloadFilePart
method. This method calls the GetFile
method of TransferEngine
class which is created by factory
class.
So, let's take a glimpse over the factory
class. This class uses the DLL of TransferEngine
layer and manufactures a new instance of the required engine (such as: Search Engine or Transfer Engine). It takes a DLL file path and loads it ,then utilizes its methods. We use this kind of access to a class library due to this rational reason that two classes cannot have refer to each other in a same time. As you see, it uses the channels to access the server's methods to download the file.
public sealed class Factory
{
Factory()
{
Assembly transferEngineAssembly = Assembly.LoadFile(String.Format(
"E:\\FreeFiles\\FreeFiles.TransferEngine.WCFPNRP\\bin\\Debug\\FreeFiles.TransferEngine.WCFPNRP.dll"));
var tnaTypes = transferEngineAssembly.GetTypes();
foreach (var item in tnaTypes)
{
if (item.GetInterface("ITransferEngineFactory") != null)
{
ITransferEngineFactory ITransferEngineFactory = Activator.CreateInstance(item) as ITransferEngineFactory;
this.transferEngine = ITransferEngineFactory.CreateTransferEngine();
this.fileProviderServer = this.transferEngine as IFileProviderServer;
break;
}
}
this.searchEngine = new Searchengine();
}..................................................................
In this class, I used a string
as the DLL path but it is wrong and makes lots of problems (for instance, for each user, we have to set the file path again and it is too stupid). Then the right style is using a method who returns the application's folder path.
As it was mentioned, this layer is a pretty important component of this project and there are lots of issues that can be employed over it(For future releases).
Server Layer
Welcome to the last explained layer in this article. If I want to explain this layer, I should say it is just a WCF service that provides some methods to search between files(shared across peers). This layer utilizes the Entity Framework as the ORM for unifying the way of querying over database. If you open the edmx file, you will see something as below which is a schema of the database construction:
Based on above design, each peer can be related with the several files (One to Many relationship) and it exactly is the stuff that we expect it. This construction is pretty simple but it will be pretty elaborate when we want to develop a more complex system (which is the main goal of this project in the next releases). Anyhow, as I said, this layer plays as a WCF service role. This vital role comes to realize across the FilesService
class as below:
public class FilesService
{
private FreeFilesEntitiesContext _freeFilesObjectContext=new FreeFilesEntitiesContext();
[OperationContract]
public void AddFiles(List<Entities.File> FilesList,Entities.Peer peer)
{
FileRepository fileRepository = new FileRepository
(_freeFilesObjectContext as FreeFilesServerConsole.IUnitOfWork);
this.AddPeer(externalPeerToEFPeer(peer));
fileRepository.AddFiles(externalFileToEFFile(FilesList));
SaveFile();
}
[OperationContract]
public void AddPeer(FreeFilesServerConsole.EF.Peer Peer)
{
FileRepository fileRepository = new FileRepository
(_freeFilesObjectContext as FreeFilesServerConsole.IUnitOfWork);
fileRepository.AddPeer(Peer);
}
[OperationContract]
public List<Entities.File> SearchAvaiableFiles(string fileName)
{
FileRepository fileRepository = new FileRepository
(_freeFilesObjectContext as FreeFilesServerConsole.IUnitOfWork);
return internalFileToEntityFile(fileRepository.SearchAvaiableFiles(fileName));
}
public void SaveFile()
{
_freeFilesObjectContext.Save();
}
.
.
.
.
The implementation of file searching (and the other methods related to file activities such as adding a file and its related peer) is observable through the FileRepository
class.
class FileRepository:IFilesRepository
{
private FreeFilesEntitiesContext _freeFilesObjectContext;
public FileRepository(IUnitOfWork unitOfWork)
{
_freeFilesObjectContext = unitOfWork as FreeFilesEntitiesContext;
}
public List<FreeFilesServerConsole.EF.File> SearchAvaiableFiles(string fileName)
{
var filesList = from files in _freeFilesObjectContext.Files
join peers in _freeFilesObjectContext.Peers on files.PeerID equals peers.PeerID
where files.FileName.Contains(fileName)
select new {files,peers };
List<FreeFilesServerConsole.EF.File> List = new List<File>();
foreach (var item in filesList)
{
File file = new File();
file.FileName = item.files.FileName;
file.FileSize = item.files.FileSize;
file.FileType = item.files.FileType;
file.PeerHostName = item.peers.PeerHostName;
List.Add(file);
}
return List;
}
public void AddFiles(List<FreeFilesServerConsole.EF.File> FilesList)
{
try
{
foreach (FreeFilesServerConsole.EF.File file in FilesList)
{
_freeFilesObjectContext.Files.AddObject(file);
}
}
catch (Exception exp)
{
throw new Exception(exp.InnerException.Message);
}
}
public void AddPeer(FreeFilesServerConsole.EF.Peer Peer)
{
try
{
_freeFilesObjectContext.Peers.AddObject(Peer);
}
catch (Exception exp)
{
throw new Exception(exp.InnerException.Message);
}
}
public void Save()
{
_freeFilesObjectContext.Save();
}
}
As you see, this class uses the Unit Of Work pattern to gather all transactions in just one transaction.
Indeed it comes with two important benefits: in-memory updates and unifying the various transactions in just one. For more details, I suggest you read this article since I found it pretty handy.
Another considerable class is the ServiceInitializer
. It hosts the service and makes it accessible to the external world (Peers) using the config file's values.
public class ServiceInitializer : IServiceInitializer
{
private string _endPointAddress = string.Empty;
public ServiceInitializer()
{
_endPointAddress =
ConfigurationSettings.AppSettings["FileServiceEndPointAddress"].ToString();
}
public void InitializeServiceHost()
{
Uri[] baseAddresses = new Uri[]{
new Uri(_endPointAddress),
};
ServiceHost Host = new ServiceHost(typeof(FilesService),baseAddresses);
Host.AddServiceEndpoint(typeof(FilesService),
new BasicHttpBinding(),"");
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
Host.Description.Behaviors.Add(smb);
Host.Open();
}
}
This service can be available when you run the FreeFilesServerConsole
project. Then, you will see a message like the below figure which announces the WCF service starting status. Afterwards, you can search or share your desired files.
Using The Code (How To Run and Debug This Application)
Using this code is not as simple as you may have imagined since you should run it across at least 2 computers which are related together through a same network, however you will be able to run and test it if you read the below lines carefully.
For running this application, first your computer should be connected to a network and the firewall ought to be deactivated. Then follow the below steps for running the code:
- Install the attached database or use the Generate Database from Edmx file option in Visual Studio on the server and set the right server configurations on the
FreeFileServerConsole
application - Run the
FreeFileServerConsole
project and wait until it writes the service running success message - Open the project into 2 different networked computers and run the Windows Form application project
- Share a file and wait until it demonstrates the "Done" Message
- Search the file and after it is found, click on the name of the file on
GridView
in order to start download. There still is no progress bar for showing the download but this feature will be added in the near future and next release - Noticeable Point: You can use just one computer to do all of these steps but you should open two instances of Visual Studio and after running one of them, you should change the port number in the second instance of Visual Studio (which has been set through the code) and set it again manually , else you will encounter an error while downloading the file. After setting a new "port number" in the second instance of Visual Studio, Run it too. Now everything is ready!
I think there are no other important details to talk about it for running this application successfully, unless I have forgotten some tiny points. In case of any problem, you can ask your question in the comments part of this page.
How Does the Current Application Look Like?
The primitive version is pretty simple in both of its appearance and features. When you setup all parts of application, in kind there is a shared file, you can search it and see the results in a simple GridView
.
You can share a desired file as well by clicking on the "Share File" button. The current sharing style is too simple. In the next version, one of the most momentous features is the ability to copy a bunch of files in a shared folder which is accessible to the others or the capability to share a folder not just one file. Nevertheless, the current appearance is just like this:
Moreover, the current appearance ought to change to a better one which is more handy and user friendly to the end user.
A Summary of the Current Shortcomings
**Important update : NetPeerTcpBinding marked obsolete in Net 4.5
For more information visit this link : http://bit.ly/1pyFOSb
As I already said, there are lots of weak points and leakages. As a list of features which is preferred for developing during the next version, it ought to focus over the below points:
- A user friendly appearance
- A port manager class that can find the open ports on the current computer
- Advanced file sharing process such as sharing a folder or copying files into a specified folder (just like Dropbox)
- Advanced search which consists of searching over file hash code, similar names, related files and so on
- The peers status of being online or not and show it to the user who want to download the file
- Downloading the different file parts from different peers who shared that file. This feature has been coded but it is not used in the current versions
- Error Handling: This part is pretty important and we did not spend sufficient time to develop it properly. Now, you can find lots of empty catch{} statements. This part is vital and we must improve this part as soon as possible.
- Message handling
- There should be a progress bar that shows the downloading status
- The design of database should be changed for elaborate features
- As I alarmed, in factory class of download manager layer, I've used a
string
as the DLL path but it is wrong and it creates a lot of problems (for instance, for each user, we have to set the file path again. Well this is not good right?). Therefore, the right way is using a method that returns the application's folder path.
There are vast amount of possible features which should be developed during the next versions. Then, if you believe in open-source development, take part.
How Can I Play a Part in this Project?
Guys, we want to develop this project again and make it such a tremendous job. So, join us!
This project is an open-source project, hence you can contribute in its development. You can notify your ideas and initiatives to have a better and advanced application. Based on this open-source essence of the current project, you can take part in the development process through GitHub. As you may know, GitHub is a web-based application for open-source development activities. You can discuss with the other developers, notify your ideas, trace the last changes, the current bugs and talk about them to others. For more information about GitHub, refer to its help center. The current application's link in GitHub is:
You can download it and collaborate in its development easily. As I mentioned, there are lots of ideas that are developable. Then, here we go!
Extra Resources for More Studies
I attempted to give a suitable view point about the Peer To Peer networks, WCF and so on but there are some amazing and informative resources to know about the Peer To Peer Network. I uploaded some beneficial videos about P2P network and its fundamental concepts as follow. You can download them by clicking on the images:
Intro to PNRP
PNRP And WCF Direct Connect:
A Great introduction to the peer Channels:
Points of Interest
During this amazing project as a compulsive learning, I came to this conclusion that I did not know enough about WCF and its authority. I believe, one of the most fabulous points of this project was understanding my weakness over WCF and this issue necessitated me to learn it more in depth. Then, I suggest learning it deeply and forgetting your limited knowledge about WCF. I believe it is impossible to get familiar with WCF completely unless you read a lot of books and after working several years with it (and adapting yourself with the new versions). Another point of interest for me was this odd and funny issue that there is no open source project around Peer To Peer file sharing using C#. Then, I hope this open-source project and the above descriptions be useful, additionally a suitable step to have an open-source project through C# about the peer to peer networks.
Final Word
I think the final word should be devoted to reminisce of some persons who had an effective role in this project. First of all, I should appreciate my great friend ,"Pooya Shahbazian" who collaborated over the architecture and coding of this project. The second one, is "Yousof Mehrdad" who encouraged me to follow out trying to resolve this problem and find a way to develop this project. Eventually, I thank "Ms. Priscilla Fontes" for her phenomenal inspirations about my ability to do my best over this project as she has been a great companion in all aspects of my life. Thank you all for reading this article and thanks to those who collaborated in the development process.
History
April 6, 2013