This tutorial will teach you how to send and receive files from your local system to a server, either through a network or through the Internet, using C# and streaming connections. This tutorial is also helpful for learning the basics of networking using the .NET Framework.
In this tutorial we’re going to build a very interesting .NET application, and based on the code here you will hopefully be able to build various applications or to help you with extending your current work.
Before digging into the code, you should know that the code you see in this tutorial and in the attached project resumes to only the strict functionality of exchanging files between two computers using the .NET Framework. If you want to integrate this into a real-life application, you will need to put more effort into this code since currently it lacks crash prevention and recovery, testing on multiple systems, against firewalls, etc.
For using this code I recommend that you use two different computers, either on a network or on the Internet. The firewall on both computers (but especially on the server computer) should be shut down to prevent the port we’re going to use being locked. However, the beauty of this application is that you can use a single computer to test it; you can run the server and the client application on the same computer and transfer files from one path to another.
Speaking of server and client, in this tutorial we’re going to build two applications. One is the server that listens for connections on a certain port and receives files in streams and then saves them to the local C:\ drive, and one is the client that connects to the server application on the same port the server is listening to.
The application is developed using .NET Framework 2.0 in Visual Studio 2005, however you should be able to convert it to .NET Framework 1.1 rather easily.
How the file transfer works
First we need to create the server application. The server application pictured below under the arrow will be the one receiving a file from a remote computer and storing it on the hard drive. Prior to receiving the actual file, it will receive information about the file: the file name and size. This information will be passed by the client, and it will be retrievied using the FileInfo class from the .NET Framework.
The first thing you need to do is to enter a port (815 is normally not taken on most computers) and click on “Start Server”. You will see a message “Starting the server…” shortly followed by “The server has started. Please connect the client to 192.168.0.1”. Of course, the IP will probably differ in your case, so you should write it down because you will need to enter it in the client application later. At this time you should leave the server application running and switch to the client application (Network File Sender).
The client application will first need to connect to the server, so you need to press the “Connect” button. Before pressing the button however, please make sure that you entered the same port as in the server window, and the IP that you were prompted to enter.
Hopefully you will not experience any problems and you will see the “Successfully connected to server” message. After that you will be able to press the “Send New File” button which will let you browse to a file. Right after you pick the file, you will see that the progress bar on the server (Network File Receiver) starts making progress and the file is being transfered. So where is the file? By default the code saves it to C:\ so look for it there. After the file is sent the client and server-side streams and connections are closed to release resources. To prepare for an upcoming connection, the server then starts again (of course, you can change this to your liking).
Receiving the file – creating the server
We will start with creating the server, since we first need an application to listen for connections before we actually send a file.
Begin by creating a new Windows Application project in Visual Studio 2005. Add to it two textboxes: txtPort stores the port that we are listening to, and txtLog will be the MultiLine textbox to which we will append text containing the status of the server. Add two buttons: btnStart and btnStop which are self explanatory, and finally a progress bar entitled prgDownload where we will display the progress of the file being transfered.
Switch to the code view and make sure you add these 4 additional using statements:
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;
Next we need to create some objects we’ll be setting and accessing throughout the application:
// The thread in which the file will be received
private Thread thrDownload;
// The stream for writing the file to the hard-drive
private Stream strLocal;
// The network stream that receives the file
private NetworkStream strRemote;
// The TCP listener that will listen for connections
private TcpListener tlsServer;
// Delegate for updating the logging textbox
private delegate void UpdateStatusCallback(string StatusMessage);
// Delegate for updating the progressbar
private delegate void UpdateProgressCallback(Int64 BytesRead, Int64 TotalBytes);
// For storing the progress in percentages
private static int PercentProgress;
Now we need to create the most important part of the application – the StartReceiving() method which listens and establishes connections with the client, and also receives the files. This method will be called from a thread, the thrDownload thread we defined above.
private void StartReceiving()
{
// There are many lines in here that can cause an exception
try
{
// Get the hostname of the current computer (the server)
string hstServer = Dns.GetHostName();
// Get the IP of the first network device, however this can prove unreliable on certain configurations
IPAddress ipaLocal = Dns.GetHostEntry(hstServer).AddressList[0];
// If the TCP listener object was not created before, create it
if (tlsServer == null)
{
// Create the TCP listener object using the IP of the server and the specified port
tlsServer = new TcpListener(ipaLocal, Convert.ToInt32(txtPort.Text));
}
// Write the status to the log textbox on the form (txtLog)
this.Invoke(new UpdateStatusCallback(this.UpdateStatus), new object[] { “Starting the server…\r\n” });
// Start the TCP listener and listen for connections
tlsServer.Start();
// Write the status to the log textbox on the form (txtLog)
this.Invoke(new UpdateStatusCallback(this.UpdateStatus), new object[] { “The server has started. Please connect the client to “ + ipaLocal.ToString() + “\r\n” });
// Accept a pending connection
TcpClient tclServer = tlsServer.AcceptTcpClient();
// Write the status to the log textbox on the form (txtLog)
this.Invoke(new UpdateStatusCallback(this.UpdateStatus), new object[] { “The server has accepted the client\r\n” });
// Receive the stream and store it in a NetworkStream object
strRemote = tclServer.GetStream();
// Write the status to the log textbox on the form (txtLog)
this.Invoke(new UpdateStatusCallback(this.UpdateStatus), new object[] { “The server has received the stream\r\n” });
// For holding the number of bytes we are reading at one time from the stream
int bytesSize = 0;
// The buffer that holds the data received from the client
byte[] downBuffer = new byte[2048];
// Read the first buffer (2048 bytes) from the stream – which represents the file name
bytesSize = strRemote.Read(downBuffer, 0, 2048);
// Convert the stream to string and store the file name
string FileName = System.Text.Encoding.ASCII.GetString(downBuffer, 0, bytesSize);
// Set the file stream to the path C:\ plus the name of the file that was on the sender’s computer
strLocal = new FileStream(@”C:\” + FileName, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
// The buffer that holds the data received from the client
downBuffer = new byte[2048];
// Read the next buffer (2048 bytes) from the stream – which represents the file size
bytesSize = strRemote.Read(downBuffer, 0, 2048);
// Convert the file size from bytes to string and then to long (Int64)
long FileSize = Convert.ToInt64(System.Text.Encoding.ASCII.GetString(downBuffer, 0, bytesSize));
// Write the status to the log textbox on the form (txtLog)
this.Invoke(new UpdateStatusCallback(this.UpdateStatus), new object[] { “Receiving file “ + FileName + ” (” + FileSize + ” bytes)\r\n” });
// The buffer size for receiving the file
downBuffer = new byte[2048];
// From now on we read everything that’s in the stream’s buffer because the file content has started
while ((bytesSize = strRemote.Read(downBuffer, 0, downBuffer.Length)) > 0)
{
// Write the data to the local file stream
strLocal.Write(downBuffer, 0, bytesSize);
// Update the progressbar by passing the file size and how much we downloaded so far to UpdateProgress()
this.Invoke(new UpdateProgressCallback(this.UpdateProgress), new object[] { strLocal.Length, FileSize });
}
// When this point is reached, the file has been received and stored successfully
}
finally
{
// This part of the method will fire no matter wether an error occured in the above code or not
// Write the status to the log textbox on the form (txtLog)
this.Invoke(new UpdateStatusCallback(this.UpdateStatus), new object[] { “The file was received. Closing streams.\r\n” });
// Close the streams
strLocal.Close();
strRemote.Close();
// Write the status to the log textbox on the form (txtLog)
this.Invoke(new UpdateStatusCallback(this.UpdateStatus), new object[] { “Streams are now closed.\r\n” });
// Start the server (TCP listener) all over again
StartReceiving();
}
}
If you are not used to networking with .NET applications, or if you have only started C# recently you may have found the above code a little overwhelming. If so, you may find the Creating a download manage in C# tutorial I created a short while back, to be more helpful for a beginner, since it has explanations on why we are using delegates to update the form from a thread, and how the streams and buffers work.
In case you are wondering why we are reading the data stream two times, 2048 bytes at a time and then we are suddenly looping through all the data using a while loop in the code above, here is why: in the client application that we are going to build, we will first send the file name within the first 2048 bytes to the server, using the same stream we will be using for transfering the actual file. Thus, first we only read the first 2048 bytes and store then in the FileName variable because the client application will send the file name within the first 2048 bytes. Then we repeat the same thing for transfering the size of the file – we read the second set of 2048 bytes which contain the file size and store this into the FileSize variable. We need to know the file name so that on the server we can save the file using the same file name as it was in the client (this includes the extension); as for the file size, we want to know that so that we can calculate the current progress of the download.
After we pass this, there is no other data coming in the stream other than the file itself, so we loop through the stream until we reach the end, and we continue writing 2048 bytes of the file at a time on the server computer.
The next method we need to implement is the one we created a delegate for – UpdateStatus() which updates the txtLog textbox with the current status of the application:
private void UpdateStatus(string StatusMessage)
{
// Append the status to the log textbox text
txtLog.Text += StatusMessage;
}
There is one more method we created a delegate for, which is also accessed from inside the thread. This method updates the progress bar with the current progress of the download:
private void UpdateProgress(Int64 BytesRead, Int64 TotalBytes)
{
if (TotalBytes > 0)
{
// Calculate the download progress in percentages
PercentProgress = Convert.ToInt32((BytesRead * 100) / TotalBytes);
// Make progress on the progress bar
prgDownload.Value = PercentProgress;
}
}
Now there’s one more important thing left to do – start the thread with the StartReceiving() method, all inside the btnStart click event. To create the Click event of this button you can double click it in form designer view. Inside it, add the following code:
thrDownload = new Thread(StartReceiving);
thrDownload.Start();
You will probably also want to close the streams using the btnStop button, so click this button and add the following code:
strLocal.Close();
strRemote.Close();
txtLog.Text += “Streams are now closed.\r\n”;
That is all, you now have a C# server ready to listen for connections and receive files. Below is the entire code for this application in case you prefer an overall look:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;
namespace NetworkFileReceiver
{
public partial class Form1 : Form
{
// The thread in which the file will be received
private Thread thrDownload;
// The stream for writing the file to the hard-drive
private Stream strLocal;
// The network stream that receives the file
private NetworkStream strRemote;
// The TCP listener that will listen for connections
private TcpListener tlsServer;
// Delegate for updating the logging textbox
private delegate void UpdateStatusCallback(string StatusMessage);
// Delegate for updating the progressbar
private delegate void UpdateProgressCallback(Int64 BytesRead, Int64 TotalBytes);
// For storing the progress in percentages
private static int PercentProgress;
public Form1()
{
InitializeComponent();
}
private void btnStart_Click(object sender, EventArgs e)
{
thrDownload = new Thread(StartReceiving);
thrDownload.Start();
}
private void StartReceiving()
{
// There are many lines in here that can cause an exception
try
{
// Get the hostname of the current computer (the server)
string hstServer = Dns.GetHostName();
// Get the IP of the first network device, however this can prove unreliable on certain configurations
IPAddress ipaLocal = Dns.GetHostEntry(hstServer).AddressList[0];
// If the TCP listener object was not created before, create it
if (tlsServer == null)
{
// Create the TCP listener object using the IP of the server and the specified port
tlsServer = new TcpListener(ipaLocal, Convert.ToInt32(txtPort.Text));
}
// Write the status to the log textbox on the form (txtLog)
this.Invoke(new UpdateStatusCallback(this.UpdateStatus), new object[] { “Starting the server…\r\n” });
// Start the TCP listener and listen for connections
tlsServer.Start();
// Write the status to the log textbox on the form (txtLog)
this.Invoke(new UpdateStatusCallback(this.UpdateStatus), new object[] { “The server has started. Please connect the client to “ + ipaLocal.ToString() + “\r\n” });
// Accept a pending connection
TcpClient tclServer = tlsServer.AcceptTcpClient();
// Write the status to the log textbox on the form (txtLog)
this.Invoke(new UpdateStatusCallback(this.UpdateStatus), new object[] { “The server has accepted the client\r\n” });
// Receive the stream and store it in a NetworkStream object
strRemote = tclServer.GetStream();
// Write the status to the log textbox on the form (txtLog)
this.Invoke(new UpdateStatusCallback(this.UpdateStatus), new object[] { “The server has received the stream\r\n” });
// For holding the number of bytes we are reading at one time from the stream
int bytesSize = 0;
// The buffer that holds the data received from the client
byte[] downBuffer = new byte[2048];
// Read the first buffer (2048 bytes) from the stream – which represents the file name
bytesSize = strRemote.Read(downBuffer, 0, 2048);
// Convert the stream to string and store the file name
string FileName = System.Text.Encoding.ASCII.GetString(downBuffer, 0, bytesSize);
// Set the file stream to the path C:\ plus the name of the file that was on the sender’s computer
strLocal = new FileStream(@”C:\” + FileName, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
// The buffer that holds the data received from the client
downBuffer = new byte[2048];
// Read the next buffer (2048 bytes) from the stream – which represents the file size
bytesSize = strRemote.Read(downBuffer, 0, 2048);
// Convert the file size from bytes to string and then to long (Int64)
long FileSize = Convert.ToInt64(System.Text.Encoding.ASCII.GetString(downBuffer, 0, bytesSize));
// Write the status to the log textbox on the form (txtLog)
this.Invoke(new UpdateStatusCallback(this.UpdateStatus), new object[] { “Receiving file “ + FileName + ” (“ + FileSize + ” bytes)\r\n” });
// The buffer size for receiving the file
downBuffer = new byte[2048];
// From now on we read everything that’s in the stream’s buffer because the file content has started
while ((bytesSize = strRemote.Read(downBuffer, 0, downBuffer.Length)) > 0)
{
// Write the data to the local file stream
strLocal.Write(downBuffer, 0, bytesSize);
// Update the progressbar by passing the file size and how much we downloaded so far to UpdateProgress()
this.Invoke(new UpdateProgressCallback(this.UpdateProgress), new object[] { strLocal.Length, FileSize });
}
// When this point is reached, the file has been received and stored successfuly
}
finally
{
// This part of the method will fire no matter wether an error occured in the above code or not
// Write the status to the log textbox on the form (txtLog)
this.Invoke(new UpdateStatusCallback(this.UpdateStatus), new object[] { “The file was received. Closing streams.\r\n” });
// Close the streams
strLocal.Close();
strRemote.Close();
// Write the status to the log textbox on the form (txtLog)
this.Invoke(new UpdateStatusCallback(this.UpdateStatus), new object[] { “Streams are now closed.\r\n” });
// Start the server (TCP listener) all over again
StartReceiving();
}
}
private void UpdateStatus(string StatusMessage)
{
// Append the status to the log textbox text
txtLog.Text += StatusMessage;
}
private void UpdateProgress(Int64 BytesRead, Int64 TotalBytes)
{
if (TotalBytes > 0)
{
// Calculate the download progress in percentages
PercentProgress = Convert.ToInt32((BytesRead * 100) / TotalBytes);
// Make progress on the progress bar
prgDownload.Value = PercentProgress;
}
}
private void btnStop_Click(object sender, EventArgs e)
{
strLocal.Close();
strRemote.Close();
txtLog.Text += “Streams are now closed.\r\n”;
}
}
}
You should now compile and run this application. If everything appears to work fine, your next move is creating the file sender application:
Sending the file – creating the client
Start another Windows Application project in Visual Studio 2005 and add to it 3 textboxes and 3 buttons. The textboxes are: txtPort, txtServer and txtLog. The buttons are btnConnect, btnDisconnect and btnSend. Similar to what we did in the server application, you will need to switch to code view and add the following using statements:
using System.Net;
using System.Net.Sockets;
using System.IO;
Follow by defining the following objects inside the class:
// The TCP client will connect to the server using an IP and a port
TcpClient tcpClient;
// The file stream will read bytes from the local file you are sending
FileStream fstFile;
// The network stream will send bytes to the server application
NetworkStream strRemote;
Before sending a file we need to establish a successful connection to the server. Inside the application’s class we’re going to build a small method that does just that:
private void ConnectToServer(string ServerIP, int ServerPort)
{
// Create a new instance of a TCP client
tcpClient = new TcpClient();
try
{
// Connect the TCP client to the specified IP and port
tcpClient.Connect(ServerIP, ServerPort);
txtLog.Text += “Successfully connected to server\r\n”;
}
catch (Exception exMessage)
{
// Display any possible error
txtLog.Text += exMessage.Message;
}
}
Now we’re going to call this method from the click event of btnConnect, so double click the button in design view so that you get the Click event automatically created, then inside it place the following code:
// Call the ConnectToServer method and pass the parameters entered by the user
ConnectToServer(txtServer.Text, Convert.ToInt32(txtPort.Text));
At this point if you open the server application we created and set it to monitor on port 815, then run the client application and press the btnConnect button, you should see in the server application window how the connection is accepted.
Moving on, we haven’t yet seen the code that actually sends the file to the server. We want to have this code inside the Click event of btnSend, thus double click that button and inside the event use the following chunk of code:
// If tclClient is not connected, try a connection
if (tcpClient.Connected == false)
{
// Call the ConnectToServer method and pass the parameters entered by the user
ConnectToServer(txtServer.Text, Convert.ToInt32(txtPort.Text));
}
// Prompt the user for opening a file
if (openFile.ShowDialog() == DialogResult.OK)
{
txtLog.Text += “Sending file information\r\n”;
// Get a stream connected to the server
strRemote = tcpClient.GetStream();
byte[] byteSend = new byte[tcpClient.ReceiveBufferSize];
// The file stream will read bytes from the file that the user has chosen
fstFile = new FileStream(openFile.FileName, FileMode.Open, FileAccess.Read);
// Read the file as binary
BinaryReader binFile = new BinaryReader(fstFile);
// Get information about the opened file
FileInfo fInfo = new FileInfo(openFile.FileName);
// Get and store the file name
string FileName = fInfo.Name;
// Store the file name as a sequence of bytes
byte[] ByteFileName = new byte[2048];
ByteFileName = System.Text.Encoding.ASCII.GetBytes(FileName.ToCharArray());
// Write the sequence of bytes (the file name) to the network stream
strRemote.Write(ByteFileName, 0, ByteFileName.Length);
// Get and store the file size
long FileSize = fInfo.Length;
// Store the file size as a sequence of bytes
byte[] ByteFileSize = new byte[2048];
ByteFileSize = System.Text.Encoding.ASCII.GetBytes(FileSize.ToString().ToCharArray());
// Write the sequence of bytes (the file size) to the network stream
strRemote.Write(ByteFileSize, 0, ByteFileSize.Length);
txtLog.Text += “Sending the file “ + FileName + ” (“ + FileSize + ” bytes)\r\n”;
// Reset the number of read bytes
int bytesSize = 0;
// Define the buffer size
byte[] downBuffer = new byte[2048];
// Loop through the file stream of the local file
while ((bytesSize = fstFile.Read(downBuffer, 0, downBuffer.Length)) > 0)
{
// Write the data that composes the file to the network stream
strRemote.Write(downBuffer, 0, bytesSize);
}
// Update the log textbox and close the connections and streams
txtLog.Text += “File sent. Closing streams and connections.\r\n”;
tcpClient.Close();
strRemote.Close();
fstFile.Close();
txtLog.Text += “Streams and connections are now closed.\r\n”;
}
As you can see here, using the stream we are first sending to the server the file name; the server on its side will read this piece of stream and store the file name in a variable for later use. Same happens for the file size, which is retrieved just like the file name using the FileInfo class and written to the stream. After we send these two pices of information, we start sending the actual content of the file (in binary). The content is sent by looping through yet another stream – the file stream, a local stream that reads the file from the hard drive. While we read pieces of this local stream we write pieces to the network stream so that the server can receive them and save them, until the very last byte of the file when the while loop ends.
After that the streams and connections are closed, and the client has received the file.
There’s one more piece of this application that we haven’t implemented yet, the disconnection from the server. This happens when btnDisconnect is clicked:
// Close connections and streams and update the log textbox
tcpClient.Close();
strRemote.Close();
fstFile.Close();
txtLog.Text += “Disconnected from server.\r\n”;
We’re pretty much finished with this tutorial. I hope you enjoyed it and didn’t experience too many problems with using this code on your system.
Below is the complete code of the network file sender application (the client):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.IO;
namespace NetworkFileSender
{
public partial class Form1 : Form
{
// The TCP client will connect to the server using an IP and a port
TcpClient tcpClient;
// The file stream will read bytes from the local file you are sending
FileStream fstFile;
// The network stream will send bytes to the server application
NetworkStream strRemote;
public Form1()
{
InitializeComponent();
}
private void ConnectToServer(string ServerIP, int ServerPort)
{
// Create a new instance of a TCP client
tcpClient = new TcpClient();
try
{
// Connect the TCP client to the specified IP and port
tcpClient.Connect(ServerIP, ServerPort);
txtLog.Text += “Successfully connected to server\r\n”;
}
catch (Exception exMessage)
{
// Display any possible error
txtLog.Text += exMessage.Message;
}
}
private void btnConnect_Click(object sender, EventArgs e)
{
// Call the ConnectToServer method and pass the parameters entered by the user
ConnectToServer(txtServer.Text, Convert.ToInt32(txtPort.Text));
}
private void btnSend_Click(object sender, EventArgs e)
{
// If tclClient is not connected, try a connection
if (tcpClient.Connected == false)
{
// Call the ConnectToServer method and pass the parameters entered by the user
ConnectToServer(txtServer.Text, Convert.ToInt32(txtPort.Text));
}
// Prompt the user for opening a file
if (openFile.ShowDialog() == DialogResult.OK)
{
txtLog.Text += “Sending file information\r\n”;
// Get a stream connected to the server
strRemote = tcpClient.GetStream();
byte[] byteSend = new byte[tcpClient.ReceiveBufferSize];
// The file stream will read bytes from the file that the user has chosen
fstFile = new FileStream(openFile.FileName, FileMode.Open, FileAccess.Read);
// Read the file as binary
BinaryReader binFile = new BinaryReader(fstFile);
// Get information about the opened file
FileInfo fInfo = new FileInfo(openFile.FileName);
// Get and store the file name
string FileName = fInfo.Name;
// Store the file name as a sequence of bytes
byte[] ByteFileName = new byte[2048];
ByteFileName = System.Text.Encoding.ASCII.GetBytes(FileName.ToCharArray());
// Write the sequence of bytes (the file name) to the network stream
strRemote.Write(ByteFileName, 0, ByteFileName.Length);
// Get and store the file size
long FileSize = fInfo.Length;
// Store the file size as a sequence of bytes
byte[] ByteFileSize = new byte[2048];
ByteFileSize = System.Text.Encoding.ASCII.GetBytes(FileSize.ToString().ToCharArray());
// Write the sequence of bytes (the file size) to the network stream
strRemote.Write(ByteFileSize, 0, ByteFileSize.Length);
txtLog.Text += “Sending the file “ + FileName + ” (“ + FileSize + ” bytes)\r\n”;
// Reset the number of read bytes
int bytesSize = 0;
// Define the buffer size
byte[] downBuffer = new byte[2048];
// Loop through the file stream of the local file
while ((bytesSize = fstFile.Read(downBuffer, 0, downBuffer.Length)) > 0)
{
// Write the data that composes the file to the network stream
strRemote.Write(downBuffer, 0, bytesSize);
}
// Update the log textbox and close the connections and streams
txtLog.Text += “File sent. Closing streams and connections.\r\n”;
tcpClient.Close();
strRemote.Close();
fstFile.Close();
txtLog.Text += “Streams and connections are now closed.\r\n”;
}
}
private void btnDisconnect_Click(object sender, EventArgs e)
{
// Close connections and streams and update the log textbox
tcpClient.Close();
strRemote.Close();
fstFile.Close();
txtLog.Text += “Disconnected from server.\r\n”;
}
}
}