How to read a UnixStream

Trying to understand how to process UnixStream from UnixSocket (Tokio version)

Hello, I am trying to make a background daemon process that receives commands from bash and calls a function depending on the command. Using the tokio library.
The script simply connects to a unix socket and should receive commands from another script handling the cli. I want it to work like lxd or docker where you input commands and it executes a function.

The thing is, I just don't know how to process a UnixStream from the tokio library. I've tried reading the documentation but none of it really points to any examples of reading bash commands.

Here is the basic code that doesn't do much so far. As you can see, I'm quite clueless on how the UnixStream works.

#[tokio::main]
async fn main(){
    println!("Running command listener...");
    
    let socket_path = "/tmp/test_socket.sock";
    let _ = std::fs::remove_file(socket_path); // Clean previous runs

    // Bind to Unix socket
    let listener = UnixListener::bind(socket_path).unwrap();
    println!("Daemon listening on {}", socket_path);

    // Main server loop
    while let Ok((mut stream, _addr)) = listener.accept().await{
        println!("Got a client");
        // Handle each new client connection
        tokio::spawn(async move{
            // Try to read the stream here
            stream.writable().await.unwrap();
            stream.write_all(b"hello world").await.unwrap();
            stream.readable().await.unwrap();
            let mut response = String::new();
            stream.read_to_string(&mut response).await.unwrap();
            println!("{}", response);
        });
    }
}

If you could provide any examples of reading bash commands from the Unix Stream, then it would be greatly appreciated.

You don't need writable() because write_all() already includes logic to wait for it to become writable.

Using read_to_string means that you want to continue reading until the connection is closed. It sounds like what you actually want is to read it line-by-line.

To do that you can use a buffered reader.

let lines = BufReader::new(stream).lines();

while let Some(line) = lines.next_line().await? {
    println!("length = {}", line.len())
}
2 Likes

Thanks!
So if I wanted to type and send commands into the UnixSocket to be read by the buffered reader, would I just use the write_all() or something else?

Using write_all() will write the provided bytes, yes.

So after doing some more digging, I've come across this piece from Fast Unix Sockets With Tokio - Blog
where they use:

while let Ok((mut stream, _)) = listener.accept().await {
    println!("Listening on {socket_path}");
    let mut buffer: [u8; 1024] = [0u8; 1024];
    tokio::spawn(async move{
        loop{
            match stream.read(&mut buffer).await{
                Ok(n) => {
                    if n == 0{
                        break;
                    }
                    println!("client: {:?}", String::from_utf8_lossy(&buffer[..n]));
                }
                Err(e) => {
                    eprintln!("Error writing to client; error: {}", e);
                    break;
                }
            }
        }
    }
}

Would that be the equivalent of the sample you provided? Where the loop looks like:

while let Ok((mut stream, _addr)) = listener.accept().await{
    println!("Got a client");
    // Handle each new client connection
    tokio::spawn(async move{
        let mut lines = BufReader::new(stream).lines();
        while let Some(line) = lines.next_line().await.unwrap() {
            println!("length = {}", line.len());
            
        }
    });
}

And what would be the ideal way to process the lines being read?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.