I'm writing an app that talks to a server over TCP connection, and the API is that we exchange binary packets. For update request, there's a packet size limit, so it must be split in multiple ones. For every packet, the client must wait for the acknoledgement packet in response. But the server also sends other updates of its state, so the client has to do this concurrently.
This looks like a good fit for async, but I can't wrap my head around this. How can I write an intermediate library so that it puts all these procedures (send, then wait for a specific packet response) in a future?
const MAX_ITEMS_IN_PACKET = 100;
async fn do_update(client: &mut Client, mut items: Vec<Item>) {
while items.len() > 0 {
let pack: Vec<Item> = items.drain(0..MAX_ITEMS_IN_PACKET).collect();
let status = client
.upload(UploadPacket { pack, request_id: 1 })
.await; // MAGIC HERE
if status.is_err() {
panic!()
}
}
}
struct UploadPacket {
request_id: u16,
pack: Vec<Item>
}
Behind this line, let status = client.upload(pack).await; a lot should happen:
Clientmust send a packet, and store a request id (the server response to this packet will contains this request id, to identify),- when
Clientnotices a packet with request id in some waiting list, it puts the response in that specific future, and the caller will get the awaited return value
Right now, trying to code this in blocking code, I know what to do, but the code that calls Client is big and split across callbacks -- and it must have more objects, to store the state.
struct Uploader {
upload_list: Vec<Item>,
last_request_id: u16,
}
impl Uploader {
fn new(client: &mut Client, mut items: Vec<Item>) ->
anyhow::Result<Self> {
let pack = items.drain(0..MAX_ITEMS_IN_PACKET).collect();
client.send(UploadPacket { pack, request_id: 1 })?;
Self { upload_list: items, last_request_id: 1 }
}
fn handle_packets(&mut self, client: &mut Client, packet: &IncomingPacket) ->
Result<Subscription> {
if packet.request_id != self.last_request_id {
return Ok(Subscription::Stay)
}
if self.upload_list.len() == 0 { return Ok(Subscription::Unsubscribe) }
let pack = self.upload_list.drain(0..MAX_ITEMS_IN_PACKET).collect();
self.last_request_id += 1;
client.send(UploadPacket { pack, request_id: self.last_request_id })?;
Ok(Subcsription::Stay)
}
}
enum Subscription { Stay, Unsubscribe }
So, async makes sense in this context. But I can't understand how to organize and operate futures to accomplish this.
I tried duckduckgoing (googling), but I get generic materials in response. Please suggest what to read on this.