Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

File Transfer using WCF and Socket

0.00/5 (No votes)
7 Jun 2009 1  
This article will show how to transfer some data from one point to another

Introduction

This article will show you how to write a specific program for transferring files and folder through network using WCF (Windows Communication Foundation) and TCP clients.

Algorithm

  • When program runs, we start a new WCF service on the specific port with number 7634
  • Each client has his own proxy, which provides functionality for working with this service.
  • One client connects to another with this proxy and calls TransferClient.SendQuery(string ip, FilesTransfer transfer,string sport)
  • If another client accepts it begins to exchange files data through the Sockets on the random port?.

Beginning

All logic of a file transfer shares on two main parts - communicating with client's WCF service and working with Sockets. So, I'll try to explain how it works in the real program.

First Step - WCF Service

If you don't know what is WCF and how it works, I strongly recommend you to read an article, Windows Communication Foundation FAQ quick starter Part 1.
All web service code is located in ITransfer.cs file. The service interface provides one method SendQuery, which is necessary for inquiry to the client.

[ServiceContract(SessionMode = SessionMode.Required)]
    public interface ITransfer
    {
        [OperationContract]
        IEnumerable<object> SendQuery(string ip, FilesTransfer transfer, int sport);
    }

As you can see, the SessionMode attribute is set as Required. It means that for each client, a separate session will be created.
Also in this piece of code, the FilesTransfer class is involved. It is used for reception of information about files by another client. Realisation is resulted below:

[DataContract]
    public class FilesTransfer
    {
        [DataMember]
        public Action Action { get; set; } // The action of the packet
        [DataMember]
        public string Name { get; set; } //Client name
        [DataMember]
        public string Id { get; set; } //Identification
        [DataMember]
        public List<Transfer> Files { get; set; } //the list of the file

        public FilesTransfer()
        {
            Files = new List();
        }

        public FilesTransfer(Action action)
            : this()
        {
            Action = action;
        }
    }

Let's stop on the list of files. It is a simple generic enumeration of Transfer class. This class comprises information on each file or the catalogue separately. If the list contains a folder, it means that it also contains all files and directories below on the hierarchy tree. It is reached by the recursive algorithm:

[DataContract]
    public class Transfer
    {
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public long Length { get; set; }
        [DataMember]
        public string Catalog { get; set; }
        [DataMember]
        public string Id { get; set; }
        [DataMember]
        public string FullPath { get; set; }
    }
private List<Transfer> RecurseFolder(string root, string folderName)
        {
            List<Transfer> result = new List<Transfer>();
            DirectoryInfo info = new DirectoryInfo(root + folderName); //get information
						// about current directory
            foreach (FileInfo file in info.GetFiles()) // get all files from 
						// "folderName" folder
            {
                Transfer transfer = new Transfer();
                transfer.Catalog = folderName.Substring(1); //without slash sign
                transfer.Id = Guid.NewGuid().ToString() + Guid.NewGuid().ToString();
                transfer.Length = file.Length;
                transfer.Name = file.Name;
                transfer.FullPath = file.FullName;
                result.Add(transfer);
            }
            foreach (DirectoryInfo dir in info.GetDirectories())
            {
                //recursively get all files from directories
	       result.AddRange(RecurseFolder(root, folderName + @"\" + dir.Name)); 
	   }
            return result;
        }

Well, when FilesTransfer class is full of necessary data, let's look at how the SendQuery method implements in the web service. Having received the necessary information, after the MessageBox.Show() request, the new TProgress form is created and client socket port is generated. The client is completely prepared for the data transferring to begin.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession,
        ConcurrencyMode = ConcurrencyMode.Multiple, 
	UseSynchronizationContext = false, IncludeExceptionDetailInFaults = true)]
    public class TransferClass : ITransfer
    {
        AutoResetEvent tcontinue = new AutoResetEvent(false);
        TProgress form;

        public IEnumerable<object> SendQuery
		(string ip, FilesTransfer transfer, int sport)
        {
            int port = 0;
            switch (transfer.Action)
            {
                case Action.Invite: 
                    {
                        port = HelpClass.GetAvailablePort(); 	//getting free port 
							//from the system
                        if (MessageBox.Show(string.Format("Accept {0} files from {1}", 
			transfer.Files.Count, transfer.Name), "Files", 
			MessageBoxButtons.YesNo) == DialogResult.Yes)
                        {
                            ThreadStart start = delegate
                            {
                                form = new TProgress(null, port);
                                form.sport = sport;
                                form.Show();
                                form.GetThread = new Thread
				(new ParameterizedThreadStart(form.GetInvoke));
                                form.GetThread.Start(transfer);
                            };
                            HelpClass.Form.BeginInvoke(start); //if we call thread 
					// by "this.Invoke()" the parental stream 
					// would be lost and we couldn't turn to 
					// other elements of a "form" class
                            break;
                        }
                        else return new List<object>() { (int)InviteRusult.Cancel, 0 };
                    }
                default:
                    {
                        return new List<object>() { (int)InviteRusult.Busy, 0 };
                    }
            }
            return new List<object>() { (int)InviteRusult.Ok, port };
        }
    }

Second Step - Socket Transferring

Now when both ports for a socket are known, clients connect to each other and begin to transfer byte data. But it isn't a model like "client-server" because when one file is passed the socket which was a server for the one moment becomes a client and also a client becomes a server. It is necessary because the server does not know when the client has completely accepted the handed over information and that there are no overloads or information leakage. It stops the action till the moment when the client will inform that all is accepted. If the type is directory, client simply creates it in the necessary folder. Let's look at the server side sender:

public void SendInvoke(object obj)
        {
            try
            {
                FilesTransfer ft = (FilesTransfer)(obj as List<object>)[0]; //get file 
					//information which you want to transfer
                ThreadStart ts = delegate //invocation of the main thread
                {
                    progressBar1.Maximum = (int)(ft.Files.Sum(t => t.Length) / 4);
                };
                progressBar1.Invoke(ts, null);
                IPEndPoint sendTo = (IPEndPoint)(EndPoint)(obj as List<object>)[1];//get
				// the address of the client we want to connect
                TcpClient client = new TcpClient(new IPEndPoint(IPAddress.Any, sport));
                client.BeginConnect(sendTo.Address, sendTo.Port, 
		new AsyncCallback(Connected), null); //opening connection
                connected.WaitOne(); //sleep till the client answered
                long sended = 0;
                foreach (TransferNamespace.Transfer t in ft.Files)
                {
                    using (FileStream stream = new FileStream
			(t.FullPath, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        ts = delegate
                        {
                            progressBar2.Value = 0;
                            progressBar2.Maximum = (int)t.Length;
                            textBox1.Text = t.Name;
                        };
                        this.Invoke(ts, null);
                        client.SendBufferSize = 65 * (int)Math.Exp
				(6 * System.Math.Log(10)); //like 65 10^6
                        sended = 0;
                        stream.Seek(0, SeekOrigin.Begin); //seek the pointer 
						   //to the begin of the file
                        while (true)
                        {
                            byte[] bytes; //we will send the data by 20000 sized packets
                            if (sended >= stream.Length) break;
                            if (stream.Length - sended < 20000)
                                bytes = new byte[stream.Length - sended]; //if it is 
							//the last packet
                            else
                                bytes = new byte[20000];
                            sended += bytes.Length; //increment the counter of 
						// the sended data
                            ThreadStart asdasd = delegate
                            {
                                progressBar1.Value += (int)bytes.Length / 4;
                                progressBar2.Value += (int)bytes.Length;
                            };
                            this.Invoke(asdasd, null);
                            stream.Read(bytes, 0, bytes.Length);
                            client.Client.Send(bytes, SocketFlags.Partial); //sending 
							//by "streaming" mode
                        }
                    }
                    client.Client.Receive(new byte[1]); //sleep for accepting 
					//that client completely got the file
                }
                client.Close();
                ts = delegate
                {
                    textBox1.Text = "Sended.";
                };
                this.Invoke(ts, null);
            }
            catch { MessageBox.Show("Aborted"); }
        }

And client:

public void GetInvoke(object obj)
        {
            try
            {
                string path = Application.StartupPath + @"\Files\"; //get the full 
						//path to the file location
                byte[] overthrow = new byte[0]; //for the surplus data
                FilesTransfer tr = (FilesTransfer)obj;
                ThreadStart callback = delegate
                {
                    progressBar1.Maximum = (int)tr.Files.Sum(t => t.Length) / 4;
                };
                progressBar1.Invoke(callback, null);
                TcpListener listener = new TcpListener(port);
                listener.Start();
                TcpClient client = listener.AcceptTcpClient();
                callback = delegate
                {
                    this.Text = string.Format("Getting from {0}", 
		   (client.Client.RemoteEndPoint as IPEndPoint).Address.ToString());
                };
                this.Invoke(callback, null);
                client.ReceiveBufferSize = 65 * (int)Math.Exp(6 * System.Math.Log(10));
                foreach (TransferNamespace.Transfer t in tr.Files)
                {
                    if (t.Catalog != "<root>" && !Directory.Exists(path + t.Catalog))
                        Directory.CreateDirectory(path + t.Catalog);
                    FileStream stream = new FileStream(path + 
			(t.Catalog == "<root>" ? "" : t.Catalog) + 
			t.Name, FileMode.OpenOrCreate, FileAccess.Write);
                    ThreadStart todo = delegate
                    {
                        progressBar2.Value = 0;
                        progressBar2.Maximum = (int)t.Length;
                        textBox1.Text = t.Name;
                    };
                    this.Invoke(todo, null);
                    long getted = 0;
                    if (overthrow.Length > 0) //If there are surpluses of the data, 
			//we are going to put it in the beginning of the new file
                    {
                        int gotted = 0;
                        if (overthrow.Length <= t.Length)
                        {
                            stream.Write(overthrow, 0, overthrow.Length);
                            gotted = overthrow.Length; //increment and start for 
						//normally getting other data
                        }
                        else //it means that we got the data of two files. 
				//It can be if files are very small
                        {
                            byte[] otherbuf = overthrow.Without(0, t.Length);
                            stream.Write(otherbuf, 0, otherbuf.Length);
                            overthrow = overthrow.Without(t.Length);
                            gotted = otherbuf.Length;
                        }
                        stream.Flush(); //writing in the file
                        getted = gotted;
                        todo = delegate
                        {
                            try
                            {
                                progressBar1.Value += (int)gotted / 4;
                                progressBar2.Value += (int)gotted;
                            }
                            catch { }
                        };
                        this.Invoke(todo, null);
                    }
                    while (getted < t.Length)
                    {
                        byte[] buffer = new byte[client.Client.Available]; //get new 
								  //bytes
                        client.Client.Receive(buffer, SocketFlags.Partial);
                        getted += buffer.Length; //increment the counter
                        if (getted > t.Length) //if we got more data that needs 
			//for current file, it must be located in the specified array
                        {
                            long length = (long)getted - t.Length;
                            overthrow = buffer.Without(buffer.Length - length);
                            buffer = buffer.Without(0, buffer.Length - length);
                            getted -= length;
                        }
                        todo = delegate
                        {
                            try
                            {
                                progressBar1.Value += (int)buffer.Length / 4;
                                progressBar2.Value += (int)buffer.Length;
                            }
                            catch { }
                        };
                        this.Invoke(todo, null);
                        stream.Write(buffer, 0, buffer.Length);
                        stream.Flush();
                    }
                    stream.Dispose();
                    client.Client.SendTo(new byte[1] { 1 }, 
			new IPEndPoint((client.Client.RemoteEndPoint 
			as IPEndPoint).Address, sport)); //continue sending data
                }
                client.Close();
                callback = delegate
                {
                    textBox1.Text = "Getted.";
                };
                this.Invoke(callback, null);
            }
            catch { MessageBox.Show("Aborted"); }
        }

Conclusion

All in all, pluses in this architecture are that we could keep many connections and transferring processes. Using such technologies as WCF, we don't care about serialization of objects and working with channels. There are ways of transferring a stream through web service, but it is not our choice. Socket provides us with a high speed, not only client-server architecture and more comfortable interface.

History

  • 7th June, 2009: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here