Rust网络编程库socketpair的使用,实现进程间高效双向通信的Unix域套接字接口

Rust网络编程库socketpair的使用,实现进程间高效双向通信的Unix域套接字接口

这个crate封装了AF_UNIX平台上的socketpair系统调用,并在Windows上使用CreateNamedPipe来模拟这个接口。

它提供了两种接口模式:

  • "stream"模式:对应SOCK_STREAMPIPE_TYPE_BYTE,提供面向流的可靠双向通信
  • "seqpacket"模式:对应SOCK_SEQPACKETPIPE_TYPE_MESSAGE,保留消息边界的数据传输

基础用法示例

// 创建一对已连接的socket
let (mut a, mut b) = socketpair_stream()?;

// 向一端写入数据
writeln!(a, "hello world")?;

// 从另一端读取数据
let mut buf = [0_u8; 4096];
let n = b.read(&mut buf)?;
assert_eq!(str::from_utf8(&buf[..n]).unwrap(), "hello world\n");

完整双向通信示例

use std::io::{Read, Write};
use socketpair::socketpair_stream;

fn main() -> std::io::Result<()> {
    // 创建一对已连接的Unix域套接字
    let (mut socket_a, mut socket_b) = socketpair_stream()?;
    
    // 从socket_a发送数据
    socket_a.write_all(b"Hello from socket_a")?;
    
    // socket_b接收数据
    let mut buf = [0; 1024];
    let n = socket_b.read(&mut buf)?;
    println!("socket_b received: {}", 
        std::str::from_utf8(&buf[..n]).unwrap());
    
    // 反向发送数据
    socket_b.write_all(b"Hello back from socket_b")?;
    
    // socket_a接收回复
    let n = socket_a.read(&mut buf)?;
    println!("socket_a received: {}", 
        std::str::from_utf8(&buf[..n]).unwrap());
    
    Ok(())
}

父子进程通信示例

use std::io::{Read, Write};
use std::os::unix::io::AsRawFd;
use socketpair::socketpair_stream;
use std::process::{Command, Stdio};

fn main() -> std::io::Result<()> {
    // 创建套接字对
    let (mut parent_socket, mut child_socket) = socketpair_stream()?;
    
    // 创建子进程
    let mut child = Command::new("/bin/bash")
        .arg("-c")
        .arg("read -r msg <&3; echo \"Child received: $msg\"; echo \"Hello parent\" >&3")
        .stdin(Stdio::inherit())
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .fd(3, child_socket.as_raw_fd()) // 将socket传递给子进程作为文件描述符3
        .spawn()?;
    
    // 父进程发送消息
    parent_socket.write_all(b"Hello child")?;
    
    // 父进程接收子进程回复
    let mut buf = [0; 1024];
    let n = parent_socket.read(&mut buf)?;
    println!("Parent received: {}", 
        std::str::from_utf8(&buf[..n]).unwrap());
    
    // 等待子进程结束
    child.wait()?;
    Ok(())
}

注意事项

  1. 目前对async-std和tokio的支持暂时禁用,直到这些crate包含所需的I/O安全特性实现
  2. Windows平台使用命名管道模拟Unix域套接字行为
  3. 在多进程场景中,需要注意文件描述符的传递和继承

这个crate为Rust提供了跨平台的高效进程间通信解决方案,特别适合需要低延迟、高吞吐量的本地进程间通信场景。


1 回复

Rust网络编程库socketpair的使用:实现进程间高效双向通信的Unix域套接字接口

介绍

socketpair是Unix系统提供的一个系统调用,用于创建一对已连接的匿名套接字,主要用于同一主机上的进程间通信(IPC)。在Rust中,我们可以通过标准库或第三方库来使用这个功能。

Unix域套接字相比网络套接字有更高的效率,因为数据不需要经过网络协议栈处理,而是直接在操作系统内核中传输。socketpair创建的双向通道特别适合父子进程或线程间的通信。

使用方法

1. 使用标准库

Rust标准库提供了Unix域套接字的支持:

use std::os::unix::net::{UnixStream, UnixListener, UnixDatagram};
use std::io::{Read, Write};

fn main() -> std::io::Result<()> {
    // 创建一对已连接的Unix流套接字
    let (mut socket1, mut socket2) = UnixStream::pair()?;
    
    // 在socket1写入数据
    socket1.write_all(b"Hello from socket1")?;
    
    // 从socket2读取数据
    let mut buf = [0; 1024];
    let n = socket2.read(&mut buf)?;
    println!("Received from socket1: {}", String::from_utf8_lossy(&buf[..n]));
    
    // 反向通信
    socket2.write_all(b"Hello back from socket2")?;
    let n = socket1.read(&mut buf)?;
    println!("Received from socket2: {}", String::from_utf8_lossy(&buf[..n]));
    
    Ok(())
}

2. 使用nix库

对于更底层的控制,可以使用nix库:

use nix::sys::socket::{socketpair, AddressFamily, SockType, SockFlag};
use nix::unistd::{read, write};
use std::os::unix::io::RawFd;

fn main() {
    // 创建socket pair
    let (fd1, fd2): (RawFd, RawFd) = socketpair(
        AddressFamily::Unix,
        SockType::Stream,
        None,
        SockFlag::empty(),
    ).expect("socketpair failed");
    
    // 写入数据到fd1
    let message = b"Hello from fd1";
    write(fd1, message).expect("write failed");
    
    // 从fd2读取数据
    let mut buf = [0u8; 1024];
    let n = read(fd2, &mut buf).expect("read failed");
    println!("Received: {}", String::from_utf8_lossy(&buf[..n]));
    
    // 关闭文件描述符
    nix::unistd::close(fd1).expect("close failed");
    nix::unistd::close(fd2).expect("close failed");
}

3. 父子进程通信示例

socketpair常用于父子进程间通信:

use std::os::unix::net::UnixStream;
use std::process;
use std::io::{Read, Write};

fn main() -> std::io::Result<()> {
    // 创建socket pair
    let (parent_sock, child_sock) = UnixStream::pair()?;
    
    // 创建子进程
    let child = process::Command::new("/bin/sh")
        .arg("-c")
        .arg("echo 'Hello from child' >&3; cat <&3")
        .stdin(process::Stdio::inherit())
        .stdout(process::Stdio::inherit())
        .stderr(process::Stdio::inherit())
        .fd(3, child_sock)
        .spawn()?;
    
    // 父进程写入数据
    parent_sock.write_all(b"Hello from parent\n")?;
    
    // 读取子进程响应
    let mut buf = [0; 1024];
    let n = parent_sock.read(&mut buf)?;
    println!("Parent received: {}", String::from_utf8_lossy(&buf[..n]));
    
    child.wait()?;
    Ok(())
}

完整示例:使用tokio实现异步socketpair通信

use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::UnixStream;

#[tokio::main]
async fn main() -> std::io::Result<()> {
    // 创建一对已连接的Unix流套接字
    let (mut socket1, mut socket2) = UnixStream::pair()?;
    
    // 异步写入数据到socket1
    tokio::spawn(async move {
        socket1.write_all(b"Hello from socket1").await?;
        Ok::<(), std::io::Error>(())
    });
    
    // 异步从socket2读取数据
    let mut buf = [0; 1024];
    let n = socket2.read(&mut buf).await?;
    println!("Received from socket1: {}", String::from_utf8_lossy(&buf[..n]));
    
    // 反向通信
    tokio::spawn(async move {
        socket2.write_all(b"Hello back from socket2").await?;
        Ok::<(), std::io::Error>(())
    });
    
    let n = socket1.read(&mut buf).await?;
    println!("Received from socket2: {}", String::from_utf8_lossy(&buf[..n]));
    
    Ok(())
}

注意事项

  1. socketpair创建的套接字默认是全双工的,可以同时读写
  2. Unix域套接字比TCP/IP套接字更高效,适合高性能IPC场景
  3. 通信的进程需要有相同的用户权限(除非使用抽象命名空间)
  4. 对于需要持久化的通信,可以考虑使用命名Unix域套接字
  5. 在多线程环境中使用时需要注意同步问题

性能优化技巧

  1. 对于大量小消息,考虑使用sendmsg/recvmsg系统调用批量传输
  2. 可以使用SO_SNDBUFSO_RCVBUF调整套接字缓冲区大小
  3. 考虑使用非阻塞I/O或异步I/O(如tokio)提高并发性能
  4. 对于高吞吐场景,可以使用SCM_RIGHTS传递文件描述符而不是数据

socketpair是Unix系统下高效进程间通信的重要工具,Rust提供了多种使用方式,可以根据具体需求选择合适的方法。

回到顶部