3

In my code snippet the tokio (v0.3) mpsc:channel receiver only receives a message when the buffer is full. It doesn't matter how big or small the buffer is.

use std::io;
use std::net::{SocketAddr, ToSocketAddrs};
use std::sync::Arc;
use std::time::Duration;
use tokio::net::UdpSocket;
use tokio::sync::mpsc;
use tokio::time::sleep;

const MESSAGE_LENGTH: usize = 1024;

pub struct Peer {
    socket: Arc<UdpSocket>,
}

impl Peer {
    pub fn new<S: ToSocketAddrs>(addr: S) -> Peer {
        let socket = std::net::UdpSocket::bind(addr).expect("could not create socket");

        let peer = Peer {
            socket: Arc::new(UdpSocket::from_std(socket).unwrap()),
        };

        peer.start_inbound_message_handler();
        peer
    }

    pub fn local_addr(&self) -> SocketAddr {
        self.socket.local_addr().unwrap()
    }

    fn start_inbound_message_handler(&self) {
        let socket = self.socket.clone();
        let (tx, rx) = mpsc::channel(1);

        self.start_request_handler(rx);

        tokio::spawn(async move {
            let mut buf = [0u8; MESSAGE_LENGTH];
            loop {
                if let Ok((len, addr)) = socket.recv_from(&mut buf).await {
                    println!("received {} bytes from {}", len, addr);

                    if let Err(_) = tx.send(true).await {
                        println!("error sending msg to request handler");
                    }
                }
            }
        });
    }

    fn start_request_handler(&self, mut receiver: mpsc::Receiver<bool>) {
        tokio::spawn(async move {
            while let Some(msg) = receiver.recv().await {
                println!("got ping request: {:?}", msg);
            }
        });
    }

    pub async fn send_ping(&self, dest: String) -> Result<(), io::Error> {
        let buf = [255u8; MESSAGE_LENGTH];

        self.socket.send_to(&buf[..], &dest).await?;

        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let peer1 = Peer::new("0.0.0.0:0");
    println!("peer1 started on: {}", peer1.local_addr().to_string());

    let peer2 = Peer::new("0.0.0.0:0");
    println!("peer2 started on: {}", peer2.local_addr().to_string());

    peer2.send_ping(peer1.local_addr().to_string()).await?;
    peer2.send_ping(peer1.local_addr().to_string()).await?;

    sleep(Duration::from_secs(100)).await;

    Ok(())
}

Link to the Playground

In the start_inbound_message_handler function I start reading from the socket, if a message was received, a message over the mpsc::channel would be send to the start_request_handler where the processing happens, in this case a simple log output would be written if the receiver receives anything.

In the main function I'm creating two peers, peer1 and peer2, after both peers are created I start a ping request to the first peer. In the start_inbound_message_handler I will receive the data from the udp socket and send a message over the mpsc::channel The send returns without error. The problem is as mentioned before that the receiver will only receive a message when the buffer is full. In this case the buffer is 1. So if I send a second ping the first ping is received. I cannot find out why this happen.

The expected behavior is, if I send a message over the channel, the receiver starts receiving messages immediately and is not waiting until the buffer is full.

createproblem
  • 1,542
  • 16
  • 30

1 Answers1

2

According to the Tokio documentation of from_std():

Creates new UdpSocket from a previously bound std::net::UdpSocket.

This function is intended to be used to wrap a UDP socket from the standard library in the Tokio equivalent. The conversion assumes nothing about the underlying socket; it is left up to the user to set it in non-blocking mode.

This can be used in conjunction with socket2's Socket interface to configure a socket before it's handed off, such as setting options like reuse_address or binding to multiple addresses.

A socket that is not in non-blocking mode will prevent Tokio from working normally.

Just use the tokio function bind(), it is way simpler.

Stargateur
  • 24,473
  • 8
  • 65
  • 91
  • 3
    I assume you meant: "a _blocking_ mode socket will prevent Tokio from working"… – Jmb Nov 03 '20 at 08:27