Recap
Yesterday, we set up a simple server-client connection with tokio
, through which we were able to send messages to the server from the client in the form of plain text. Today we’re going to start with file transferring. My first idea was to start with sharing the name and the size of the file we are sending first, so the server knows what and how much to look for when receiving the file. But first we have a bug to squash from the last log.
The critter 🐛
I was messing around with sending messages to the server and I noticed that as soon as the client disconnects, the server output just vomits a bunch of
Received:
Received:
Received:
messages, seemingly infinitely. At first I was pretty clueless as to what’s going on, but after a quick look-up, I found out that it’s because of something called zero-length reads.
Zero-length reads happen when the client disconnects, and the server doesn’t automatically close the socket, instead trying to read from it continuously. This results in it reading 0 bytes over and over again.
This issue can be solved with a quick check when reading the message on the server side
loop {
match socket.read(&mut buffer).await {
Ok(0) => {
println!("Client has closed the connection.");
break;
}
Ok(bytes_read) => println!(
"Recieved: {}",
String::from_utf8_lossy(&buffer[..bytes_read])
),
Err(e) => {
eprintln!("Failed to read from socket: {}", e);
break;
}
}
}
The change is the first pattern in the match case statement, where we do a quick check to see if bytes_read
is 0, in which case we break out of the feedback loop for said client. See commit for more information. Now let’s get back on track!
Sending metadata
I’m going to be use the std::fs
library to be handling the files. It contains the function, fs::metadata
which takes the path to a file and returns a fs::Metadata
type, from which we can access the data we need, in this case, fs::Metadata::len
, so here’s the first implementation
let mut file_name = String::new();
print!("Enter the file name to send (or type 'exit' to quit): ");
io::stdout().flush().unwrap();
io::stdin().read_line(&mut file_name)?;
/* snip snip */
match fs::metadata(&file_name) {
Ok(metadata) => {
stream
.write_all(format!("{}:{}", &file_name, metadata.len()).as_bytes())
.await?;
println!("Message sent!");
}
Err(e) => {
println!("There has been an error in locating the file:\n{}", e);
}
}
Let’s test this out with an aptly named file,
this has important data that i want to send
Connected to server
Enter the file name to send (or type 'exit' to quit): testfile.txt
Message sent!
And…
Server is running on 127.0.0.1:8080
New connection from: 127.0.0.1:55077
Recieved: testfile.txt:44
Viola! We have successfully sent the name and the size of the file to the server! Woohoo!
Check out the commit on GitHub.
Reflection
That’s all for today! Next, we will start chunking and sending the files to the server. Today’s work was a lot less than I expected to get done, but it was also really fulfilling for me. Slowly working on a project idea that I’m passionate and curious about has really boosted my motivation, and logging my process has helped me push myself to keep working on it, while reigniting my love for writing.
See ya on the next one.