Wrapping my head around tokio server/client


#1

So, I’m trying to learn some networking with tokio. I’m having a really hard time with the abstractions used and what’s worse the documentation is not really there right now.

Here’s my current problem:

I’m trying to do something pretty simple: I have a server that acts as a dumb database. It gets send a Vec, stores it in a HashMap and should send back a id for future retrival. The client can send that data and later retrieve it with the received Id.

Later I would like to be able to be able to deserialize that data with serde on the client side (the server doesn’t really need to know what is being stored).

Each package the has the following format:

+===============+==================+============+==============+==================+
| msg_size: u64 | request_type: u8 | has_id: u8 | id: [u8; 16] | content: Vec<u8> |
+===============+==================+============+==============+==================+

ps: yes, this message pack could be better optimized and stuff, but this is just a proof of concept.

My client/server code is here (the client carries a T generic parameter so I can work with serde later on)

When I try to use this code as a client the connection seems to never be established (the server never prints that something arrived). What’s interesting though is that I never get an error and when I stop the application (using C^) the server does receive b"".

I’m pretty sure my client part of the code is wrong and I would like some help with fixing it.

Anyway, the second part of my problem is with the server itself. If I use this code as a client the server seems to receive packages nonstop. It keeps printing the it received something even though I only sent the package once. What gives?

Thanks in advance. Also, is there any good intro to tokio with more then simple “ping pong” examples? The examples I find are either too simple (ping pong, echo) or too complex (an http-server). It would be nice to have something in between properly explaining how to work with all the abstractions…


#2

<meta>

  • Client code you’ve linked to is incomplete, as there’s no definition of Objeto; in my example, I filled it out just enough to make it compile.
  • TCP clients shouldn’t connect to the unspecified address, so I changed it to local loopback.

</meta>

With that out of the way: you’re right, it’s the client that’s problematic, specifically using wait() on the futures, which you shouldn’t call when using the Tokio event loop. There’s an explicit warning against it in the documentation. Use Core::run() instead:

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Objeto {
    value: i32,
    network: Option<String>,
}

impl ObjetoDominio for Objeto {
    fn get_network_id(&self) -> Option<Uuid> {
        None
    }
}

fn main() {
    let mut core = Core::new().unwrap();
    let conn = Client::<Objeto>::connect(&"127.0.0.1:4000".parse().unwrap(), &core.handle());
    let client = core.run(conn).unwrap();
    let objeto = Objeto {value: 1, network: None};
    let new_f = client.post(None, &objeto)
        .and_then(|id| client.get(id));
    let new = core.run(new_f).unwrap();
    assert_eq!(objeto.value, new.value);
}

This will panic on the last unwrap(), but it will also talk to the server.


#3

Thanks!

With my Objeto everything worked. I’m still trying to figure out
everything, but for now I’m quite happy!


#4

Sorry, it actually didn’t. It looks like the client’s call method makes no sense when I’m trying to post.

So, my client needs to do two things: if it’s posting it should get the struct, serialize it into the defined data format and send it. If it’s getting it should deserialize it. Should I do this into the service? if so how do I differentiate between posting and getting? Should I implement two Services for client? Can you even do that?
If I’m not suposed to do this in a service, then what would I use the service for on the client side?


#5

I haven’t read the implementation very closely (all those unsafe transmutes scared me away :wink: ) but I notice that you have a single codec for the server and the client. That won’t work unless the protocol framing is perfectly symmetric. If it isn’t, the client’s encode should match the server’s decode and vice versa, so you need separate codecs, one for each role.


#6

That makes sense. Those transmutes are just the easiest way to turn a primitive into a series of bytes that I found.