I haven't done much network programming lately so I decided to make a small program. I quickly made this small program within 2 days, it uses .net's TcpClient and TcpListener classes.
All the program does is transfer files to/from computers. Server mode of the program accepts files, while client mode of the program sends files.
I would like any experienced (networking) programmers to tell me what I should improve on.
Looking at the code now I would:
1. try and somehow make reading the file and uploading the file happen on different threads (which will probably make the transfer faster). Same with downloading/saving the file.
2. split the code up a bit mode, its mostly crammed into one method - how would I do this though?
Here are the 4 classes used in the program:
Program.cs...
//By xanather.
using System;
namespace Transfar
{
class Program
{
static void Main(string[] args)
{
Console.Title = "Transfar - Clean and Fast TCP File Transfers";
Console.WriteLine("Loaded: Transfar v1.0");
if (args.Length == 0)
{
ConsoleColor color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("This executable needs to be passed some arguments...");
Console.ForegroundColor = color;
ShowHelp();
Console.Write("Press any key to close this program... ");
Console.ReadKey();
return;
}
else
{
if (args[0] == "-server")
{
Server server;
server = new Server();
server.Start(args);
}
else if (args[0] == "-client")
{
Client client;
client = new Client();
client.Start(args);
}
else
{
ConsoleColor color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Invalid argument(s). Here is the list of arguments...");
Console.ForegroundColor = color;
ShowHelp();
Console.Write("Press any key to close this program... ");
Console.ReadKey();
return;
}
}
}
static void ShowHelp()
{
Console.WriteLine("Available arguments (commands) for this application:");
Console.WriteLine("Argument\t\tDescription");
Console.WriteLine("-server [dir] [port]\n");
Console.WriteLine("\t\t\tStarts the application in server mode so the");
Console.WriteLine("\t\t\tapplication can receive and download files sent by");
Console.WriteLine("\t\t\tTransfar clients. Port forwarding on the router");
Console.WriteLine("\t\t\tmay be required so that the server can listen for");
Console.WriteLine("\t\t\tinternet connections.\n");
Console.WriteLine("\t\t\tThe [dir] argument specifies the folder/directory");
Console.WriteLine("\t\t\tthe server should place received/downloaded files");
Console.WriteLine("\t\t\tfrom clients. This argument is required.\n");
Console.WriteLine("\t\t\tThe [port] argument specifies the port in which the");
Console.WriteLine("\t\t\tserver should listen to for accepting Transfer client");
Console.WriteLine("\t\t\tconnections. Default port is 5655. This argument is");
Console.WriteLine("\t\t\toptional.\n");
Console.WriteLine("-client [address] [file] [port]\n");
Console.WriteLine("\t\t\tStarts the application in client mode so the");
Console.WriteLine("\t\t\tapplication can send and upload files to a Transfar");
Console.WriteLine("\t\t\tserver.\n");
Console.WriteLine("\t\t\tThe [address] argument specifies the server's IP");
Console.WriteLine("\t\t\tin which to connect to. This argument is required.\n");
Console.WriteLine("\t\t\tThe [file] argument specifies the file on disk");
Console.WriteLine("\t\t\tthe client should send/upload to the server.");
Console.WriteLine("\t\t\tThis argument is required.\n");
Console.WriteLine("\t\t\tThe [port] argument specifies which port the client");
Console.WriteLine("\t\t\tshould use to connect to Transfar servers of the same");
Console.WriteLine("\t\t\tport. Default port is 5655. This argument is optional.\n");
}
}
}
ArrayHelper.cs...
//By xanather
using System;
namespace Transfar
{
static class ArrayHelper
{
public static bool Equals(byte[] a, byte[] b)
{
if (a.Length != b.Length)
{
return false;
}
for (int i = 0; i < a.Length; i++)
{
if (a[i] != b[i])
{
return false;
}
}
return true;
}
}
}
Client.cs...
//By xanather
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
namespace Transfar
{
class Client
{
public static readonly byte[] clientVerify = new byte[] { 6, 2, 5, 1, 255 };
public string srcFile;
public int port;
public string address;
public void Start(string[] args)
{
//get address argument
if (args.Length > 1)
{
address = args[1];
}
else
{
ConsoleColor color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("No IP address has been entered.");
Console.WriteLine("It is a required argument.");
Console.ForegroundColor = color;
Console.Write("Press any key to close this program... ");
Console.ReadKey();
return;
}
//get file argument
if (args.Length > 2)
{
if (File.Exists(args[2]))
{
srcFile = args[2];
}
else
{
ConsoleColor color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("The file specified does not exist.");
Console.WriteLine("It is a required argument.");
Console.ForegroundColor = color;
Console.Write("Press any key to close this program... ");
Console.ReadKey();
return;
}
}
else
{
ConsoleColor color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("No file location has been entered.");
Console.WriteLine("It is a required argument.");
Console.ForegroundColor = color;
Console.Write("Press any key to close this program... ");
Console.ReadKey();
return;
}
if (args.Length > 3)
{
try
{
port = int.Parse(args[3]);
if (port < 0 || port > 65565)
{
throw new FormatException();
}
}
catch (FormatException)
{
ConsoleColor color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Invalid port argument...");
Console.ForegroundColor = color;
Console.Write("Press any key to close this program... ");
Console.ReadKey();
return;
}
}
else
{
port = Server.defaultPort;
}
//check the size of the file
try
{
long fileSize = checked(new FileInfo(srcFile).Length);
}
catch (OverflowException)
{
ConsoleColor color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("The file is too big! The maximum size supported is " + long.MaxValue + " bytes.");
Console.ForegroundColor = color;
Console.Write("Press any key to close this program... ");
Console.ReadKey();
return;
}
ProcessConnection();
}
public void ProcessConnection()
{
TcpClient client;
NetworkStream stream;
try
{
Console.WriteLine("Attempting to connect...");
client = new TcpClient(address, port);
stream = client.GetStream();
}
catch (SocketException e)
{
ConsoleColor color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Cannot connect to server.");
Console.WriteLine(e.Message);
Console.ForegroundColor = color;
Console.Write("Press any key to close this program... ");
Console.ReadKey();
return;
}
Console.WriteLine("Connected to server.");
int readSize;
int readAmount;
byte[] readBuffer;
int readAmountTotal;
byte[] writeBuffer;
byte[] writeBuffer1;
//send client verification
stream.Write(clientVerify, 0, clientVerify.Length);
//read server verification
readAmount = Server.serverVerify.Length;
readBuffer = new byte[readAmount];
readAmountTotal = readAmount;
while (readAmount != 0)
{
readSize = stream.Read(readBuffer, readAmountTotal - readAmount, readAmount);
readAmount -= readSize;
}
if (!ArrayHelper.Equals(readBuffer, Server.serverVerify))
{
stream.Close();
client.Close();
ConsoleColor color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Disconnected, invalid server.");
Console.ForegroundColor = color;
Console.Write("Press any key to close this program... ");
Console.ReadKey();
return;
}
//write file name
string fileName = Path.GetFileName(srcFile);
writeBuffer1 = Encoding.UTF8.GetBytes(fileName);
writeBuffer = new byte[writeBuffer1.Length + 1];
writeBuffer[0] = (byte)writeBuffer1.Length;
Buffer.BlockCopy(writeBuffer1, 0, writeBuffer, 1, writeBuffer1.Length);
stream.Write(writeBuffer, 0, writeBuffer.Length);
//read if file already exists in server directory
stream.Read(readBuffer, 0, 1);
if (readBuffer[0] == 0)
{
stream.Close();
client.Close();
ConsoleColor color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Disconnected, the file {0} already exists in the server directory.");
Console.ForegroundColor = color;
Console.Write("Press any key to close this program... ");
Console.ReadKey();
return;
}
//write the size (in bytes) of the file
Console.WriteLine("Now uploading the file...");
long fileSize = new FileInfo(srcFile).Length;
stream.Write(BitConverter.GetBytes(fileSize), 0, 8);
//start uploading the whole file
long writeRemaining = fileSize;
int percent = 0;
int prevPercent = -1;
using (FileStream fileStream = new FileStream(srcFile, FileMode.Open))
{
while (writeRemaining != 0)
{
readAmount = short.MaxValue; //write 65565 chuncks.
if (readAmount > writeRemaining)
{
readAmount = (int)writeRemaining;
}
readBuffer = new byte[readAmount];
fileStream.Read(readBuffer, 0, readAmount);
try
{
stream.Write(readBuffer, 0, readAmount);
}
catch (IOException e)
{
stream.Close();
client.Close();
ConsoleColor color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Disconnected: " + e.Message);
Console.ForegroundColor = color;
Console.Write("Press any key to close this program... ");
Console.ReadKey();
return;
}
writeRemaining -= readAmount;
percent = (int)(((fileSize - writeRemaining) / (double)fileSize * 100));
if (percent != prevPercent)
{
prevPercent = percent;
Console.WriteLine("{0} - Upload at {1}%. {2}KB remaining ({3}KB/{4}KB).", fileName, percent, writeRemaining / 1024, (fileSize - writeRemaining) / 1024, fileSize / 1024);
}
}
}
//write clientVerify for final handshake
stream.Write(clientVerify, 0, clientVerify.Length);
stream.Close();
client.Close();
Console.WriteLine("Upload complete!");
Console.Write("Press any key to close this program... ");
Console.ReadKey();
}
}
}
Server.cs...
//By xanather
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
namespace Transfar
{
class Server
{
public static readonly byte[] serverVerify = new byte[]{10, 10, 35, 244, 21};
public const int defaultPort = 5655;
public int port;
public string destDirectory;
public AutoResetEvent connectionSignal = new AutoResetEvent(false);
public TcpListener listener;
public void Start(string[] args)
{
//get directory argument
if (args.Length > 1)
{
if (!args[1].EndsWith(Path.DirectorySeparatorChar.ToString()))
{
args[1] += Path.DirectorySeparatorChar;
}
if (Directory.Exists(args[1]))
{
destDirectory = args[1];
}
else
{
ConsoleColor color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("The directory specified does not exist.");
Console.WriteLine("It is a required argument.");
Console.ForegroundColor = color;
Console.Write("Press any key to close this program... ");
Console.ReadKey();
return;
}
}
else
{
ConsoleColor color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("No destination directory has been entered.");
Console.WriteLine("It is a required argument.");
Console.ForegroundColor = color;
Console.Write("Press any key to close this program... ");
Console.ReadKey();
return;
}
//get port argument
if (args.Length > 2)
{
try
{
port = int.Parse(args[2]);
if (port < 0 || port > 65565)
{
throw new FormatException();
}
}
catch (FormatException)
{
ConsoleColor color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Invalid port argument...");
Console.ForegroundColor = color;
Console.Write("Press any key to close this program... ");
Console.ReadKey();
return;
}
}
else
{
port = defaultPort;
}
StartListening();
}
public void StartListening()
{
listener = new TcpListener(IPAddress.Any, port);
listener.Start();
while (true)
{
Console.WriteLine("Listening for connections...");
ProcessClientAsync();
connectionSignal.WaitOne();
connectionSignal.Reset();
}
}
public async void ProcessClientAsync()
{
TcpClient client = await listener.AcceptTcpClientAsync();
NetworkStream stream = client.GetStream();
connectionSignal.Set();
//connection variables
string socketAddress = ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString();
int socketPort = int.Parse(((IPEndPoint)client.Client.RemoteEndPoint).Port.ToString());
int readSize;
int readAmount;
byte[] readBuffer;
int readAmountTotal;
//verify client connection
readAmount = Client.clientVerify.Length;
readBuffer = new byte[readAmount];
readAmountTotal = readAmount;
while (readAmount != 0)
{
readSize = stream.Read(readBuffer, readAmountTotal - readAmount, readAmount);
readAmount -= readSize;
}
if (!ArrayHelper.Equals(readBuffer, Client.clientVerify))
{
stream.Close();
client.Close();
Console.WriteLine(string.Format("[{0}:{1}] Warning: Invalid connection request by this IP.", socketAddress, socketPort));
return;
}
//send server verification
stream.Write(serverVerify, 0, serverVerify.Length);
Console.WriteLine(string.Format("[{0}:{1}] Connected.", socketAddress, socketPort));
//read file name
stream.Read(readBuffer, 0, 1);
readAmount = readBuffer[0];
readBuffer = new byte[readAmount];
readAmountTotal = readAmount;
while (readAmount != 0)
{
readSize = stream.Read(readBuffer, readAmountTotal - readAmount, readAmount);
readAmount -= readSize;
}
string fileName = Encoding.UTF8.GetString(readBuffer);
//write if file already exists in destDirectory
if (File.Exists(destDirectory + fileName))
{
stream.Write(new byte[] { 0 }, 0, 1);
stream.Close();
client.Close();
Console.WriteLine(string.Format("[{0}:{1}] Disconnected, tried to save a existing file ({2}).", socketAddress, socketPort, fileName));
return;
}
else
{
stream.Write(new byte[] { 1 }, 0, 1);
}
//read the size (in bytes) of the file
readAmount = 8;
readBuffer = new byte[readAmount];
readAmountTotal = readAmount;
while (readAmount != 0)
{
readSize = stream.Read(readBuffer, readAmountTotal - readAmount, readAmount);
readAmount -= readSize;
}
long fileSize = BitConverter.ToInt64(readBuffer, 0);
//start downloading the whole file
long readRemaining = fileSize;
int percent = 0;
int prevPercent = -1;
Console.WriteLine(string.Format("[{0}:{1}] Now downloading file {2}. Total size: {3}KB.", socketAddress, socketPort, fileName, fileSize / 1024));
using (FileStream fileStream = new FileStream(destDirectory + fileName, FileMode.CreateNew))
{
while (readRemaining != 0)
{
readAmount = short.MaxValue; //read 65565 chuncks.
if (readAmount > readRemaining)
{
readAmount = (int)readRemaining;
}
readBuffer = new byte[readAmount];
readAmountTotal = readAmount;
while (readAmount != 0)
{
try
{
readSize = stream.Read(readBuffer, readAmountTotal - readAmount, readAmount);
}
catch (IOException e)
{
stream.Close();
client.Close();
Console.WriteLine(string.Format("[{0}:{1}] Disconnected: {2}", socketAddress, socketPort, e.Message));
Console.WriteLine(string.Format("[{0}:{1}] The file has not been received in full and may be corrupted.", socketAddress, socketPort, e.Message));
return;
}
readAmount -= readSize;
}
fileStream.Write(readBuffer, 0, readAmountTotal);
readRemaining -= readAmountTotal;
percent = (int)(((fileSize - readRemaining) / (double)fileSize) * 100);
if (percent != prevPercent)
{
prevPercent = percent;
Console.WriteLine("[{0}:{1}] {2} - Download at {3}%. {4}KB remaining ({5}KB/{6}KB).", socketAddress, socketPort, fileName, percent, readRemaining / 1024, (fileSize - readRemaining) / 1024, fileSize / 1024);
}
}
}
//read clientVerify for final handshake
readAmount = Client.clientVerify.Length;
readBuffer = new byte[readAmount];
readAmountTotal = readAmount;
while (readAmount != 0)
{
readSize = stream.Read(readBuffer, readAmountTotal - readAmount, readAmount);
readAmount -= readSize;
}
if (!ArrayHelper.Equals(readBuffer, Client.clientVerify))
{
stream.Close();
client.Close();
Console.WriteLine(string.Format("[{0}:{1}] Warning: Unspecified error, file may not be received properly.", socketAddress, socketPort));
return;
}
else
{
Console.WriteLine(string.Format("[{0}:{1}] {2} - Download complete!", socketAddress, socketPort, fileName));
}
stream.Close();
client.Close();
}
}
}
Ok, that ended up longer than I thought.
All replies are appriciated.
Thanks,
Xanather.