// 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; }
// 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 }