How to communicate a Rust and a Ruby process using a Unix socket pair

二次信任 提交于 2020-04-16 05:49:31

问题


I am trying to communicate a Rust process with a child Ruby process using a Unix socket pair. I have tried the same using only Ruby and it works, but I can't seem to get it to work with Rust.

I have tried passing the "rust_socket" file descriptor to the Ruby script, passing the "ruby_socket" file descriptor to Ruby and different combinations of reading / writing to the socket. I feel like I should be passing the "ruby_socket" file descriptor, but when I do that I get a bad file descriptor error.

// The rust side of things
use std::process::Command;
use std::os::unix::net::UnixStream;
use std::os::unix::io::IntoRawFd;
use std::io::{Read, Write};

fn main() {
    let (rust_socket, mut ruby_socket) = match UnixStream::pair() {
        Ok((rust_socket, ruby_socket)) => (rust_socket, ruby_socket),
        Err(e) => {
            println!("Failed to open socket pair: {:?}", e);
            return;
        }
    };

    let _output = Command::new("ruby")
        .args(&["/home/station/workspace/rust_server/src/client.rb", &rust_socket.into_raw_fd().to_string()])
        .spawn()
        .expect("Failed to start ruby process");

    let mut response = String::new();
    ruby_socket.read_to_string(&mut response).unwrap();
}
# The ruby side of things
require "socket"

begin
  socket = UNIXSocket.for_fd(ARGV.shift.to_i)
  socket.send("Hello world!\n", 0)
ensure
  socket&.close
end

I expected to be able to read the "Hello world!" string from Rust, but it does not work.


回答1:


The problem seems to be that Rust sets all file descriptors to be closed when a child is created by setting the FD_CLOEXEC flag. The only way to get around this seems to be using libc to call fcntl.

Here’s some code that seems to work, but I don’t know Rust, so use at your own risk. Another issue you will have is you need to close the parent side of rust_socket after spawing the child, otherwise read_to_string will block forever waiting for the stream to be closed. You can do this with drop, but you will also need to use AsRawFd rather than IntoRawFd:

use std::process::Command;
use std::os::unix::net::UnixStream;
use std::os::unix::io::AsRawFd;
use std::io::Read;

extern crate libc;

fn main() {
    // Create the socket pair.
    let (rust_socket, mut ruby_socket) = UnixStream::pair().unwrap();

    // Unset FD_CLOEXEC on the socket to be passed to the child.
    let fd = rust_socket.as_raw_fd();
    unsafe {
        let flags = libc::fcntl(fd, libc::F_GETFD);
        libc::fcntl(fd, libc::F_SETFD, flags & !libc::FD_CLOEXEC);
    }

    // Spawn the child
    let _output = Command::new("ruby")
        .args(&["client.rb", &fd.to_string()])
        .spawn();

    // After spawning, close the parents side of rust_socket.
    // If we use IntoRawFd, rust_socket would have been moved by this point
    // so we need AsRawFD instead.
    drop(rust_socket);

    let mut response = String::new();
    ruby_socket.read_to_string(&mut response).unwrap();

    println!("Ruby said '{}'", response);
}


来源:https://stackoverflow.com/questions/55540577/how-to-communicate-a-rust-and-a-ruby-process-using-a-unix-socket-pair

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!