Hello Rustaceans!
This is my first adventure into Rust and I'm excited to be apart of this growing community and I'm even more excited about everything Rust has to offer. I come from a C background so please bear with me, my functional and objected-oriented language experience is sparse to say the least.
For my first project I am attempting to write a reliable UDP server, which I eventually plan to use in other projects. It's really a learning exercise for me. I have the core of the UDP server & client set up: The client can send messages and the server will send a cloned reply. RIght now the server is pretty basic and all of my effort has been getting the client set up (and yes they do share a lot of the same functionality).
#Repo
Link to my GitHub Repo for any curious cats. Latest commit doesn't compile, you'll have to use the second most recent commit (f8176f0
).
#Background
I am using mio
and mioco
to set do some of the heavy lifting. Mio
handles all of the UDP socket, while I use mioco
to pass messages between threads to perform actions on. One thread listens on a socket while the other is prepared to send data on that same socket. A third thread listens for user input from the command line, meant only for the debugging the client and firing off actions.
For the reliability portion (See netbuffers.rs), I have a structure defined which keeps track of 32 transactions, specifically:
- What packets have been sent for this frame set (
sent_packets
andtx_packets
)? - What packets have been received by the server (
rx_acks
)? - What packets need to be resent (
high_priority_acks
)?
We'll call this structure NetworkBuffer
(well because that's its name).
So in C, I would declare a static global instance of NetworkBuffer
and then call helper functions to access it. Something like...
static NetworkBuffer udp_network_buffer;
void remove_packet(some_index) {
udp_network_buffer.sent_packets[some_index] = 0;
}
void insert_packet(Packet) {
udp_network_buffer.sent_packets[some_index] = Packet;
}
int main(...) {
...
}
All I need to do is to include the header file which defined udp_network_buffer
, and anybody could modify it via the helper functions and semaphores (and yes, I know this is not safe, especially in a multi-threaded environment). Okay great.
What do I want?
This is what I want...but more clearly: I want a way for NetworkBuffer
to be created when the program is run and will remain alive until the program is terminated (something something lifetimes). That is truly its lifetime. And then, when the listening thread receives a packet, it can update this struct "I got an ACK" or whatever it may need. Same goes for the tx thread, it could modify the buffer when packets are sent.
#What I have tried
But Rust is not C. I've been trying to get this to work but I haven't had any luck. Here's what I've tried:
- I've tried to specify the lifetime of a statically declared instance of my structure but I couldn't get it to work. I haven't used lifetimes yet in practice but I do know their purpose in theory. Lifetimes are used to tell the compiler how long variables should live and that relation with the functions that use them. Well something like that.
- I tried declaring an object at the root function
main()
and passing it to each thread handler but the problem is the ownership moves after the first thread. Because it does not implement copy. But I can't implement copy because it seems overly complicated. Deriving or implementing copy forNetworkBuffer
requires that Copy is recursively defined for all of its members... Some of them being anVec<T>
.
How would this problem be solved here? Ultimately I need a way to get this reliability scheme implemented so that the sending and receiving threads can access it.
I hope I have explained it well enough. Thanks.