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; }
[DataMember]
public string Name { get; set; }
[DataMember]
public string Id { get; set; }
[DataMember]
public List<Transfer> Files { get; set; }
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);
foreach (FileInfo file in info.GetFiles())
{
Transfer transfer = new Transfer();
transfer.Catalog = folderName.Substring(1);
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())
{
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();
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);
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];
ThreadStart ts = delegate
{
progressBar1.Maximum = (int)(ft.Files.Sum(t => t.Length) / 4);
};
progressBar1.Invoke(ts, null);
IPEndPoint sendTo = (IPEndPoint)(EndPoint)(obj as List<object>)[1];
TcpClient client = new TcpClient(new IPEndPoint(IPAddress.Any, sport));
client.BeginConnect(sendTo.Address, sendTo.Port,
new AsyncCallback(Connected), null);
connected.WaitOne();
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));
sended = 0;
stream.Seek(0, SeekOrigin.Begin);
while (true)
{
byte[] bytes;
if (sended >= stream.Length) break;
if (stream.Length - sended < 20000)
bytes = new byte[stream.Length - sended];
else
bytes = new byte[20000];
sended += bytes.Length;
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);
}
}
client.Client.Receive(new byte[1]);
}
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\";
byte[] overthrow = new byte[0];
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)
{
int gotted = 0;
if (overthrow.Length <= t.Length)
{
stream.Write(overthrow, 0, overthrow.Length);
gotted = overthrow.Length;
}
else
{
byte[] otherbuf = overthrow.Without(0, t.Length);
stream.Write(otherbuf, 0, otherbuf.Length);
overthrow = overthrow.Without(t.Length);
gotted = otherbuf.Length;
}
stream.Flush();
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];
client.Client.Receive(buffer, SocketFlags.Partial);
getted += buffer.Length;
if (getted > t.Length)
{
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));
}
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