Skip to content

Instantly share code, notes, and snippets.

@define-private-public
Last active May 1, 2023 06:30
Show Gist options
  • Save define-private-public/e75db7dfd50fe5415786e7141f8398b9 to your computer and use it in GitHub Desktop.
Save define-private-public/e75db7dfd50fe5415786e7141f8398b9 to your computer and use it in GitHub Desktop.
UDP based file transfer in C#
// Filename: Block.cs
// Author: Benjamin N. Summerton <define-private-public>
// License: Unlicense (http://unlicense.org/)
using System;
using System.Text;
using System.Linq;
namespace UdpFileTransfer
{
// These are the chunks of data that will be sent across the network
public class Block
{
public UInt32 Number { get; set; }
public byte[] Data { get; set; } = new byte[0];
#region Constructors
// Creates a new block of data w/ the supplied number
public Block(UInt32 number=0)
{
Number = number;
}
// Creates a Block from a byte array
public Block (byte[] bytes)
{
// First four bytes are the number
Number = BitConverter.ToUInt32(bytes, 0);
// Data starts at byte 4
Data = bytes.Skip(4).ToArray();
}
#endregion // Constructors
public override string ToString()
{
// Take some of the first few bits of data and turn that into a string
String dataStr;
if (Data.Length > 8)
dataStr = Encoding.ASCII.GetString(Data, 0, 8) + "...";
else
dataStr = Encoding.ASCII.GetString(Data, 0, Data.Length);
return string.Format(
"[Block:\n" +
" Number={0},\n" +
" Size={1},\n" +
" Data=`{2}`]",
Number, Data.Length, dataStr);
}
// Returns the data in the block as a byte array
public byte[] GetBytes()
{
// Convert meta-data
byte[] numberBytes = BitConverter.GetBytes(Number);
// Join all the data into one bigger array
byte[] bytes = new byte[numberBytes.Length + Data.Length];
numberBytes.CopyTo(bytes, 0);
Data.CopyTo(bytes, 4);
return bytes;
}
}
}
// Filename: NetworkMessage.cs
// Author: Benjamin N. Summerton <define-private-public>
// License: Unlicense (http://unlicense.org/)
using System.Net;
namespace UdpFileTransfer
{
// This is a Simple datastructure that is used in packet queues
public class NetworkMessage
{
public IPEndPoint Sender { get; set; }
public Packet Packet { get; set; }
}
}
// Filename: Packet.cs
// Author: Benjamin N. Summerton <define-private-public>
// License: Unlicense (http://unlicense.org/)
using System;
using System.Text;
using System.Linq;
namespace UdpFileTransfer
{
public class Packet
{
#region Messge Types (Static)
public static UInt32 Ack = BitConverter.ToUInt32(Encoding.ASCII.GetBytes("ACK "), 0);
public static UInt32 Bye = BitConverter.ToUInt32(Encoding.ASCII.GetBytes("BYE "), 0);
public static UInt32 RequestFile = BitConverter.ToUInt32(Encoding.ASCII.GetBytes("REQF"), 0);
public static UInt32 RequestBlock = BitConverter.ToUInt32(Encoding.ASCII.GetBytes("REQB"), 0);
public static UInt32 Info = BitConverter.ToUInt32(Encoding.ASCII.GetBytes("INFO"), 0);
public static UInt32 Send = BitConverter.ToUInt32(Encoding.ASCII.GetBytes("SEND"), 0);
#endregion
// The Fields for the packet
public UInt32 PacketType { get; set; }
public byte[] Payload { get; set; } = new byte[0];
#region Handy Properties
public bool IsAck { get { return PacketType == Ack; } }
public bool IsBye { get { return PacketType == Bye; } }
public bool IsRequestFile { get { return PacketType == RequestFile; } }
public bool IsRequestBlock { get { return PacketType == RequestBlock; } }
public bool IsInfo { get { return PacketType == Info; } }
public bool IsSend { get { return PacketType == Send; } }
public bool IsUnknown { get { return !(IsAck || IsBye || IsRequestFile || IsRequestBlock || IsInfo || IsSend); } }
public string MessageTypeString { get { return Encoding.UTF8.GetString(BitConverter.GetBytes(PacketType)); } }
#endregion
#region Constructors
public Packet(UInt32 packetType)
{
// Set the message type
PacketType = packetType;
}
// Creates a Packet from a byte array
public Packet(byte[] bytes)
{
PacketType = BitConverter.ToUInt32(bytes, 0); // Will grab the first four bytes (which are the type)
// Payload starts at byte 4
Payload = new byte[bytes.Length - 4];
bytes.Skip(4).ToArray().CopyTo(Payload, 0);
}
#endregion // Constructors
public override string ToString()
{
// Take some of the first few bits of data and turn that into a string
String payloadStr;
int payloadSize = Payload.Length;
if (payloadSize > 8)
payloadStr = Encoding.ASCII.GetString(Payload, 0, 8) + "...";
else
payloadStr = Encoding.ASCII.GetString(Payload, 0, payloadSize);
// type string
String typeStr = "UKNOWN";
if (!IsUnknown)
typeStr = MessageTypeString;
return string.Format(
"[Packet:\n" +
" Type={0},\n" +
" PayloadSize={1},\n" +
" Payload=`{2}`]",
typeStr, payloadSize, payloadStr);
}
// Gets the Packet as a byte array
public byte[] GetBytes()
{
// Join the byte arrays
byte[] bytes = new byte[4 + Payload.Length];
BitConverter.GetBytes(PacketType).CopyTo(bytes, 0);
Payload.CopyTo(bytes, 4);
return bytes;
}
}
#region Definite Packets
// ACK
public class AckPacket : Packet
{
public string Message
{
get { return Encoding.UTF8.GetString(Payload); }
set { Payload = Encoding.UTF8.GetBytes(value); }
}
public AckPacket(Packet p=null) :
base(Ack)
{
if (p != null)
Payload = p.Payload;
}
}
// REQF
public class RequestFilePacket : Packet
{
public string Filename
{
get { return Encoding.UTF8.GetString(Payload); }
set { Payload = Encoding.UTF8.GetBytes(value); }
}
public RequestFilePacket(Packet p=null) :
base(RequestFile)
{
if (p != null)
Payload = p.Payload;
}
}
// REQB
public class RequestBlockPacket : Packet
{
public UInt32 Number
{
get { return BitConverter.ToUInt32(Payload, 0); }
set { Payload = BitConverter.GetBytes(value); }
}
public RequestBlockPacket(Packet p = null)
: base(RequestBlock)
{
if (p != null)
Payload = p.Payload;
}
}
// INFO
public class InfoPacket : Packet
{
// Should be an MD5 checksum
public byte[] Checksum
{
get { return Payload.Take(16).ToArray(); }
set { value.CopyTo(Payload, 0); }
}
public UInt32 FileSize
{
get { return BitConverter.ToUInt32(Payload.Skip(16).Take(4).ToArray(), 0); }
set { BitConverter.GetBytes(value).CopyTo(Payload, 16); }
}
public UInt32 MaxBlockSize
{
get { return BitConverter.ToUInt32(Payload.Skip(16 + 4).Take(4).ToArray(), 0); }
set { BitConverter.GetBytes(value).CopyTo(Payload, 16 + 4); }
}
public UInt32 BlockCount
{
get { return BitConverter.ToUInt32(Payload.Skip(16 + 4 + 4).Take(4).ToArray(), 0); }
set { BitConverter.GetBytes(value).CopyTo(Payload, 16 + 4 + 4); }
}
public InfoPacket(Packet p = null)
: base(Info)
{
if (p != null)
Payload = p.Payload;
else
Payload = new byte[16 + 4 + 4 + 4];
}
}
// SEND
public class SendPacket : Packet
{
public Block Block {
get { return new Block(Payload); }
set { Payload = value.GetBytes(); }
}
public SendPacket(Packet p=null)
: base(Send)
{
if (p != null)
Payload = p.Payload;
}
}
#endregion // Definite Packets
}
// Filename: UdpFileReceiver.cs
// Author: Benjamin N. Summerton <define-private-public>
// License: Unlicense (http://unlicense.org/)
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;
using System.Security.Cryptography;
namespace UdpFileTransfer
{
class UdpFileReceiver
{
#region Statics
public static readonly int MD5ChecksumByteSize = 16;
#endregion // Statics
enum ReceiverState {
NotRunning,
RequestingFile,
WaitingForRequestFileACK,
WaitingForInfo,
PreparingForTransfer,
Transfering,
TransferSuccessful,
}
// Connection data
private UdpClient _client;
public readonly int Port;
public readonly string Hostname;
private bool _shutdownRequested = false;
private bool _running = false;
// Receive Data
private Dictionary<UInt32, Block> _blocksReceived = new Dictionary<UInt32, Block>();
private Queue<UInt32> _blockRequestQueue = new Queue<UInt32>();
private Queue<NetworkMessage> _packetQueue = new Queue<NetworkMessage>();
// Other data
private MD5 _hasher;
// Constructor, sets up connection to <hostname> on <port>
public UdpFileReceiver(string hostname, int port)
{
Port = port;
Hostname = hostname;
// Sets a default client to send/receive packets with
_client = new UdpClient(Hostname, Port); // will resolve DNS for us
_hasher = MD5.Create();
}
// Tries to perform a graceful shutdown
public void Shutdown()
{
_shutdownRequested = true;
}
// Tries to grab a file and download it to our local machine
public void GetFile(string filename)
{
// Init the get file state
Console.WriteLine("Requesting file: {0}", filename);
ReceiverState state = ReceiverState.RequestingFile;
byte[] checksum = null;
UInt32 fileSize = 0;
UInt32 numBlocks = 0;
UInt32 totalRequestedBlocks = 0;
Stopwatch transferTimer = new Stopwatch();
// Small function to reset the transfer state
Action ResetTransferState = new Action(() =>
{
state = ReceiverState.RequestingFile;
checksum = null;
fileSize = 0;
numBlocks = 0;
totalRequestedBlocks = 0;
_blockRequestQueue.Clear();
_blocksReceived.Clear();
transferTimer.Reset();
});
// Main loop
_running = true;
bool senderQuit = false;
bool wasRunning = _running;
while (_running)
{
// Check for some new packets (if there are some)
_checkForNetworkMessages();
NetworkMessage nm = (_packetQueue.Count > 0) ? _packetQueue.Dequeue() : null;
// In case the sender is shutdown, quit
bool isBye = (nm == null) ? false : nm.Packet.IsBye;
if (isBye)
senderQuit = true;
// The state
switch (state)
{
case ReceiverState.RequestingFile:
// Create the REQF
RequestFilePacket REQF = new RequestFilePacket();
REQF.Filename = filename;
// Send it
byte[] buffer = REQF.GetBytes();
_client.Send(buffer, buffer.Length);
// Move the state to waiting for ACK
state = ReceiverState.WaitingForRequestFileACK;
break;
case ReceiverState.WaitingForRequestFileACK:
// If it is an ACK and the payload is the filename, we're good
bool isAck = (nm == null) ? false : (nm.Packet.IsAck);
if (isAck)
{
AckPacket ACK = new AckPacket(nm.Packet);
// Make sure they respond with the filename
if (ACK.Message == filename)
{
// They got it, shift the state
state = ReceiverState.WaitingForInfo;
Console.WriteLine("They have the file, waiting for INFO...");
}
else
ResetTransferState(); // Not what we wanted, reset
}
break;
case ReceiverState.WaitingForInfo:
// Verify it's file info
bool isInfo = (nm == null) ? false : (nm.Packet.IsInfo);
if (isInfo)
{
// Pull data
InfoPacket INFO = new InfoPacket(nm.Packet);
fileSize = INFO.FileSize;
checksum = INFO.Checksum;
numBlocks = INFO.BlockCount;
// Allocate some client side resources
Console.WriteLine("Received an INFO packet:");
Console.WriteLine(" Max block size: {0}", INFO.MaxBlockSize);
Console.WriteLine(" Num blocks: {0}", INFO.BlockCount);
// Send an ACK for the INFO
AckPacket ACK = new AckPacket();
ACK.Message = "INFO";
buffer = ACK.GetBytes();
_client.Send(buffer, buffer.Length);
// Shift the state to ready
state = ReceiverState.PreparingForTransfer;
}
break;
case ReceiverState.PreparingForTransfer:
// Prepare the request queue
for (UInt32 id = 1; id <= numBlocks; id++)
_blockRequestQueue.Enqueue(id);
totalRequestedBlocks += numBlocks;
// Shift the state
Console.WriteLine("Starting Transfer...");
transferTimer.Start();
state = ReceiverState.Transfering;
break;
case ReceiverState.Transfering:
// Send a block request
if (_blockRequestQueue.Count > 0)
{
// Setup a request for a Block
UInt32 id = _blockRequestQueue.Dequeue();
RequestBlockPacket REQB = new RequestBlockPacket();
REQB.Number = id;
// Send the Packet
buffer = REQB.GetBytes();
_client.Send(buffer, buffer.Length);
// Some handy info
Console.WriteLine("Sent request for Block #{0}", id);
}
// Check if we have any blocks ourselves in the queue
bool isSend = (nm == null) ? false : (nm.Packet.IsSend);
if (isSend)
{
// Get the data (and save it
SendPacket SEND = new SendPacket(nm.Packet);
Block block = SEND.Block;
_blocksReceived.Add(block.Number, block);
// Print some info
Console.WriteLine("Received Block #{0} [{1} bytes]", block.Number, block.Data.Length);
}
// Requeue any requests that we haven't received
if ((_blockRequestQueue.Count == 0) && (_blocksReceived.Count != numBlocks))
{
for (UInt32 id = 1; id <= numBlocks; id++)
{
if (!_blocksReceived.ContainsKey(id) && !_blockRequestQueue.Contains(id))
{
_blockRequestQueue.Enqueue(id);
totalRequestedBlocks++;
}
}
}
// Did we get all the block we need? Move to the "transfer successful state."
if (_blocksReceived.Count == numBlocks)
state = ReceiverState.TransferSuccessful;
break;
case ReceiverState.TransferSuccessful:
transferTimer.Stop();
// Things were good, send a BYE message
Packet BYE = new Packet(Packet.Bye);
buffer = BYE.GetBytes();
_client.Send(buffer, buffer.Length);
Console.WriteLine("Transfer successful; it took {0:0.000}s with a success ratio of {1:0.000}.",
transferTimer.Elapsed.TotalSeconds, (double)numBlocks / (double)totalRequestedBlocks);
Console.WriteLine("Decompressing the Blocks...");
// Reconstruct the data
if (_saveBlocksToFile(filename, checksum, fileSize))
Console.WriteLine("Saved file as {0}.", filename);
else
Console.WriteLine("There was some trouble in saving the Blocks to {0}.", filename);
// And we're done here
_running = false;
break;
}
// Sleep
Thread.Sleep(1);
// Check for shutdown
_running &= !_shutdownRequested;
_running &= !senderQuit;
}
// Send a BYE message if the user wanted to cancel
if (_shutdownRequested && wasRunning)
{
Console.WriteLine("User canceled transfer.");
Packet BYE = new Packet(Packet.Bye);
byte[] buffer = BYE.GetBytes();
_client.Send(buffer, buffer.Length);
}
// If the server told us to shutdown
if (senderQuit && wasRunning)
Console.WriteLine("The sender quit on us, canceling the transfer.");
ResetTransferState(); // This also cleans up collections
_shutdownRequested = false; // In case we shut down one download, but want to start a new one
}
public void Close()
{
_client.Close();
}
// Trys to fill the queue of packets
private void _checkForNetworkMessages()
{
if (!_running)
return;
// Check that there is something available (and at least four bytes for type)
int bytesAvailable = _client.Available;
if (bytesAvailable >= 4)
{
// This will read ONE datagram (even if multiple have been received)
IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
byte[] buffer = _client.Receive(ref ep);
Packet p = new Packet(buffer);
// Create the message structure and queue it up for processing
NetworkMessage nm = new NetworkMessage();
nm.Sender = ep;
nm.Packet = p;
_packetQueue.Enqueue(nm);
}
}
// Trys to uncompress the blocks and save them to a file
private bool _saveBlocksToFile(string filename, byte[] networkChecksum, UInt32 fileSize)
{
bool good = false;
try
{
// Allocate some memory
int compressedByteSize = 0;
foreach (Block block in _blocksReceived.Values)
compressedByteSize += block.Data.Length;
byte[] compressedBytes = new byte[compressedByteSize];
// Reconstruct into one big block
int cursor = 0;
for (UInt32 id = 1; id <= _blocksReceived.Keys.Count; id++)
{
Block block = _blocksReceived[id];
block.Data.CopyTo(compressedBytes, cursor);
cursor += Convert.ToInt32(block.Data.Length);
}
// Now save it
using (MemoryStream uncompressedStream = new MemoryStream())
using (MemoryStream compressedStream = new MemoryStream(compressedBytes))
using (DeflateStream deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
{
deflateStream.CopyTo(uncompressedStream);
// Verify checksums
uncompressedStream.Position = 0;
byte[] checksum = _hasher.ComputeHash(uncompressedStream);
if (!Enumerable.SequenceEqual(networkChecksum, checksum))
throw new Exception("Checksum of uncompressed blocks doesn't match that of INFO packet.");
// Write it to the file
uncompressedStream.Position = 0;
using (FileStream fileStream = new FileStream(filename, FileMode.Create))
uncompressedStream.CopyTo(fileStream);
}
good = true;
}
catch (Exception e)
{
// Crap...
Console.WriteLine("Could not save the blocks to \"{0}\", reason:", filename);
Console.WriteLine(e.Message);
}
return good;
}
#region Program Execution
public static UdpFileReceiver fileReceiver;
public static void InterruptHandler(object sender, ConsoleCancelEventArgs args)
{
args.Cancel = true;
fileReceiver?.Shutdown();
}
public static void Main(string[] args)
{
// setup the receiver
string hostname = "localhost";//args[0].Trim();
int port = 6000;//int.Parse(args[1].Trim());
string filename = "short_message.txt";//args[2].Trim();
fileReceiver = new UdpFileReceiver(hostname, port);
// Add the Ctrl-C handler
Console.CancelKeyPress += InterruptHandler;
// Get a file
fileReceiver.GetFile(filename);
fileReceiver.Close();
}
#endregion // Program Execution
}
}
// Filename: UdpFileSender.cs
// Author: Benjamin N. Summerton <define-private-public>
// License: Unlicense (http://unlicense.org/)
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;
using System.Security.Cryptography;
namespace UdpFileTransfer
{
public class UdpFileSender
{
#region Statics
public static readonly UInt32 MaxBlockSize = 8 * 1024; // 8KB
#endregion // Statics
enum SenderState
{
NotRunning,
WaitingForFileRequest,
PreparingFileForTransfer,
SendingFileInfo,
WaitingForInfoACK,
Transfering
}
// Connection data
private UdpClient _client;
public readonly int Port;
public bool Running { get; private set; } = false;
// Transfer data
public readonly string FilesDirectory;
private HashSet<string> _transferableFiles;
private Dictionary<UInt32, Block> _blocks = new Dictionary<UInt32, Block>();
private Queue<NetworkMessage> _packetQueue = new Queue<NetworkMessage>();
// Other stuff
private MD5 _hasher;
// Constructor, creates a UdpClient on <port>
public UdpFileSender(string filesDirectory, int port)
{
FilesDirectory = filesDirectory;
Port = port;
_client = new UdpClient(Port, AddressFamily.InterNetwork); // Bind in IPv4
_hasher = MD5.Create();
}
// Prepares the Sender for file transfers
public void Init()
{
// Scan files (only the top directory)
List<string> files = new List<string>(Directory.EnumerateFiles(FilesDirectory));
_transferableFiles = new HashSet<string>(files.Select(s => s.Substring(FilesDirectory.Length + 1)));
// Make sure we have at least one to send
if (_transferableFiles.Count != 0)
{
// Modify the state
Running = true;
// Print info
Console.WriteLine("I'll transfer these files:");
foreach (string s in _transferableFiles)
Console.WriteLine(" {0}", s);
}
else
Console.WriteLine("I don't have any files to transfer.");
}
// signals for a (graceful) shutdown)
public void Shutdown()
{
Running = false;
}
// Main loop of the sender
public void Run()
{
// Transfer state
SenderState state = SenderState.WaitingForFileRequest;
string requestedFile = "";
IPEndPoint receiver = null;
// This is a handly little function to reset the transfer state
Action ResetTransferState = new Action(() =>
{
state = SenderState.WaitingForFileRequest;
requestedFile = "";
receiver = null;
_blocks.Clear();
});
while (Running)
{
// Check for some new messages
_checkForNetworkMessages();
NetworkMessage nm = (_packetQueue.Count > 0) ? _packetQueue.Dequeue() : null;
// Check to see if we have a BYE
bool isBye = (nm == null) ? false : nm.Packet.IsBye;
if (isBye)
{
// Set back to the original state
ResetTransferState();
Console.WriteLine("Received a BYE message, waiting for next client.");
}
// Do an action depending on the current state
switch (state)
{
case SenderState.WaitingForFileRequest:
// Check to see that we got a file request
// If there was a packet, and it's a request file, send and ACK and switch the state
bool isRequestFile = (nm == null) ? false : nm.Packet.IsRequestFile;
if (isRequestFile)
{
// Prepare the ACK
RequestFilePacket REQF = new RequestFilePacket(nm.Packet);
AckPacket ACK = new AckPacket();
requestedFile = REQF.Filename;
// Print info
Console.WriteLine("{0} has requested file file \"{1}\".", nm.Sender, requestedFile);
// Check that we have the file
if (_transferableFiles.Contains(requestedFile))
{
// Mark that we have the file, save the sender as our current receiver
receiver = nm.Sender;
ACK.Message = requestedFile;
state = SenderState.PreparingFileForTransfer;
Console.WriteLine(" We have it.");
}
else
ResetTransferState();
// Send the message
byte[] buffer = ACK.GetBytes();
_client.Send(buffer, buffer.Length, nm.Sender);
}
break;
case SenderState.PreparingFileForTransfer:
// Using the requested file, prepare it in memory
byte[] checksum;
UInt32 fileSize;
if (_prepareFile(requestedFile, out checksum, out fileSize))
{
// It's good, send an info Packet
InfoPacket INFO = new InfoPacket();
INFO.Checksum = checksum;
INFO.FileSize = fileSize;
INFO.MaxBlockSize = MaxBlockSize;
INFO.BlockCount = Convert.ToUInt32(_blocks.Count);
// Send it
byte[] buffer = INFO.GetBytes();
_client.Send(buffer, buffer.Length, receiver);
// Move the state
Console.WriteLine("Sending INFO, waiting for ACK...");
state = SenderState.WaitingForInfoACK;
}
else
ResetTransferState(); // File not good, reset the state
break;
case SenderState.WaitingForInfoACK:
// If it is an ACK and the payload is the filename, we're good
bool isAck = (nm == null) ? false : (nm.Packet.IsAck);
if (isAck)
{
AckPacket ACK = new AckPacket(nm.Packet);
if (ACK.Message == "INFO")
{
Console.WriteLine("Starting Transfer...");
state = SenderState.Transfering;
}
}
break;
case SenderState.Transfering:
// If there is a block request, send it
bool isRequestBlock = (nm == null) ? false : nm.Packet.IsRequestBlock;
if (isRequestBlock)
{
// Pull out data
RequestBlockPacket REQB = new RequestBlockPacket(nm.Packet);
Console.WriteLine("Got request for Block #{0}", REQB.Number);
// Create the response packet
Block block = _blocks[REQB.Number];
SendPacket SEND = new SendPacket();
SEND.Block = block;
// Send it
byte[] buffer = SEND.GetBytes();
_client.Send(buffer, buffer.Length, nm.Sender);
Console.WriteLine("Sent Block #{0} [{1} bytes]", block.Number, block.Data.Length);
}
break;
}
Thread.Sleep(1);
}
// If there was a receiver set, that means we need to notify it to shutdown
if (receiver != null)
{
Packet BYE = new Packet(Packet.Bye);
byte[] buffer = BYE.GetBytes();
_client.Send(buffer, buffer.Length, receiver);
}
state = SenderState.NotRunning;
}
// Shutsdown the underlying UDP client
public void Close()
{
_client.Close();
}
// Trys to fill the queue of packets
private void _checkForNetworkMessages()
{
if (!Running)
return;
// Check that there is something available (and at least four bytes for type)
int bytesAvailable = _client.Available;
if (bytesAvailable >= 4)
{
// This will read ONE datagram (even if multiple have been received)
IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
byte[] buffer = _client.Receive(ref ep);
// Create the message structure and queue it up for processing
NetworkMessage nm = new NetworkMessage();
nm.Sender = ep;
nm.Packet = new Packet(buffer);
_packetQueue.Enqueue(nm);
}
}
// Loads the file into the blocks, returns true if the requested file is ready
private bool _prepareFile(string requestedFile, out byte[] checksum, out UInt32 fileSize)
{
Console.WriteLine("Preparing the file to send...");
bool good = false;
fileSize = 0;
try
{
// Read it in & compute a checksum of the original file
byte[] fileBytes = File.ReadAllBytes(Path.Combine(FilesDirectory, requestedFile));
checksum = _hasher.ComputeHash(fileBytes);
fileSize = Convert.ToUInt32(fileBytes.Length);
Console.WriteLine("{0} is {1} bytes large.", requestedFile, fileSize);
// Compress it
Stopwatch timer = new Stopwatch();
using (MemoryStream compressedStream = new MemoryStream())
{
// Perform the actual compression
DeflateStream deflateStream = new DeflateStream(compressedStream, CompressionMode.Compress, true);
timer.Start();
deflateStream.Write(fileBytes, 0, fileBytes.Length);
deflateStream.Close();
timer.Stop();
// Put it into blocks
compressedStream.Position = 0;
long compressedSize = compressedStream.Length;
UInt32 id = 1;
while (compressedStream.Position < compressedSize)
{
// Grab a chunk
long numBytesLeft = compressedSize - compressedStream.Position;
long allocationSize = (numBytesLeft > MaxBlockSize) ? MaxBlockSize : numBytesLeft;
byte[] data = new byte[allocationSize];
compressedStream.Read(data, 0, data.Length);
// Create a new block
Block b = new Block(id++);
b.Data = data;
_blocks.Add(b.Number, b);
}
// Print some info and say we're good
Console.WriteLine("{0} compressed is {1} bytes large in {2:0.000}s.", requestedFile, compressedSize, timer.Elapsed.TotalSeconds);
Console.WriteLine("Sending the file in {0} blocks, using a max block size of {1} bytes.", _blocks.Count, MaxBlockSize);
good = true;
}
}
catch (Exception e)
{
// Crap...
Console.WriteLine("Could not prepare the file for transfer, reason:");
Console.WriteLine(e.Message);
// Reset a few things
_blocks.Clear();
checksum = null;
}
return good;
}
#region Program Execution
public static UdpFileSender fileSender;
public static void InterruptHandler(object sender, ConsoleCancelEventArgs args)
{
args.Cancel = true;
fileSender?.Shutdown();
}
public static void Main(string[] args)
{
// Setup the sender
string filesDirectory = "Files";//args[0].Trim();
int port = 6000;//int.Parse(args[1].Trim());
fileSender = new UdpFileSender(filesDirectory, port);
// Add the Ctrl-C handler
Console.CancelKeyPress += InterruptHandler;
// Run it
fileSender.Init();
fileSender.Run();
fileSender.Close();
}
#endregion // Program Execution
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment