Part 2. UDP Sockets (10 points)
In this part, you will reimplement your server using UDP (User Datagram Protocol) sockets instead of TCP. As with Part 1, the client code (to test your server) is provided for you. Note that the client will again print out time elapsed, which is relevant to Part 3.
Your task
In your assignment repo, cd into the udp directory.
Create a new cargo project called server
with cargo new server. cd into the server directory.
This is where you will implement your UDP web server.
You will have 3 main tasks to do with your server:
- Create (and bind to) a UDP socket.
- Receive a message from that UDP socket.
- Send a message back to the client through the UDP socket.
To do this, you will follow the examples in the Rust documentation for UDP sockets. Pay particularly close attention to the first example; you can (and should) copy code directly from the documentation.
In the first example, the server receives a message from the client (stored in the buf array). Notice that the example in the documentation
uses an array size of 10 to receive the incoming message. This will likely be too small. I used 1024 in my implementation.
The call to recv_from returns two objects: the size of the message received (amt) and the address of the sender (src).
The server then reverses that message and sends it back to the client. You should not reverse buf and send it back to the client.
Instead, create a new response with: let response = "HTTP/1.1 200 OK\r\n\r\n";.
You should then send this response back to the client with the socket.send_to() method. send_to() takes as input two arguments: (1) the
message to send, and (2) a reference to the address to send it to (the client’s address). We already know the address! We got that when we received the message (src).
We cannot pass in a String or &str to send_to() as the message we’re sending, because the method expects the message in bytes (not as a string). The good news is that this is an easy conversion! If you have a &str response, you can convert it by doing response.as_bytes().
When you receive from a UDP socket, it stores the received message as an array of bytes. You will want to convert this
to a String to print it out. One way you can do this is with String::from_utf8_lossy.
This method will take as input an array of bytes, and convert it to valid UTF-8 string. If there are any
invalid UTF-8 characters, this method will replace them with a replacement character.
You can find more details in the documentation.
You can use this method in the following way: let request = String::from_utf8_lossy(buf). This will create a String, that
you should then print out with println!("Request:\n{request}");.
Setting the Address
As with Part 1, you should still use the loopback address ("127.0.0.1"). Additionally, you should pick a new port number for your server. The code in the documentation uses 34254; you should replace this with any 4 digit number of your choice (that’s greater than 1024). It should be different from the port number you used in Part 1.
Now look at the client code in udp/client/src/main.rs. You’ll notice this looks a little different than the TCP client because there are 2 different port numbers (lines 5 and 6). First, replace the todo!() on line 6 with the port number you’ve chosen for your server. For example, if I chose 1025 for my server, I would replace line 6 with const SERVER_PORT_NUMBER: u32 = 1025;. This step tells the client where to send the messages to.
Because UDP doesn’t set up a connection between the client and server, we need to give the client an explicit port number (which we didn’t need to do with TCP). Choose another (different) 4 digit port number. Replace the todo!() in line 5 in the client code with that number. For example, I can choose 1026, and I would replace line 5 with const CLIENT_PORT_NUMBER: u32 = 1026;.
Now you’re ready to test your UDP server!
Testing Your Server
You will test your server in the same way you did for Part 1. You will need to have 2 terminals open (you can open multiple terminals in VSCode, or you can open up separate terminals). Make sure you’re ssh’ed into mcnulty on both.
In the first terminal, you will start the server by running cargo run from the udp/server directory.
In the other terminal, you will run the client by doing cargo run from the udp/client directory.
You should only run one server at a time. This means that you shouldn’t start your UDP server if your TCP
server is still running. Make sure to close your server when you’re done. You can do this by typing Ctrl-C in the terminal in which
the server is running.
If your server is working correctly, it should print out the request it receives from the client. The client
will print out the response it gets from your server (which should be HTTP/1.1 200 OK).
Make note of the value the client prints out for time elapsed; you will need it for the next part.